Query - Logical - CreateNode now accepts an optional input. If provided, CreateNode creates a node for each successful Pull from input. If not provided, CreateNode creates a single node
Reviewers: buda, mislav.bradac, teon.banek Reviewed By: teon.banek Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D173
This commit is contained in:
parent
63aae05ec0
commit
f9b91cf680
@ -19,7 +19,7 @@ class Cursor {
|
||||
virtual ~Cursor() {}
|
||||
};
|
||||
|
||||
class CreateOp;
|
||||
class CreateNode;
|
||||
class CreateExpand;
|
||||
class ScanAll;
|
||||
class Expand;
|
||||
@ -28,7 +28,7 @@ class EdgeFilter;
|
||||
class Produce;
|
||||
|
||||
using LogicalOperatorVisitor =
|
||||
::utils::Visitor<CreateOp, CreateExpand, ScanAll, Expand, NodeFilter,
|
||||
::utils::Visitor<CreateNode, CreateExpand, ScanAll, Expand, NodeFilter,
|
||||
EdgeFilter, Produce>;
|
||||
|
||||
class LogicalOperator : public ::utils::Visitable<LogicalOperatorVisitor> {
|
||||
@ -41,33 +41,49 @@ class LogicalOperator : public ::utils::Visitable<LogicalOperatorVisitor> {
|
||||
std::vector<std::shared_ptr<LogicalOperator>> children_;
|
||||
};
|
||||
|
||||
class CreateOp : public LogicalOperator {
|
||||
// TODO add an optional input that (if given) gets pulled
|
||||
// and the create op gets executed for each success
|
||||
// TODO rename to CreateSingle or CreateNode
|
||||
/**
|
||||
* Operator for creating a node. This op is used both for
|
||||
* creating a single node (CREATE statement without
|
||||
* a preceeding MATCH), or multiple nodes (MATCH CREATE).
|
||||
*
|
||||
* This node
|
||||
*/
|
||||
class CreateNode : public LogicalOperator {
|
||||
public:
|
||||
CreateOp(NodeAtom *node_atom) : node_atom_(node_atom) {}
|
||||
DEFVISITABLE(LogicalOperatorVisitor);
|
||||
/**
|
||||
*
|
||||
* @param node_atom
|
||||
* @param input Optional. If nullptr, then a single node will be
|
||||
* created (a single successfull Pull from this Op's Cursor).
|
||||
* If a valid input, then a node will be created for each
|
||||
* successful pull from the given input.
|
||||
*/
|
||||
CreateNode(NodeAtom *node_atom, std::shared_ptr<LogicalOperator> input)
|
||||
: node_atom_(node_atom), input_(input) {}
|
||||
|
||||
void Accept(LogicalOperatorVisitor &visitor) override {
|
||||
visitor.Visit(*this);
|
||||
if (input_) input_->Accept(visitor);
|
||||
visitor.PostVisit(*this);
|
||||
}
|
||||
|
||||
private:
|
||||
class CreateOpCursor : public Cursor {
|
||||
class CreateNodeCursor : public Cursor {
|
||||
public:
|
||||
CreateOpCursor(CreateOp &self, GraphDbAccessor &db)
|
||||
: self_(self), db_(db) {}
|
||||
CreateNodeCursor(CreateNode &self, GraphDbAccessor &db)
|
||||
: self_(self),
|
||||
db_(db),
|
||||
input_cursor_(self.input_ ? self.input_->MakeCursor(db) : nullptr) {}
|
||||
|
||||
bool Pull(Frame &frame, SymbolTable &symbol_table) override {
|
||||
if (!did_create_) {
|
||||
auto new_node = db_.insert_vertex();
|
||||
for (auto label : self_.node_atom_->labels_) new_node.add_label(label);
|
||||
|
||||
ExpressionEvaluator evaluator(frame, symbol_table);
|
||||
for (auto &kv : self_.node_atom_->properties_) {
|
||||
kv.second->Accept(evaluator);
|
||||
new_node.PropsSet(kv.first, evaluator.PopBack());
|
||||
}
|
||||
|
||||
frame[symbol_table[*self_.node_atom_->identifier_]] = new_node;
|
||||
|
||||
if (input_cursor_) {
|
||||
if (input_cursor_->Pull(frame, symbol_table)) {
|
||||
Create(frame, symbol_table);
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
} else if (!did_create_) {
|
||||
Create(frame, symbol_table);
|
||||
did_create_ = true;
|
||||
return true;
|
||||
} else
|
||||
@ -75,18 +91,38 @@ class CreateOp : public LogicalOperator {
|
||||
}
|
||||
|
||||
private:
|
||||
CreateOp &self_;
|
||||
CreateNode &self_;
|
||||
GraphDbAccessor &db_;
|
||||
// optional, used in situations in which this create op
|
||||
// pulls from an input (in MATCH CREATE, CREATE ... CREATE)
|
||||
std::unique_ptr<Cursor> input_cursor_;
|
||||
// control switch when creating only one node (nullptr input)
|
||||
bool did_create_{false};
|
||||
|
||||
/**
|
||||
* Creates a single node and places it in the frame.
|
||||
*/
|
||||
void Create(Frame &frame, SymbolTable &symbol_table) {
|
||||
auto new_node = db_.insert_vertex();
|
||||
for (auto label : self_.node_atom_->labels_) new_node.add_label(label);
|
||||
|
||||
ExpressionEvaluator evaluator(frame, symbol_table);
|
||||
for (auto &kv : self_.node_atom_->properties_) {
|
||||
kv.second->Accept(evaluator);
|
||||
new_node.PropsSet(kv.first, evaluator.PopBack());
|
||||
}
|
||||
frame[symbol_table[*self_.node_atom_->identifier_]] = new_node;
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
std::unique_ptr<Cursor> MakeCursor(GraphDbAccessor &db) override {
|
||||
return std::make_unique<CreateOpCursor>(*this, db);
|
||||
return std::make_unique<CreateNodeCursor>(*this, db);
|
||||
}
|
||||
|
||||
private:
|
||||
NodeAtom *node_atom_ = nullptr;
|
||||
std::shared_ptr<LogicalOperator> input_;
|
||||
};
|
||||
|
||||
class CreateExpand : public LogicalOperator {
|
||||
|
@ -61,16 +61,10 @@ auto GenCreateForPattern(Pattern &pattern, LogicalOperator *input_op,
|
||||
const SymbolTable &symbol_table,
|
||||
std::unordered_set<int> bound_symbols) {
|
||||
auto base = [&](NodeAtom *node) -> LogicalOperator * {
|
||||
if (BindSymbol(bound_symbols, symbol_table.at(*node->identifier_))) {
|
||||
// TODO: Pass input_op when CreateOp gets support for it. This will
|
||||
// support e.g. `MATCH (n) CREATE (m)` and `CREATE (n), (m)`.
|
||||
if (input_op) {
|
||||
throw NotYetImplemented();
|
||||
}
|
||||
return new CreateOp(node);
|
||||
} else {
|
||||
if (BindSymbol(bound_symbols, symbol_table.at(*node->identifier_)))
|
||||
return new CreateNode(node, std::shared_ptr<LogicalOperator>(input_op));
|
||||
else
|
||||
return input_op;
|
||||
}
|
||||
};
|
||||
|
||||
auto collect = [&](LogicalOperator *last_op, NodeAtom *prev_node,
|
||||
|
@ -66,7 +66,7 @@ void Interpret(const std::string &query, GraphDbAccessor &db_accessor,
|
||||
}
|
||||
|
||||
summary["type"] = "r";
|
||||
} else if (auto create = dynamic_cast<CreateOp *>(logical_plan.get())) {
|
||||
} else if (auto create = dynamic_cast<CreateNode *>(logical_plan.get())) {
|
||||
auto cursor = create->MakeCursor(db_accessor);
|
||||
while (cursor->Pull(frame, symbol_table)) {
|
||||
continue;
|
||||
|
@ -259,7 +259,7 @@ TEST(Interpreter, CreateNodeWithAttributes) {
|
||||
node->labels_.emplace_back(label);
|
||||
node->properties_[property] = storage.Create<Literal>(42);
|
||||
|
||||
auto create = std::make_shared<CreateOp>(node);
|
||||
auto create = std::make_shared<CreateNode>(node, nullptr);
|
||||
ExecuteCreate(create, *dba, symbol_table);
|
||||
dba->advance_command();
|
||||
|
||||
@ -294,7 +294,7 @@ TEST(Interpreter, CreateReturn) {
|
||||
node->labels_.emplace_back(label);
|
||||
node->properties_[property] = storage.Create<Literal>(42);
|
||||
|
||||
auto create = std::make_shared<CreateOp>(node);
|
||||
auto create = std::make_shared<CreateNode>(node, nullptr);
|
||||
auto named_expr_n =
|
||||
storage.Create<NamedExpression>("n", storage.Create<Identifier>("n"));
|
||||
symbol_table[*named_expr_n] = symbol_table.CreateSymbol("named_expr_n");
|
||||
@ -360,7 +360,7 @@ TEST(Interpreter, CreateExpand) {
|
||||
r->edge_types_.emplace_back(edge_type);
|
||||
r->properties_[property] = storage.Create<Literal>(3);
|
||||
|
||||
auto create_op = std::make_shared<CreateOp>(n);
|
||||
auto create_op = std::make_shared<CreateNode>(n, nullptr);
|
||||
auto create_expand =
|
||||
std::make_shared<CreateExpand>(m, r, create_op, n_sym, cycle);
|
||||
ExecuteCreate(create_expand, *dba, symbol_table);
|
||||
@ -395,7 +395,36 @@ TEST(Interpreter, CreateExpand) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Interpreter, MatchCreate) {
|
||||
TEST(Interpreter, MatchCreateNode) {
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
|
||||
// add three nodes we'll match and expand-create from
|
||||
dba->insert_vertex();
|
||||
dba->insert_vertex();
|
||||
dba->insert_vertex();
|
||||
dba->advance_command();
|
||||
|
||||
SymbolTable symbol_table;
|
||||
AstTreeStorage storage;
|
||||
|
||||
// first node
|
||||
auto n_scan_all = MakeScanAll(storage, symbol_table, "n");
|
||||
auto n_sym = symbol_table.CreateSymbol("n");
|
||||
symbol_table[*std::get<0>(n_scan_all)->identifier_] = n_sym;
|
||||
// second node
|
||||
auto m = storage.Create<NodeAtom>(storage.Create<Identifier>("m"));
|
||||
symbol_table[*m->identifier_] = symbol_table.CreateSymbol("m");
|
||||
// creation op
|
||||
auto create_node = std::make_shared<CreateNode>(m, std::get<1>(n_scan_all));
|
||||
|
||||
EXPECT_EQ(CountIterable(dba->vertices()), 3);
|
||||
ExecuteCreate(create_node, *dba, symbol_table);
|
||||
dba->advance_command();
|
||||
EXPECT_EQ(CountIterable(dba->vertices()), 6);
|
||||
}
|
||||
|
||||
TEST(Interpreter, MatchCreateExpand) {
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
|
||||
|
@ -20,7 +20,7 @@ class PlanChecker : public LogicalOperatorVisitor {
|
||||
|
||||
PlanChecker(std::list<size_t> types) : types_(types) {}
|
||||
|
||||
void Visit(CreateOp &op) override { AssertType(op); }
|
||||
void Visit(CreateNode &op) override { AssertType(op); }
|
||||
void Visit(CreateExpand &op) override { AssertType(op); }
|
||||
void Visit(ScanAll &op) override { AssertType(op); }
|
||||
void Visit(Expand &op) override { AssertType(op); }
|
||||
@ -97,7 +97,7 @@ TEST(TestLogicalPlanner, CreateNodeReturn) {
|
||||
query->Accept(symbol_generator);
|
||||
auto plan = MakeLogicalPlan(*query, symbol_table);
|
||||
std::list<size_t> expected_types;
|
||||
expected_types.emplace_back(typeid(CreateOp).hash_code());
|
||||
expected_types.emplace_back(typeid(CreateNode).hash_code());
|
||||
expected_types.emplace_back(typeid(Produce).hash_code());
|
||||
PlanChecker plan_checker(expected_types);
|
||||
plan->Accept(plan_checker);
|
||||
@ -120,7 +120,7 @@ TEST(TestLogicalPlanner, CreateExpand) {
|
||||
query->Accept(symbol_generator);
|
||||
auto plan = MakeLogicalPlan(*query, symbol_table);
|
||||
std::list<size_t> expected_types;
|
||||
expected_types.emplace_back(typeid(CreateOp).hash_code());
|
||||
expected_types.emplace_back(typeid(CreateNode).hash_code());
|
||||
expected_types.emplace_back(typeid(CreateExpand).hash_code());
|
||||
PlanChecker plan_checker(expected_types);
|
||||
plan->Accept(plan_checker);
|
||||
|
Loading…
Reference in New Issue
Block a user