From 981f93d575feeb67b4114d9d27cfbc9078451ea6 Mon Sep 17 00:00:00 2001 From: florijan Date: Wed, 22 Mar 2017 13:40:50 +0100 Subject: [PATCH] Query - ExpandCreate, prop tests, MATCH_CREATE Summary: Query - ExpandCreate op added (with tests). LogicalOp tests have property enabled. MATCH-CREATE test started but disabled due to MVCC. Reviewers: buda, mislav.bradac, teon.banek Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D150 --- src/mvcc/record.hpp | 10 +- src/query/frontend/interpret/interpret.hpp | 4 + src/query/frontend/logical/operator.hpp | 116 +++++++++++ tests/unit/graph_db_accessor.cpp | 109 +++++------ tests/unit/interpreter.cpp | 214 +++++++++++++++++++-- tests/unit/record_edge_vertex_accessor.cpp | 1 + 6 files changed, 381 insertions(+), 73 deletions(-) diff --git a/src/mvcc/record.hpp b/src/mvcc/record.hpp index 8ce33fc98..aecdaf494 100644 --- a/src/mvcc/record.hpp +++ b/src/mvcc/record.hpp @@ -59,11 +59,9 @@ class Record : public Version { // Mike Olson says 17 march 1993: the tests in this routine are correct; // if you think they're not, you're wrong, and you should think about it // again. i know, it happened to me. - // This implementation is different than the original one by the <= between - // cmd.cre() and t.cid, instead of just <. This is the behaviour we want. return ((tx.cre() == t.id && // inserted by the current transaction - cmd.cre() <= t.cid && // before or during this command, and + cmd.cre() < t.cid && // before this command, and (tx.exp() == Id(0) || // the row has not been deleted, or (tx.exp() == t.id && // it was deleted by the current // transaction @@ -114,9 +112,13 @@ class Record : public Version { // TODO: Test this // True if this record is visible for write. + // Note that this logic is different from the one above + // in the sense that a record is visible if created before + // OR DURING this command. this is done to support cypher's + // queries which can match, update and return in the same query bool is_visible_write(const tx::Transaction &t) { return (tx.cre() == t.id && // inserted by the current transaction - cmd.cre() <= t.cid && // before this command, and + cmd.cre() <= t.cid && // before OR DURING this command, and (tx.exp() == Id(0) || // the row has not been deleted, or (tx.exp() == t.id && // it was deleted by the current // transaction diff --git a/src/query/frontend/interpret/interpret.hpp b/src/query/frontend/interpret/interpret.hpp index 9ad4c1494..9d6fb6bba 100644 --- a/src/query/frontend/interpret/interpret.hpp +++ b/src/query/frontend/interpret/interpret.hpp @@ -77,6 +77,10 @@ class ExpressionEvaluator : public TreeVisitorBase { } } + void Visit(Literal &literal) override { + result_stack_.push_back(literal.value_); + } + private: Frame &frame_; SymbolTable &symbol_table_; diff --git a/src/query/frontend/logical/operator.hpp b/src/query/frontend/logical/operator.hpp index 4c9e25ab1..74ca09aaf 100644 --- a/src/query/frontend/logical/operator.hpp +++ b/src/query/frontend/logical/operator.hpp @@ -40,6 +40,9 @@ class LogicalOperator : public ::utils::Visitable { }; 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 public: CreateOp(NodeAtom* node_atom) : node_atom_(node_atom) {} DEFVISITABLE(LogicalOperatorVisitor); @@ -61,6 +64,8 @@ class CreateOp : public LogicalOperator { new_node.PropsSet(kv.first, evaluator.PopBack()); } + frame[symbol_table[*self_.node_atom_->identifier_]] = new_node; + did_create_ = true; return true; } else @@ -82,6 +87,117 @@ class CreateOp : public LogicalOperator { NodeAtom* node_atom_ = nullptr; }; +class CreateExpand : public LogicalOperator { + + public: + CreateExpand(NodeAtom* node_atom, EdgeAtom* edge_atom, + const std::shared_ptr& input, + const Symbol& input_symbol, bool node_existing) + : node_atom_(node_atom), + edge_atom_(edge_atom), + input_(input), + input_symbol_(input_symbol), + node_existing_(node_existing) {} + + private: + class CreateExpandCursor : public Cursor { + public: + CreateExpandCursor(CreateExpand& self, GraphDbAccessor& db) + : self_(self), db_(db), input_cursor_(self.input_->MakeCursor(db)) {} + + bool Pull(Frame& frame, SymbolTable& symbol_table) override { + if (!input_cursor_->Pull(frame, symbol_table)) return false; + + // get the origin vertex + TypedValue vertex_value = frame[self_.input_symbol_]; + auto v1 = vertex_value.Value(); + + ExpressionEvaluator evaluator(frame, symbol_table); + + // get the destination vertex (possibly an existing node) + VertexAccessor v2 = OtherVertex(frame, symbol_table, evaluator); + + // create an edge between the two nodes + switch (self_.edge_atom_->direction_) { + case EdgeAtom::Direction::LEFT: + CreateEdge(v2, v1, evaluator); + break; + case EdgeAtom::Direction::RIGHT: + CreateEdge(v1, v2, evaluator); + break; + case EdgeAtom::Direction::BOTH: + permanent_fail("Undefined direction not allowed in create"); + } + + return true; + } + + private: + CreateExpand& self_; + GraphDbAccessor& db_; + std::unique_ptr input_cursor_; + + /** + * Helper function for getting an existing node or creating a new one. + * @return The newly created or already existing node. + */ + VertexAccessor OtherVertex(Frame &frame, SymbolTable &symbol_table, + ExpressionEvaluator &evaluator) { + if (self_.node_existing_) { + TypedValue& dest_node_value = + frame[symbol_table[*self_.node_atom_->identifier_]]; + return dest_node_value.Value(); + } else { + // the node does not exist, it needs to be created + auto node = db_.insert_vertex(); + for (auto label : self_.node_atom_->labels_) node.add_label(label); + for (auto kv : self_.node_atom_->properties_) { + kv.second->Accept(evaluator); + node.PropsSet(kv.first, evaluator.PopBack()); + } + frame[symbol_table[*self_.node_atom_->identifier_]] = node; + return node; + } + } + + /** + * Helper function for creating an edge. + * + * @param from Origin vertex of the edge. + * @param to Destination vertex of the edge. + * @param evaluator Expression evaluator for property value eval. + */ + void CreateEdge(VertexAccessor& from, VertexAccessor& to, + ExpressionEvaluator& evaluator) { + EdgeAccessor edge = + db_.insert_edge(from, to, self_.edge_atom_->edge_types_[0]); + for (auto kv : self_.edge_atom_->properties_) { + kv.second->Accept(evaluator); + edge.PropsSet(kv.first, evaluator.PopBack()); + } + }; + }; + + public: + std::unique_ptr MakeCursor(GraphDbAccessor& db) override { + return std::make_unique(*this, db); + } + + private: + // info on what's getting expanded + NodeAtom* node_atom_; + EdgeAtom* edge_atom_; + + // the input op and the symbol under which the op's result + // can be found in the frame + std::shared_ptr input_; + const Symbol input_symbol_; + + // if the given node atom refers to an existing node + // (either matched or created) + bool node_existing_; +}; + class ScanAll : public LogicalOperator { public: ScanAll(NodeAtom *node_atom) : node_atom_(node_atom) {} diff --git a/tests/unit/graph_db_accessor.cpp b/tests/unit/graph_db_accessor.cpp index 9ef403a9f..7a1c9ef65 100644 --- a/tests/unit/graph_db_accessor.cpp +++ b/tests/unit/graph_db_accessor.cpp @@ -34,9 +34,11 @@ TEST(GraphDbAccessorTest, InsertVertex) { EXPECT_EQ(CountVertices(*accessor), 0); accessor->insert_vertex(); + accessor->advance_command(); EXPECT_EQ(CountVertices(*accessor), 1); accessor->insert_vertex(); + accessor->advance_command(); EXPECT_EQ(CountVertices(*accessor), 2); } @@ -47,6 +49,7 @@ TEST(GraphDbAccessorTest, RemoveVertexSameTransaction) { EXPECT_EQ(CountVertices(*accessor), 0); auto va1 = accessor->insert_vertex(); + accessor->advance_command(); EXPECT_EQ(CountVertices(*accessor), 1); EXPECT_TRUE(accessor->remove_vertex(va1)); @@ -81,6 +84,7 @@ TEST(GraphDbAccessorTest, InsertEdge) { auto va1 = dba->insert_vertex(); auto va2 = dba->insert_vertex(); + dba->advance_command(); EXPECT_EQ(va1.in_degree(), 0); EXPECT_EQ(va1.out_degree(), 0); EXPECT_EQ(va2.in_degree(), 0); @@ -88,6 +92,7 @@ TEST(GraphDbAccessorTest, InsertEdge) { // setup (v1) - [:likes] -> (v2) dba->insert_edge(va1, va2, dba->edge_type("likes")); + dba->advance_command(); EXPECT_EQ(CountEdges(*dba), 1); EXPECT_EQ(va1.out().begin()->to(), va2); EXPECT_EQ(va2.in().begin()->from(), va1); @@ -99,6 +104,7 @@ TEST(GraphDbAccessorTest, InsertEdge) { // setup (v1) - [:likes] -> (v2) <- [:hates] - (v3) auto va3 = dba->insert_vertex(); dba->insert_edge(va3, va2, dba->edge_type("hates")); + dba->advance_command(); EXPECT_EQ(CountEdges(*dba), 2); EXPECT_EQ(va3.out().begin()->to(), va2); EXPECT_EQ(va1.in_degree(), 0); @@ -111,35 +117,33 @@ TEST(GraphDbAccessorTest, InsertEdge) { TEST(GraphDbAccessorTest, RemoveEdge) { Dbms dbms; - auto dba1 = dbms.active(); + auto dba = dbms.active(); // setup (v1) - [:likes] -> (v2) <- [:hates] - (v3) - auto va1 = dba1->insert_vertex(); - auto va2 = dba1->insert_vertex(); - auto va3 = dba1->insert_vertex(); - dba1->insert_edge(va1, va2, dba1->edge_type("likes")); - dba1->insert_edge(va3, va2, dba1->edge_type("hates")); - EXPECT_EQ(CountEdges(*dba1), 2); + auto va1 = dba->insert_vertex(); + auto va2 = dba->insert_vertex(); + auto va3 = dba->insert_vertex(); + dba->insert_edge(va1, va2, dba->edge_type("likes")); + dba->insert_edge(va3, va2, dba->edge_type("hates")); + dba->advance_command(); + EXPECT_EQ(CountEdges(*dba), 2); // remove all [:hates] edges - dba1->commit(); - auto dba2 = dbms.active(); - EXPECT_EQ(CountEdges(*dba2), 2); - for (auto edge : dba2->edges()) - if (edge.edge_type() == dba2->edge_type("hates")) dba2->remove_edge(edge); + EXPECT_EQ(CountEdges(*dba), 2); + for (auto edge : dba->edges()) + if (edge.edge_type() == dba->edge_type("hates")) dba->remove_edge(edge); // current state: (v1) - [:likes] -> (v2), (v3) - dba2->commit(); - auto dba3 = dbms.active(); - EXPECT_EQ(CountEdges(*dba3), 1); - EXPECT_EQ(CountVertices(*dba3), 3); - for (auto edge : dba3->edges()) { - EXPECT_EQ(edge.edge_type(), dba3->edge_type("likes")); + dba->advance_command(); + EXPECT_EQ(CountEdges(*dba), 1); + EXPECT_EQ(CountVertices(*dba), 3); + for (auto edge : dba->edges()) { + EXPECT_EQ(edge.edge_type(), dba->edge_type("likes")); auto v1 = edge.from(); auto v2 = edge.to(); // ensure correct connectivity for all the vertices - for (auto vertex : dba3->vertices()) { + for (auto vertex : dba->vertices()) { if (vertex == v1) { EXPECT_EQ(vertex.in_degree(), 0); EXPECT_EQ(vertex.out_degree(), 1); @@ -156,60 +160,57 @@ TEST(GraphDbAccessorTest, RemoveEdge) { TEST(GraphDbAccessorTest, DetachRemoveVertex) { Dbms dbms; - auto dba1 = dbms.active(); + auto dba = dbms.active(); // setup (v1) - [:likes] -> (v2) <- [:hates] - (v3) - auto va1 = dba1->insert_vertex(); - auto va2 = dba1->insert_vertex(); - auto va3 = dba1->insert_vertex(); - dba1->insert_edge(va1, va2, dba1->edge_type("likes")); - dba1->insert_edge(va1, va3, dba1->edge_type("likes")); + auto va1 = dba->insert_vertex(); + auto va2 = dba->insert_vertex(); + auto va3 = dba->insert_vertex(); + dba->insert_edge(va1, va2, dba->edge_type("likes")); + dba->insert_edge(va1, va3, dba->edge_type("likes")); + dba->advance_command(); // ensure that plain remove does NOT work - EXPECT_EQ(CountVertices(*dba1), 3); - EXPECT_EQ(CountEdges(*dba1), 2); - EXPECT_FALSE(dba1->remove_vertex(va1)); - EXPECT_FALSE(dba1->remove_vertex(va2)); - EXPECT_FALSE(dba1->remove_vertex(va3)); - EXPECT_EQ(CountVertices(*dba1), 3); - EXPECT_EQ(CountEdges(*dba1), 2); + EXPECT_EQ(CountVertices(*dba), 3); + EXPECT_EQ(CountEdges(*dba), 2); + EXPECT_FALSE(dba->remove_vertex(va1)); + EXPECT_FALSE(dba->remove_vertex(va2)); + EXPECT_FALSE(dba->remove_vertex(va3)); + EXPECT_EQ(CountVertices(*dba), 3); + EXPECT_EQ(CountEdges(*dba), 2); // make a new transaction because at the moment deletions // in the same transaction are not visible // DETACH REMOVE V3 // new situation: (v1) - [:likes] -> (v2) - dba1->detach_remove_vertex(va3); - dba1->commit(); - auto dba2 = dbms.active(); + dba->detach_remove_vertex(va3); + dba->advance_command(); - EXPECT_EQ(CountVertices(*dba2), 2); - EXPECT_EQ(CountEdges(*dba2), 1); - for (auto va : dba2->vertices()) EXPECT_FALSE(dba2->remove_vertex(va)); + EXPECT_EQ(CountVertices(*dba), 2); + EXPECT_EQ(CountEdges(*dba), 1); + for (auto va : dba->vertices()) EXPECT_FALSE(dba->remove_vertex(va)); - dba2->commit(); - auto dba3 = dbms.active(); - EXPECT_EQ(CountVertices(*dba3), 2); - EXPECT_EQ(CountEdges(*dba3), 1); + dba->advance_command(); + EXPECT_EQ(CountVertices(*dba), 2); + EXPECT_EQ(CountEdges(*dba), 1); - for (auto va : dba3->vertices()) { - EXPECT_FALSE(dba3->remove_vertex(va)); - dba3->detach_remove_vertex(va); + for (auto va : dba->vertices()) { + EXPECT_FALSE(dba->remove_vertex(va)); + dba->detach_remove_vertex(va); break; } - dba3->commit(); - auto dba4 = dbms.active(); - EXPECT_EQ(CountVertices(*dba4), 1); - EXPECT_EQ(CountEdges(*dba4), 0); + dba->advance_command(); + EXPECT_EQ(CountVertices(*dba), 1); + EXPECT_EQ(CountEdges(*dba), 0); // remove the last vertex, it has no connections // so that should work - for (auto va : dba4->vertices()) EXPECT_TRUE(dba4->remove_vertex(va)); + for (auto va : dba->vertices()) EXPECT_TRUE(dba->remove_vertex(va)); - dba4->commit(); - auto dba5 = dbms.active(); - EXPECT_EQ(CountVertices(*dba5), 0); - EXPECT_EQ(CountEdges(*dba5), 0); + dba->advance_command(); + EXPECT_EQ(CountVertices(*dba), 0); + EXPECT_EQ(CountEdges(*dba), 0); } TEST(GraphDbAccessorTest, Labels) { diff --git a/tests/unit/interpreter.cpp b/tests/unit/interpreter.cpp index d1589a0af..859ed2e14 100644 --- a/tests/unit/interpreter.cpp +++ b/tests/unit/interpreter.cpp @@ -3,6 +3,7 @@ // Created by Florijan Stamenkovic on 14.03.17. // +#include #include #include @@ -56,8 +57,8 @@ auto CollectProduce(std::shared_ptr produce, SymbolTable &symbol_table, return stream; } -void ExecuteCreate(std::shared_ptr create, GraphDbAccessor &db) { - SymbolTable symbol_table; +void ExecuteCreate(std::shared_ptr create, GraphDbAccessor &db, + SymbolTable symbol_table) { Frame frame(symbol_table.max_position()); auto cursor = create->MakeCursor(db); while (cursor->Pull(frame, symbol_table)) { @@ -109,6 +110,11 @@ auto MakeExpand(AstTreeStorage &ast_storage, SymbolTable &symbol_table, return std::make_tuple(edge, edge_sym, node, node_sym, op); } +template +auto CountIterable(TIterable iterable) { + return std::distance(iterable.begin(), iterable.end()); +} + /* * Actual tests start here. */ @@ -120,6 +126,7 @@ TEST(Interpreter, MatchReturn) { // add a few nodes to the database dba->insert_vertex(); dba->insert_vertex(); + dba->advance_command(); AstTreeStorage storage; SymbolTable symbol_table; @@ -158,6 +165,7 @@ TEST(Interpreter, NodeFilterLabelsAndProperties) { v2.PropsSet(property, 1); v4.PropsSet(property, 42); v5.PropsSet(property, 1); + dba->advance_command(); AstTreeStorage storage; SymbolTable symbol_table; @@ -165,7 +173,7 @@ TEST(Interpreter, NodeFilterLabelsAndProperties) { // make a scan all auto n = MakeScanAll(storage, symbol_table, "n"); std::get<0>(n)->labels_.emplace_back(label); - // TODO set a property once int-literal expressions are available + std::get<0>(n)->properties_[property] = storage.Create(42); // node filtering auto node_filter = std::make_shared( @@ -179,8 +187,7 @@ TEST(Interpreter, NodeFilterLabelsAndProperties) { auto produce = MakeProduce(node_filter, output); ResultStreamFaker result = CollectProduce(produce, symbol_table, *dba); - // TODO change expected value to 1 once literal expressions are avaialable - EXPECT_EQ(result.GetResults().size(), 3); + EXPECT_EQ(result.GetResults().size(), 1); } TEST(Interpreter, NodeFilterMultipleLabels) { @@ -206,6 +213,7 @@ TEST(Interpreter, NodeFilterMultipleLabels) { v3.add_label(label1); v3.add_label(label2); v3.add_label(label3); + dba->advance_command(); AstTreeStorage storage; SymbolTable symbol_table; @@ -214,10 +222,8 @@ TEST(Interpreter, NodeFilterMultipleLabels) { auto n = MakeScanAll(storage, symbol_table, "n"); std::get<0>(n)->labels_.emplace_back(label1); std::get<0>(n)->labels_.emplace_back(label2); - // TODO set a property once int-literal expressions are available // node filtering - // TODO implement the test once int-literal expressions are available auto node_filter = std::make_shared( std::get<1>(n), std::get<2>(n), std::get<0>(n)); @@ -242,13 +248,16 @@ TEST(Interpreter, CreateNodeWithAttributes) { GraphDb::Property property = dba->label("age"); AstTreeStorage storage; + SymbolTable symbol_table; auto node = storage.Create(storage.Create("n")); + symbol_table[*node->identifier_] = symbol_table.CreateSymbol("n"); node->labels_.emplace_back(label); - // TODO make a property here with an int literal expression + node->properties_[property] = storage.Create(42); auto create = std::make_shared(node); - ExecuteCreate(create, *dba); + ExecuteCreate(create, *dba, symbol_table); + dba->advance_command(); // count the number of vertices int vertex_count = 0; @@ -256,15 +265,185 @@ TEST(Interpreter, CreateNodeWithAttributes) { vertex_count++; EXPECT_EQ(vertex.labels().size(), 1); EXPECT_EQ(*vertex.labels().begin(), label); - // TODO re-enable these tests once int literal becomes available - // EXPECT_EQ(vertex.Properties().size(), 1); - // auto prop_eq = vertex.PropsAt(property) == TypedValue(42); - // ASSERT_EQ(prop_eq.type(), TypedValue::Type::Bool); - // EXPECT_TRUE(prop_eq.Value()); + EXPECT_EQ(vertex.Properties().size(), 1); + auto prop_eq = vertex.PropsAt(property) == TypedValue(42); + ASSERT_EQ(prop_eq.type(), TypedValue::Type::Bool); + EXPECT_TRUE(prop_eq.Value()); } EXPECT_EQ(vertex_count, 1); } +TEST(Interpreter, CreateReturn) { + // test CREATE (n:Person {age: 42}) RETURN n, n.age + Dbms dbms; + auto dba = dbms.active(); + + GraphDb::Label label = dba->label("Person"); + GraphDb::Property property = dba->label("age"); + + AstTreeStorage storage; + SymbolTable symbol_table; + + auto node = storage.Create(storage.Create("n")); + auto sym_n = symbol_table.CreateSymbol("n"); + symbol_table[*node->identifier_] = sym_n; + node->labels_.emplace_back(label); + node->properties_[property] = storage.Create(42); + + auto create = std::make_shared(node); + auto named_expr_n = + storage.Create("n", storage.Create("n")); + symbol_table[*named_expr_n] = symbol_table.CreateSymbol("named_expr_n"); + symbol_table[*named_expr_n->expression_] = sym_n; + auto prop_lookup = + storage.Create(storage.Create("n"), property); + symbol_table[*prop_lookup->expression_] = sym_n; + auto named_expr_n_p = storage.Create("n", prop_lookup); + symbol_table[*named_expr_n_p] = symbol_table.CreateSymbol("named_expr_n_p"); + symbol_table[*named_expr_n->expression_] = sym_n; + + auto produce = MakeProduce(create, named_expr_n, named_expr_n_p); + auto result = CollectProduce(produce, symbol_table, *dba); + EXPECT_EQ(1, result.GetResults().size()); + EXPECT_EQ(2, result.GetResults()[0].size()); + EXPECT_EQ(TypedValue::Type::Vertex, result.GetResults()[0][0].type()); + EXPECT_EQ(1, + result.GetResults()[0][0].Value().labels().size()); + EXPECT_EQ(label, + result.GetResults()[0][0].Value().labels()[0]); + EXPECT_EQ(TypedValue::Type::Int, result.GetResults()[0][1].type()); + EXPECT_EQ(42, result.GetResults()[0][1].Value()); + + dba->advance_command(); + EXPECT_EQ(1, CountIterable(dba->vertices())); +} + +TEST(Interpreter, CreateExpand) { + Dbms dbms; + auto dba = dbms.active(); + + GraphDb::Label label_node_1 = dba->label("Node1"); + GraphDb::Label label_node_2 = dba->label("Node2"); + GraphDb::Property property = dba->label("prop"); + GraphDb::EdgeType edge_type = dba->label("edge_type"); + + SymbolTable symbol_table; + AstTreeStorage storage; + + auto test_create_path = [&](bool cycle, int expected_nodes_created, + int expected_edges_created) { + int before_v = CountIterable(dba->vertices()); + int before_e = CountIterable(dba->edges()); + + // data for the first node + auto n = storage.Create(storage.Create("n")); + n->labels_.emplace_back(label_node_1); + n->properties_[property] = storage.Create(1); + auto n_sym = symbol_table.CreateSymbol("n"); + symbol_table[*n->identifier_] = n_sym; + + // data for the second node + auto m = storage.Create(storage.Create("m")); + m->labels_.emplace_back(label_node_2); + m->properties_[property] = storage.Create(2); + if (cycle) + symbol_table[*m->identifier_] = n_sym; + else + symbol_table[*m->identifier_] = symbol_table.CreateSymbol("m"); + + auto r = storage.Create(storage.Create("r"), + EdgeAtom::Direction::RIGHT); + r->edge_types_.emplace_back(edge_type); + r->properties_[property] = storage.Create(3); + + auto create_op = std::make_shared(n); + auto create_expand = + std::make_shared(m, r, create_op, n_sym, cycle); + ExecuteCreate(create_expand, *dba, symbol_table); + dba->advance_command(); + + EXPECT_EQ(CountIterable(dba->vertices()) - before_v, + expected_nodes_created); + EXPECT_EQ(CountIterable(dba->edges()) - before_e, expected_edges_created); + }; + + test_create_path(false, 2, 1); + test_create_path(true, 1, 1); + + for (VertexAccessor vertex : dba->vertices()) { + EXPECT_EQ(vertex.labels().size(), 1); + GraphDb::Label label = vertex.labels()[0]; + if (label == label_node_1) { + // node created by first op + EXPECT_EQ(vertex.PropsAt(property).Value(), 1); + } else if (label == label_node_2) { + // node create by expansion + EXPECT_EQ(vertex.PropsAt(property).Value(), 2); + } else { + // should not happen + FAIL(); + } + + for (EdgeAccessor edge : dba->edges()) { + EXPECT_EQ(edge.edge_type(), edge_type); + EXPECT_EQ(edge.PropsAt(property).Value(), 3); + } + } +} + +TEST(Interpreter, MatchCreate) { + 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(); + + // GraphDb::Label label_node_1 = dba->label("Node1"); + // GraphDb::Label label_node_2 = dba->label("Node2"); + // GraphDb::Property property = dba->label("prop"); + GraphDb::EdgeType edge_type = dba->label("edge_type"); + + SymbolTable symbol_table; + AstTreeStorage storage; + + auto test_create_path = [&](bool cycle, int expected_nodes_created, + int expected_edges_created) { + int before_v = CountIterable(dba->vertices()); + int before_e = CountIterable(dba->edges()); + + // data for the 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; + + // data for the second node + auto m = storage.Create(storage.Create("m")); + if (cycle) + symbol_table[*m->identifier_] = n_sym; + else + symbol_table[*m->identifier_] = symbol_table.CreateSymbol("m"); + + auto r = storage.Create(storage.Create("r"), + EdgeAtom::Direction::RIGHT); + r->edge_types_.emplace_back(edge_type); + + auto create_expand = std::make_shared( + m, r, std::get<1>(n_scan_all), n_sym, cycle); + ExecuteCreate(create_expand, *dba, symbol_table); + dba->advance_command(); + + EXPECT_EQ(CountIterable(dba->vertices()) - before_v, + expected_nodes_created); + EXPECT_EQ(CountIterable(dba->edges()) - before_e, expected_edges_created); + }; + + test_create_path(false, 3, 3); + test_create_path(true, 0, 6); +} + TEST(Interpreter, Expand) { Dbms dbms; auto dba = dbms.active(); @@ -279,11 +458,13 @@ TEST(Interpreter, Expand) { auto edge_type = dba->edge_type("Edge"); dba->insert_edge(v1, v2, edge_type); dba->insert_edge(v1, v3, edge_type); + dba->advance_command(); AstTreeStorage storage; SymbolTable symbol_table; - auto test_expand = [&](EdgeAtom::Direction direction, int expected_result_count) { + auto test_expand = [&](EdgeAtom::Direction direction, + int expected_result_count) { auto n = MakeScanAll(storage, symbol_table, "n"); auto r_m = MakeExpand(storage, symbol_table, std::get<1>(n), std::get<2>(n), "r", direction, false, "m", false); @@ -315,6 +496,7 @@ TEST(Interpreter, ExpandNodeCycle) { auto edge_type = dba->edge_type("Edge"); dba->insert_edge(v1, v1, edge_type); dba->insert_edge(v1, v2, edge_type); + dba->advance_command(); AstTreeStorage storage; SymbolTable symbol_table; @@ -357,6 +539,7 @@ TEST(Interpreter, ExpandEdgeCycle) { auto edge_type = dba->edge_type("Edge"); dba->insert_edge(v1, v2, edge_type); dba->insert_edge(v1, v3, edge_type); + dba->advance_command(); AstTreeStorage storage; SymbolTable symbol_table; @@ -417,6 +600,7 @@ TEST(Interpreter, EdgeFilter) { break; } } + dba->advance_command(); AstTreeStorage storage; SymbolTable symbol_table; diff --git a/tests/unit/record_edge_vertex_accessor.cpp b/tests/unit/record_edge_vertex_accessor.cpp index 5f2f46c7b..ce77d117d 100644 --- a/tests/unit/record_edge_vertex_accessor.cpp +++ b/tests/unit/record_edge_vertex_accessor.cpp @@ -141,6 +141,7 @@ TEST(RecordAccessor, VertexEdgeConnections) { auto v1 = dba->insert_vertex(); auto v2 = dba->insert_vertex(); auto edge = dba->insert_edge(v1, v2, dba->edge_type("likes")); + dba->advance_command(); EXPECT_EQ(edge.from(), v1); EXPECT_NE(edge.from(), v2);