// // Copyright 2017 Memgraph // Created by Florijan Stamenkovic on 14.03.17. // #include #include #include "gmock/gmock.h" #include "gtest/gtest.h" #include "communication/result_stream_faker.hpp" #include "dbms/dbms.hpp" #include "query/entry.hpp" using namespace query; /** * Helper function that collects all the results from the given * Produce into a ResultStreamFaker and returns that object. * * @param produce * @param symbol_table * @param db_accessor * @return */ auto CollectProduce(std::shared_ptr produce, SymbolTable &symbol_table, GraphDbAccessor &db_accessor) { ResultStreamFaker stream; Frame frame(symbol_table.max_position()); // top level node in the operator tree is a produce (return) // so stream out results // generate header std::vector header; for (auto named_expression : produce->named_expressions()) header.push_back(named_expression->name_); stream.Header(header); // collect the symbols from the return clause std::vector symbols; for (auto named_expression : produce->named_expressions()) symbols.emplace_back(symbol_table[*named_expression]); // stream out results auto cursor = produce->MakeCursor(db_accessor); while (cursor->Pull(frame, symbol_table)) { std::vector values; for (auto &symbol : symbols) values.emplace_back(frame[symbol]); stream.Result(values); } stream.Summary({{std::string("type"), TypedValue("r")}}); return stream; } 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)) { continue; } } template auto MakeProduce(std::shared_ptr input, TNamedExpressions... named_expressions) { return std::make_shared( input, std::vector{named_expressions...}); } /** * Creates and returns a tuple of stuff for a scan-all starting * from the node with the given name. * * Returns (node_atom, scan_all_logical_op, symbol). */ auto MakeScanAll(AstTreeStorage &ast_storage, SymbolTable &symbol_table, const std::string &identifier) { auto node = ast_storage.Create(ast_storage.Create(identifier)); auto logical_op = std::make_shared(node); auto symbol = symbol_table.CreateSymbol(identifier); symbol_table[*node->identifier_] = symbol; return std::make_tuple(node, logical_op, symbol); } auto MakeExpand(AstTreeStorage &ast_storage, SymbolTable &symbol_table, std::shared_ptr input, Symbol input_symbol, const std::string &edge_identifier, EdgeAtom::Direction direction, bool edge_cycle, const std::string &node_identifier, bool node_cycle) { auto edge = ast_storage.Create( ast_storage.Create(edge_identifier), direction); auto edge_sym = symbol_table.CreateSymbol(edge_identifier); symbol_table[*edge->identifier_] = edge_sym; auto node = ast_storage.Create( ast_storage.Create(node_identifier)); auto node_sym = symbol_table.CreateSymbol(node_identifier); symbol_table[*node->identifier_] = node_sym; auto op = std::make_shared(node, edge, input, input_symbol, node_cycle, edge_cycle); return std::make_tuple(edge, edge_sym, node, node_sym, op); } /* * Actual tests start here. */ TEST(Interpreter, MatchReturn) { Dbms dbms; auto dba = dbms.active(); // add a few nodes to the database dba->insert_vertex(); dba->insert_vertex(); AstTreeStorage storage; SymbolTable symbol_table; auto scan_all = MakeScanAll(storage, symbol_table, "n"); auto output = storage.Create("n", storage.Create("n")); auto produce = MakeProduce(std::get<1>(scan_all), output); symbol_table[*output->expression_] = std::get<2>(scan_all); symbol_table[*output] = symbol_table.CreateSymbol("named_expression_1"); ResultStreamFaker result = CollectProduce(produce, symbol_table, *dba); EXPECT_EQ(result.GetResults().size(), 2); } TEST(Interpreter, NodeFilterLabelsAndProperties) { Dbms dbms; auto dba = dbms.active(); // add a few nodes to the database GraphDb::Label label = dba->label("Label"); GraphDb::Property property = dba->property("Property"); auto v1 = dba->insert_vertex(); auto v2 = dba->insert_vertex(); auto v3 = dba->insert_vertex(); auto v4 = dba->insert_vertex(); auto v5 = dba->insert_vertex(); dba->insert_vertex(); // test all combination of (label | no_label) * (no_prop | wrong_prop | // right_prop) // only v1 will have the right labels v1.add_label(label); v2.add_label(label); v3.add_label(label); v1.PropsSet(property, 42); v2.PropsSet(property, 1); v4.PropsSet(property, 42); v5.PropsSet(property, 1); AstTreeStorage storage; SymbolTable symbol_table; // 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 // node filtering auto node_filter = std::make_shared( std::get<1>(n), std::get<2>(n), std::get<0>(n)); // make a named expression and a produce auto output = storage.Create("x", storage.Create("n")); symbol_table[*output->expression_] = std::get<2>(n); symbol_table[*output] = symbol_table.CreateSymbol("named_expression_1"); 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); } TEST(Interpreter, NodeFilterMultipleLabels) { Dbms dbms; auto dba = dbms.active(); // add a few nodes to the database GraphDb::Label label1 = dba->label("label1"); GraphDb::Label label2 = dba->label("label2"); GraphDb::Label label3 = dba->label("label3"); // the test will look for nodes that have label1 and label2 dba->insert_vertex(); // NOT accepted dba->insert_vertex().add_label(label1); // NOT accepted dba->insert_vertex().add_label(label2); // NOT accepted dba->insert_vertex().add_label(label3); // NOT accepted auto v1 = dba->insert_vertex(); // YES accepted v1.add_label(label1); v1.add_label(label2); auto v2 = dba->insert_vertex(); // NOT accepted v2.add_label(label1); v2.add_label(label3); auto v3 = dba->insert_vertex(); // YES accepted v3.add_label(label1); v3.add_label(label2); v3.add_label(label3); AstTreeStorage storage; SymbolTable symbol_table; // make a scan all 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)); // make a named expression and a produce auto output = storage.Create("n", storage.Create("n")); auto produce = MakeProduce(node_filter, output); // fill up the symbol table symbol_table[*output] = symbol_table.CreateSymbol("named_expression_1"); symbol_table[*output->expression_] = std::get<2>(n); ResultStreamFaker result = CollectProduce(produce, symbol_table, *dba); EXPECT_EQ(result.GetResults().size(), 2); } TEST(Interpreter, CreateNodeWithAttributes) { Dbms dbms; auto dba = dbms.active(); GraphDb::Label label = dba->label("Person"); GraphDb::Property property = dba->label("age"); AstTreeStorage storage; auto node = storage.Create(storage.Create("n")); node->labels_.emplace_back(label); // TODO make a property here with an int literal expression auto create = std::make_shared(node); ExecuteCreate(create, *dba); // count the number of vertices int vertex_count = 0; for (VertexAccessor vertex : dba->vertices()) { 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_count, 1); } TEST(Interpreter, Expand) { Dbms dbms; auto dba = dbms.active(); // make a V-graph (v3)<-[r2]-(v1)-[r1]->(v2) auto v1 = dba->insert_vertex(); v1.add_label((GraphDb::Label)1); auto v2 = dba->insert_vertex(); v2.add_label((GraphDb::Label)2); auto v3 = dba->insert_vertex(); v3.add_label((GraphDb::Label)3); auto edge_type = dba->edge_type("Edge"); dba->insert_edge(v1, v2, edge_type); dba->insert_edge(v1, v3, edge_type); AstTreeStorage storage; SymbolTable symbol_table; 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); // make a named expression and a produce auto output = storage.Create("m", storage.Create("m")); symbol_table[*output->expression_] = std::get<3>(r_m); symbol_table[*output] = symbol_table.CreateSymbol("named_expression_1"); auto produce = MakeProduce(std::get<4>(r_m), output); ResultStreamFaker result = CollectProduce(produce, symbol_table, *dba); EXPECT_EQ(result.GetResults().size(), expected_result_count); }; test_expand(EdgeAtom::Direction::RIGHT, 2); test_expand(EdgeAtom::Direction::LEFT, 2); test_expand(EdgeAtom::Direction::BOTH, 4); } TEST(Interpreter, ExpandNodeCycle) { Dbms dbms; auto dba = dbms.active(); // make a graph (v1)->(v2) that // has a recursive edge (v1)->(v1) auto v1 = dba->insert_vertex(); auto v2 = dba->insert_vertex(); auto edge_type = dba->edge_type("Edge"); dba->insert_edge(v1, v1, edge_type); dba->insert_edge(v1, v2, edge_type); AstTreeStorage storage; SymbolTable symbol_table; auto test_cycle = [&](bool with_cycle, int expected_result_count) { auto n = MakeScanAll(storage, symbol_table, "n"); auto r_n = MakeExpand(storage, symbol_table, std::get<1>(n), std::get<2>(n), "r", EdgeAtom::Direction::RIGHT, false, "n", with_cycle); if (with_cycle) symbol_table[*std::get<2>(r_n)->identifier_] = symbol_table[*std::get<0>(n)->identifier_]; // make a named expression and a produce auto output = storage.Create("n", storage.Create("n")); symbol_table[*output->expression_] = std::get<2>(n); symbol_table[*output] = symbol_table.CreateSymbol("named_expression_1"); auto produce = MakeProduce(std::get<4>(r_n), output); ResultStreamFaker result = CollectProduce(produce, symbol_table, *dba); EXPECT_EQ(result.GetResults().size(), expected_result_count); }; test_cycle(true, 1); test_cycle(false, 2); } TEST(Interpreter, ExpandEdgeCycle) { Dbms dbms; auto dba = dbms.active(); // make a V-graph (v3)<-[r2]-(v1)-[r1]->(v2) auto v1 = dba->insert_vertex(); v1.add_label((GraphDb::Label)1); auto v2 = dba->insert_vertex(); v2.add_label((GraphDb::Label)2); auto v3 = dba->insert_vertex(); v3.add_label((GraphDb::Label)3); auto edge_type = dba->edge_type("Edge"); dba->insert_edge(v1, v2, edge_type); dba->insert_edge(v1, v3, edge_type); AstTreeStorage storage; SymbolTable symbol_table; auto test_cycle = [&](bool with_cycle, int expected_result_count) { auto i = MakeScanAll(storage, symbol_table, "i"); auto r_j = MakeExpand(storage, symbol_table, std::get<1>(i), std::get<2>(i), "r", EdgeAtom::Direction::BOTH, false, "j", false); auto r_k = MakeExpand(storage, symbol_table, std::get<4>(r_j), std::get<3>(r_j), "r", EdgeAtom::Direction::BOTH, with_cycle, "k", false); if (with_cycle) symbol_table[*std::get<0>(r_k)->identifier_] = symbol_table[*std::get<0>(r_j)->identifier_]; // make a named expression and a produce auto output = storage.Create("r", storage.Create("r")); symbol_table[*output->expression_] = std::get<1>(r_j); symbol_table[*output] = symbol_table.CreateSymbol("named_expression_1"); auto produce = MakeProduce(std::get<4>(r_k), output); ResultStreamFaker result = CollectProduce(produce, symbol_table, *dba); EXPECT_EQ(result.GetResults().size(), expected_result_count); }; test_cycle(true, 4); test_cycle(false, 6); } TEST(Interpreter, EdgeFilter) { Dbms dbms; auto dba = dbms.active(); // make an N-star expanding from (v1) // where only one edge will qualify // and there are all combinations of // (edge_type yes|no) * (property yes|absent|no) std::vector edge_types; for (int j = 0; j < 2; ++j) edge_types.push_back(dba->edge_type("et" + std::to_string(j))); std::vector vertices; for (int i = 0; i < 7; ++i) vertices.push_back(dba->insert_vertex()); GraphDb::Property prop = dba->property("prop"); std::vector edges; for (int i = 0; i < 6; ++i) { edges.push_back( dba->insert_edge(vertices[0], vertices[i + 1], edge_types[i % 2])); switch (i % 3) { case 0: edges.back().PropsSet(prop, 42); break; case 1: edges.back().PropsSet(prop, 100); break; default: break; } } AstTreeStorage storage; SymbolTable symbol_table; // define an operator tree for query // MATCH (n)-[r]->(m) RETURN m auto n = MakeScanAll(storage, symbol_table, "n"); auto r_m = MakeExpand(storage, symbol_table, std::get<1>(n), std::get<2>(n), "r", EdgeAtom::Direction::RIGHT, false, "m", false); std::get<0>(r_m)->edge_types_.push_back(edge_types[0]); // TODO when int literal expression becomes available // add a property filter auto edge_filter = std::make_shared( std::get<4>(r_m), std::get<1>(r_m), std::get<0>(r_m)); // make a named expression and a produce auto output = storage.Create("m", storage.Create("m")); symbol_table[*output->expression_] = std::get<3>(r_m); symbol_table[*output] = symbol_table.CreateSymbol("named_expression_1"); auto produce = MakeProduce(edge_filter, output); ResultStreamFaker result = CollectProduce(produce, symbol_table, *dba); // TODO change the expected value to 1 once property filtering is available EXPECT_EQ(result.GetResults().size(), 3); }