#include #include #include #include #include #include "communication/result_stream_faker.hpp" #include "database/graph_db.hpp" #include "database/graph_db_accessor.hpp" #include "database/single_node/dump.hpp" #include "query/interpreter.hpp" #include "query/typed_value.hpp" #include "storage/common/types/property_value.hpp" using database::CypherDumpGenerator; using database::GraphDbAccessor; const char *kPropertyId = "property_id"; const char *kCreateInternalIndex = "CREATE INDEX ON :__mg_vertex__(__mg_id__);"; const char *kDropInternalIndex = "DROP INDEX ON :__mg_vertex__(__mg_id__);"; const char *kRemoveInternalLabelProperty = "MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__;"; // A helper struct that contains info about database that is used to compare // two databases (to check if their states are the same). It is assumed that // each vertex and each edge have unique integer property under key // `kPropertyId`. struct DatabaseState { struct Vertex { int64_t id; std::set labels; std::map props; }; struct Edge { int64_t from, to; std::string edge_type; std::map props; }; struct IndexKey { std::string label; std::string property; }; struct UniqueConstraint { std::string label; std::set props; }; std::set vertices; std::set edges; std::set indices; std::set constraints; }; bool operator<(const DatabaseState::Vertex &first, const DatabaseState::Vertex &second) { if (first.id != second.id) return first.id < second.id; if (first.labels != second.labels) return first.labels < second.labels; return first.props < second.props; } bool operator<(const DatabaseState::Edge &first, const DatabaseState::Edge &second) { if (first.from != second.from) return first.from < second.from; if (first.to != second.to) return first.to < second.to; if (first.edge_type != second.edge_type) return first.edge_type < second.edge_type; return first.props < second.props; } bool operator<(const DatabaseState::IndexKey &first, const DatabaseState::IndexKey &second) { if (first.label != second.label) return first.label < second.label; return first.property < second.property; } bool operator<(const DatabaseState::UniqueConstraint &first, const DatabaseState::UniqueConstraint &second) { if (first.label != second.label) return first.label < second.label; return first.props < second.props; } bool operator==(const DatabaseState::Vertex &first, const DatabaseState::Vertex &second) { return first.id == second.id && first.labels == second.labels && first.props == second.props; } bool operator==(const DatabaseState::Edge &first, const DatabaseState::Edge &second) { return first.from == second.from && first.to == second.to && first.edge_type == second.edge_type && first.props == second.props; } bool operator==(const DatabaseState::IndexKey &first, const DatabaseState::IndexKey &second) { return first.label == second.label && first.property == second.property; } bool operator==(const DatabaseState::UniqueConstraint &first, const DatabaseState::UniqueConstraint &second) { return first.label == second.label && first.props == second.props; } bool operator==(const DatabaseState &first, const DatabaseState &second) { return first.vertices == second.vertices && first.edges == second.edges && first.indices == second.indices; } // Returns next query if the end is not reached, otherwise returns an empty // string. std::string DumpNext(CypherDumpGenerator *dump) { std::ostringstream oss; if (dump->NextQuery(&oss)) return oss.str(); return ""; } class DatabaseEnvironment { public: GraphDbAccessor Access() { return db_.Access(); } DatabaseState GetState() { // Capture all vertices std::map gid_mapping; std::set vertices; auto dba = db_.Access(); for (const auto &vertex : dba.Vertices(false)) { std::set labels; for (const auto &label : vertex.labels()) { labels.insert(dba.LabelName(label)); } std::map props; for (const auto &kv : vertex.Properties()) { props.emplace(dba.PropertyName(kv.first), kv.second); } CHECK(props.count(kPropertyId) == 1); const auto id = props[kPropertyId].ValueInt(); gid_mapping[vertex.gid()] = id; vertices.insert({id, labels, props}); } // Capture all edges std::set edges; for (const auto &edge : dba.Edges(false)) { const auto &edge_type_name = dba.EdgeTypeName(edge.EdgeType()); std::map props; for (const auto &kv : edge.Properties()) { props.emplace(dba.PropertyName(kv.first), kv.second); } const auto from = gid_mapping[edge.from().gid()]; const auto to = gid_mapping[edge.to().gid()]; edges.insert({from, to, edge_type_name, props}); } // Capture all indices std::set indices; for (const auto &key : dba.GetIndicesKeys()) { indices.insert( {dba.LabelName(key.label_), dba.PropertyName(key.property_)}); } // Capture all unique constraints std::set constraints; for (const auto &constraint : dba.ListUniqueConstraints()) { std::set props; for (const auto &prop : constraint.properties) { props.insert(dba.PropertyName(prop)); } constraints.insert({dba.LabelName(constraint.label), props}); } return {vertices, edges, indices, constraints}; } private: database::GraphDb db_; }; void Execute(GraphDbAccessor *dba, const std::string &query) { CHECK(dba); ResultStreamFaker results; query::DbAccessor query_dba(dba); query::Interpreter()(query, &query_dba, {}, false, utils::NewDeleteResource()) .PullAll(results); } VertexAccessor CreateVertex(GraphDbAccessor *dba, const std::vector &labels, const std::map &props, bool add_property_id = true) { CHECK(dba); auto vertex = dba->InsertVertex(); for (const auto &label_name : labels) { vertex.add_label(dba->Label(label_name)); } for (const auto &kv : props) { vertex.PropsSet(dba->Property(kv.first), kv.second); } if (add_property_id) { vertex.PropsSet(dba->Property(kPropertyId), PropertyValue(vertex.gid().AsInt())); } return vertex; } EdgeAccessor CreateEdge(GraphDbAccessor *dba, VertexAccessor from, VertexAccessor to, const std::string &edge_type_name, const std::map &props, bool add_property_id = true) { CHECK(dba); auto edge = dba->InsertEdge(from, to, dba->EdgeType(edge_type_name)); for (const auto &kv : props) { edge.PropsSet(dba->Property(kv.first), kv.second); } if (add_property_id) { edge.PropsSet(dba->Property(kPropertyId), PropertyValue(edge.gid().AsInt())); } return edge; } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(DumpTest, EmptyGraph) { DatabaseEnvironment db; auto dba = db.Access(); query::DbAccessor query_dba(&dba); CypherDumpGenerator dump(&query_dba); EXPECT_EQ(DumpNext(&dump), ""); } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(DumpTest, SingleVertex) { DatabaseEnvironment db; { auto dba = db.Access(); CreateVertex(&dba, {}, {}, false); dba.Commit(); } { auto dba = db.Access(); query::DbAccessor query_dba(&dba); CypherDumpGenerator dump(&query_dba); EXPECT_EQ(DumpNext(&dump), kCreateInternalIndex); EXPECT_EQ(DumpNext(&dump), "CREATE (:__mg_vertex__ {__mg_id__: 0});"); EXPECT_EQ(DumpNext(&dump), kDropInternalIndex); EXPECT_EQ(DumpNext(&dump), kRemoveInternalLabelProperty); EXPECT_EQ(DumpNext(&dump), ""); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(DumpTest, VertexWithSingleLabel) { DatabaseEnvironment db; { auto dba = db.Access(); CreateVertex(&dba, {"Label1"}, {}, false); dba.Commit(); } { auto dba = db.Access(); query::DbAccessor query_dba(&dba); CypherDumpGenerator dump(&query_dba); EXPECT_EQ(DumpNext(&dump), kCreateInternalIndex); EXPECT_EQ(DumpNext(&dump), "CREATE (:__mg_vertex__:Label1 {__mg_id__: 0});"); EXPECT_EQ(DumpNext(&dump), kDropInternalIndex); EXPECT_EQ(DumpNext(&dump), kRemoveInternalLabelProperty); EXPECT_EQ(DumpNext(&dump), ""); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(DumpTest, VertexWithMultipleLabels) { DatabaseEnvironment db; { auto dba = db.Access(); CreateVertex(&dba, {"Label1", "Label2"}, {}, false); dba.Commit(); } { auto dba = db.Access(); query::DbAccessor query_dba(&dba); CypherDumpGenerator dump(&query_dba); EXPECT_EQ(DumpNext(&dump), kCreateInternalIndex); EXPECT_EQ(DumpNext(&dump), "CREATE (:__mg_vertex__:Label1:Label2 {__mg_id__: 0});"); EXPECT_EQ(DumpNext(&dump), kDropInternalIndex); EXPECT_EQ(DumpNext(&dump), kRemoveInternalLabelProperty); EXPECT_EQ(DumpNext(&dump), ""); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(DumpTest, VertexWithSingleProperty) { DatabaseEnvironment db; { auto dba = db.Access(); CreateVertex(&dba, {}, {{"prop", PropertyValue(42)}}, false); dba.Commit(); } { auto dba = db.Access(); query::DbAccessor query_dba(&dba); CypherDumpGenerator dump(&query_dba); EXPECT_EQ(DumpNext(&dump), kCreateInternalIndex); EXPECT_EQ(DumpNext(&dump), "CREATE (:__mg_vertex__ {__mg_id__: 0, prop: 42});"); EXPECT_EQ(DumpNext(&dump), kDropInternalIndex); EXPECT_EQ(DumpNext(&dump), kRemoveInternalLabelProperty); EXPECT_EQ(DumpNext(&dump), ""); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(DumpTest, MultipleVertices) { DatabaseEnvironment db; { auto dba = db.Access(); CreateVertex(&dba, {}, {}, false); CreateVertex(&dba, {}, {}, false); CreateVertex(&dba, {}, {}, false); dba.Commit(); } { auto dba = db.Access(); query::DbAccessor query_dba(&dba); CypherDumpGenerator dump(&query_dba); EXPECT_EQ(DumpNext(&dump), kCreateInternalIndex); EXPECT_EQ(DumpNext(&dump), "CREATE (:__mg_vertex__ {__mg_id__: 0});"); EXPECT_EQ(DumpNext(&dump), "CREATE (:__mg_vertex__ {__mg_id__: 1});"); EXPECT_EQ(DumpNext(&dump), "CREATE (:__mg_vertex__ {__mg_id__: 2});"); EXPECT_EQ(DumpNext(&dump), kDropInternalIndex); EXPECT_EQ(DumpNext(&dump), kRemoveInternalLabelProperty); EXPECT_EQ(DumpNext(&dump), ""); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(DumpTest, SingleEdge) { DatabaseEnvironment db; { auto dba = db.Access(); auto u = CreateVertex(&dba, {}, {}, false); auto v = CreateVertex(&dba, {}, {}, false); CreateEdge(&dba, u, v, "EdgeType", {}, false); dba.Commit(); } { auto dba = db.Access(); query::DbAccessor query_dba(&dba); CypherDumpGenerator dump(&query_dba); EXPECT_EQ(DumpNext(&dump), kCreateInternalIndex); EXPECT_EQ(DumpNext(&dump), "CREATE (:__mg_vertex__ {__mg_id__: 0});"); EXPECT_EQ(DumpNext(&dump), "CREATE (:__mg_vertex__ {__mg_id__: 1});"); EXPECT_EQ(DumpNext(&dump), "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = " "0 AND v.__mg_id__ = 1 CREATE (u)-[:EdgeType]->(v);"); EXPECT_EQ(DumpNext(&dump), kDropInternalIndex); EXPECT_EQ(DumpNext(&dump), kRemoveInternalLabelProperty); EXPECT_EQ(DumpNext(&dump), ""); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(DumpTest, MultipleEdges) { DatabaseEnvironment db; { auto dba = db.Access(); auto u = CreateVertex(&dba, {}, {}, false); auto v = CreateVertex(&dba, {}, {}, false); auto w = CreateVertex(&dba, {}, {}, false); CreateEdge(&dba, u, v, "EdgeType", {}, false); CreateEdge(&dba, v, u, "EdgeType", {}, false); CreateEdge(&dba, v, w, "EdgeType", {}, false); dba.Commit(); } { auto dba = db.Access(); query::DbAccessor query_dba(&dba); CypherDumpGenerator dump(&query_dba); EXPECT_EQ(DumpNext(&dump), kCreateInternalIndex); EXPECT_EQ(DumpNext(&dump), "CREATE (:__mg_vertex__ {__mg_id__: 0});"); EXPECT_EQ(DumpNext(&dump), "CREATE (:__mg_vertex__ {__mg_id__: 1});"); EXPECT_EQ(DumpNext(&dump), "CREATE (:__mg_vertex__ {__mg_id__: 2});"); EXPECT_EQ(DumpNext(&dump), "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = " "0 AND v.__mg_id__ = 1 CREATE (u)-[:EdgeType]->(v);"); EXPECT_EQ(DumpNext(&dump), "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = " "1 AND v.__mg_id__ = 0 CREATE (u)-[:EdgeType]->(v);"); EXPECT_EQ(DumpNext(&dump), "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = " "1 AND v.__mg_id__ = 2 CREATE (u)-[:EdgeType]->(v);"); EXPECT_EQ(DumpNext(&dump), kDropInternalIndex); EXPECT_EQ(DumpNext(&dump), kRemoveInternalLabelProperty); EXPECT_EQ(DumpNext(&dump), ""); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(DumpTest, EdgeWithProperties) { DatabaseEnvironment db; { auto dba = db.Access(); auto u = CreateVertex(&dba, {}, {}, false); auto v = CreateVertex(&dba, {}, {}, false); CreateEdge(&dba, u, v, "EdgeType", {{"prop", PropertyValue(13)}}, false); dba.Commit(); } { auto dba = db.Access(); query::DbAccessor query_dba(&dba); CypherDumpGenerator dump(&query_dba); EXPECT_EQ(DumpNext(&dump), kCreateInternalIndex); EXPECT_EQ(DumpNext(&dump), "CREATE (:__mg_vertex__ {__mg_id__: 0});"); EXPECT_EQ(DumpNext(&dump), "CREATE (:__mg_vertex__ {__mg_id__: 1});"); EXPECT_EQ(DumpNext(&dump), "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = " "0 AND v.__mg_id__ = 1 CREATE (u)-[:EdgeType {prop: 13}]->(v);"); EXPECT_EQ(DumpNext(&dump), kDropInternalIndex); EXPECT_EQ(DumpNext(&dump), kRemoveInternalLabelProperty); EXPECT_EQ(DumpNext(&dump), ""); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(DumpTest, IndicesKeys) { DatabaseEnvironment db; { auto dba = db.Access(); CreateVertex(&dba, {"Label1", "Label2"}, {{"p", PropertyValue(1)}}, false); Execute(&dba, "CREATE INDEX ON :Label1(prop);"); Execute(&dba, "CREATE INDEX ON :Label2(prop);"); dba.Commit(); } { auto dba = db.Access(); query::DbAccessor query_dba(&dba); CypherDumpGenerator dump(&query_dba); EXPECT_EQ(DumpNext(&dump), "CREATE INDEX ON :Label1(prop);"); EXPECT_EQ(DumpNext(&dump), "CREATE INDEX ON :Label2(prop);"); EXPECT_EQ(DumpNext(&dump), kCreateInternalIndex); EXPECT_EQ(DumpNext(&dump), "CREATE (:__mg_vertex__:Label1:Label2 {__mg_id__: 0, p: 1});"); EXPECT_EQ(DumpNext(&dump), kDropInternalIndex); EXPECT_EQ(DumpNext(&dump), kRemoveInternalLabelProperty); EXPECT_EQ(DumpNext(&dump), ""); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(DumpTest, UniqueConstraints) { DatabaseEnvironment db; { auto dba = db.Access(); CreateVertex(&dba, {"Label"}, {{"prop", PropertyValue(1)}}, false); Execute(&dba, "CREATE CONSTRAINT ON (u:Label) ASSERT u.prop IS UNIQUE;"); // Create one with multiple properties. Execute( &dba, "CREATE CONSTRAINT ON (u:Label) ASSERT u.prop1, u.prop2 IS UNIQUE;"); dba.Commit(); } { auto dba = db.Access(); query::DbAccessor query_dba(&dba); CypherDumpGenerator dump(&query_dba); EXPECT_EQ(DumpNext(&dump), kCreateInternalIndex); EXPECT_EQ(DumpNext(&dump), "CREATE CONSTRAINT ON (u:Label) ASSERT u.prop IS UNIQUE;"); EXPECT_EQ( DumpNext(&dump), "CREATE CONSTRAINT ON (u:Label) ASSERT u.prop1, u.prop2 IS UNIQUE;"); EXPECT_EQ(DumpNext(&dump), "CREATE (:__mg_vertex__:Label {__mg_id__: 0, prop: 1});"); EXPECT_EQ(DumpNext(&dump), kDropInternalIndex); EXPECT_EQ(DumpNext(&dump), kRemoveInternalLabelProperty); EXPECT_EQ(DumpNext(&dump), ""); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(DumpTest, CheckStateVertexWithMultipleProperties) { DatabaseEnvironment db; { auto dba = db.Access(); std::map prop1 = { {"nested1", PropertyValue(1337)}, {"nested2", PropertyValue(3.14)}}; CreateVertex( &dba, {"Label1", "Label2"}, {{"prop1", PropertyValue(prop1)}, {"prop2", PropertyValue("$'\t'")}}); dba.Commit(); } DatabaseEnvironment db_dump; { auto dba = db.Access(); query::DbAccessor query_dba(&dba); CypherDumpGenerator dump(&query_dba); std::string cmd; while (!(cmd = DumpNext(&dump)).empty()) { auto dba_dump = db_dump.Access(); Execute(&dba_dump, cmd); dba_dump.Commit(); } } EXPECT_EQ(db.GetState(), db_dump.GetState()); } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(DumpTest, CheckStateSimpleGraph) { DatabaseEnvironment db; { auto dba = db.Access(); auto u = CreateVertex(&dba, {"Person"}, {{"name", PropertyValue("Ivan")}}); auto v = CreateVertex(&dba, {"Person"}, {{"name", PropertyValue("Josko")}}); auto w = CreateVertex( &dba, {"Person"}, {{"name", PropertyValue("Bosko")}, {"id", PropertyValue(0)}}); auto z = CreateVertex( &dba, {"Person"}, {{"name", PropertyValue("Buha")}, {"id", PropertyValue(1)}}); CreateEdge(&dba, u, v, "Knows", {}); CreateEdge(&dba, v, w, "Knows", {{"how_long", PropertyValue(5)}}); CreateEdge(&dba, w, u, "Knows", {{"how", PropertyValue("distant past")}}); CreateEdge(&dba, v, u, "Knows", {}); CreateEdge(&dba, v, u, "Likes", {}); CreateEdge(&dba, z, u, "Knows", {}); CreateEdge(&dba, w, z, "Knows", {{"how", PropertyValue("school")}}); CreateEdge(&dba, w, z, "Likes", {{"how", PropertyValue("very much")}}); // Create few indices Execute(&dba, "CREATE CONSTRAINT ON (u:Person) ASSERT u.name IS UNIQUE;"); Execute(&dba, "CREATE INDEX ON :Person(id);"); Execute(&dba, "CREATE INDEX ON :Person(unexisting_property);"); } const auto &db_initial_state = db.GetState(); DatabaseEnvironment db_dump; { auto dba = db.Access(); query::DbAccessor query_dba(&dba); CypherDumpGenerator dump(&query_dba); std::string cmd; while (!(cmd = DumpNext(&dump)).empty()) { auto dba_dump = db_dump.Access(); Execute(&dba_dump, cmd); dba_dump.Commit(); } } EXPECT_EQ(db.GetState(), db_dump.GetState()); // Make sure that dump function doesn't make changes on the database. EXPECT_EQ(db.GetState(), db_initial_state); } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(DumpTest, ExecuteDumpDatabase) { DatabaseEnvironment db; { auto dba = db.Access(); CreateVertex(&dba, {}, {}, false); dba.Commit(); } { auto dba = db.Access(); query::DbAccessor query_dba(&dba); const std::string query = "DUMP DATABASE"; ResultStreamFaker stream; auto results = query::Interpreter()(query, &query_dba, {}, false, utils::NewDeleteResource()); stream.Header(results.header()); results.PullAll(stream); stream.Summary(results.summary()); EXPECT_EQ(stream.GetResults().size(), 4U); ASSERT_EQ(stream.GetHeader().size(), 1U); EXPECT_EQ(stream.GetHeader()[0], "QUERY"); } }