#include #include #include #include #include #include "communication/result_stream_faker.hpp" #include "query/dump.hpp" #include "query/interpreter.hpp" #include "query/typed_value.hpp" #include "storage/v2/storage.hpp" 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 LabelItem { std::string label; }; struct LabelPropertyItem { std::string label; std::string property; }; std::set vertices; std::set edges; std::set label_indices; std::set label_property_indices; std::set existence_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::LabelItem &first, const DatabaseState::LabelItem &second) { return first.label < second.label; } bool operator<(const DatabaseState::LabelPropertyItem &first, const DatabaseState::LabelPropertyItem &second) { if (first.label != second.label) return first.label < second.label; return first.property < second.property; } 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::LabelItem &first, const DatabaseState::LabelItem &second) { return first.label == second.label; } bool operator==(const DatabaseState::LabelPropertyItem &first, const DatabaseState::LabelPropertyItem &second) { return first.label == second.label && first.property == second.property; } bool operator==(const DatabaseState &first, const DatabaseState &second) { return first.vertices == second.vertices && first.edges == second.edges && first.label_indices == second.label_indices && first.label_property_indices == second.label_property_indices && first.existence_constraints == second.existence_constraints; } DatabaseState GetState(storage::Storage *db) { // Capture all vertices std::map gid_mapping; std::set vertices; auto dba = db->Access(); for (const auto &vertex : dba.Vertices(storage::View::NEW)) { std::set labels; auto maybe_labels = vertex.Labels(storage::View::NEW); CHECK(maybe_labels.HasValue()); for (const auto &label : *maybe_labels) { labels.insert(dba.LabelToName(label)); } std::map props; auto maybe_properties = vertex.Properties(storage::View::NEW); CHECK(maybe_properties.HasValue()); for (const auto &kv : *maybe_properties) { props.emplace(dba.PropertyToName(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 &vertex : dba.Vertices(storage::View::NEW)) { auto maybe_edges = vertex.OutEdges(storage::View::NEW); CHECK(maybe_edges.HasValue()); for (const auto &edge : *maybe_edges) { const auto &edge_type_name = dba.EdgeTypeToName(edge.EdgeType()); std::map props; auto maybe_properties = edge.Properties(storage::View::NEW); CHECK(maybe_properties.HasValue()); for (const auto &kv : *maybe_properties) { props.emplace(dba.PropertyToName(kv.first), kv.second); } const auto from = gid_mapping[edge.FromVertex().Gid()]; const auto to = gid_mapping[edge.ToVertex().Gid()]; edges.insert({from, to, edge_type_name, props}); } } // Capture all indices std::set label_indices; std::set label_property_indices; { auto info = dba.ListAllIndices(); for (const auto &item : info.label) { label_indices.insert({dba.LabelToName(item)}); } for (const auto &item : info.label_property) { label_property_indices.insert( {dba.LabelToName(item.first), dba.PropertyToName(item.second)}); } } // Capture all constraints std::set existence_constraints; { auto info = dba.ListAllConstraints(); for (const auto &item : info.existence) { existence_constraints.insert( {dba.LabelToName(item.first), dba.PropertyToName(item.second)}); } } return {vertices, edges, label_indices, label_property_indices, existence_constraints}; } auto Execute(storage::Storage *db, const std::string &query) { query::InterpreterContext context(db); query::Interpreter interpreter(&context); ResultStreamFaker stream(db); auto [header, _] = interpreter.Prepare(query, {}); stream.Header(header); auto summary = interpreter.PullAll(&stream); stream.Summary(summary); return stream; } storage::VertexAccessor CreateVertex( storage::Storage::Accessor *dba, const std::vector &labels, const std::map &props, bool add_property_id = true) { CHECK(dba); auto vertex = dba->CreateVertex(); for (const auto &label_name : labels) { CHECK(vertex.AddLabel(dba->NameToLabel(label_name)).HasValue()); } for (const auto &kv : props) { CHECK(vertex.SetProperty(dba->NameToProperty(kv.first), kv.second) .HasValue()); } if (add_property_id) { CHECK(vertex .SetProperty(dba->NameToProperty(kPropertyId), storage::PropertyValue(vertex.Gid().AsInt())) .HasValue()); } return vertex; } storage::EdgeAccessor CreateEdge( storage::Storage::Accessor *dba, storage::VertexAccessor *from, storage::VertexAccessor *to, const std::string &edge_type_name, const std::map &props, bool add_property_id = true) { CHECK(dba); auto edge = dba->CreateEdge(from, to, dba->NameToEdgeType(edge_type_name)); CHECK(edge.HasValue()); for (const auto &kv : props) { CHECK( edge->SetProperty(dba->NameToProperty(kv.first), kv.second).HasValue()); } if (add_property_id) { CHECK(edge->SetProperty(dba->NameToProperty(kPropertyId), storage::PropertyValue(edge->Gid().AsInt())) .HasValue()); } return *edge; } template void VerifyQueries( const std::vector> &results, TArgs &&... args) { std::vector expected{std::forward(args)...}; std::vector got; got.reserve(results.size()); for (const auto &result : results) { ASSERT_EQ(result.size(), 1); ASSERT_TRUE(result[0].IsString()); got.push_back(result[0].ValueString()); } ASSERT_EQ(got, expected); } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(DumpTest, EmptyGraph) { storage::Storage db; ResultStreamFaker stream(&db); query::AnyStream query_stream(&stream, utils::NewDeleteResource()); { auto acc = db.Access(); query::DbAccessor dba(&acc); query::DumpDatabaseToCypherQueries(&dba, &query_stream); } ASSERT_EQ(stream.GetResults().size(), 0); } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(DumpTest, SingleVertex) { storage::Storage db; { auto dba = db.Access(); CreateVertex(&dba, {}, {}, false); ASSERT_FALSE(dba.Commit().HasError()); } { ResultStreamFaker stream(&db); query::AnyStream query_stream(&stream, utils::NewDeleteResource()); { auto acc = db.Access(); query::DbAccessor dba(&acc); query::DumpDatabaseToCypherQueries(&dba, &query_stream); } VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0});", kDropInternalIndex, kRemoveInternalLabelProperty); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(DumpTest, VertexWithSingleLabel) { storage::Storage db; { auto dba = db.Access(); CreateVertex(&dba, {"Label1"}, {}, false); ASSERT_FALSE(dba.Commit().HasError()); } { ResultStreamFaker stream(&db); query::AnyStream query_stream(&stream, utils::NewDeleteResource()); { auto acc = db.Access(); query::DbAccessor dba(&acc); query::DumpDatabaseToCypherQueries(&dba, &query_stream); } VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__:Label1 {__mg_id__: 0});", kDropInternalIndex, kRemoveInternalLabelProperty); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(DumpTest, VertexWithMultipleLabels) { storage::Storage db; { auto dba = db.Access(); CreateVertex(&dba, {"Label1", "Label2"}, {}, false); ASSERT_FALSE(dba.Commit().HasError()); } { ResultStreamFaker stream(&db); query::AnyStream query_stream(&stream, utils::NewDeleteResource()); { auto acc = db.Access(); query::DbAccessor dba(&acc); query::DumpDatabaseToCypherQueries(&dba, &query_stream); } VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__:Label1:Label2 {__mg_id__: 0});", kDropInternalIndex, kRemoveInternalLabelProperty); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(DumpTest, VertexWithSingleProperty) { storage::Storage db; { auto dba = db.Access(); CreateVertex(&dba, {}, {{"prop", storage::PropertyValue(42)}}, false); ASSERT_FALSE(dba.Commit().HasError()); } { ResultStreamFaker stream(&db); query::AnyStream query_stream(&stream, utils::NewDeleteResource()); { auto acc = db.Access(); query::DbAccessor dba(&acc); query::DumpDatabaseToCypherQueries(&dba, &query_stream); } VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0, prop: 42});", kDropInternalIndex, kRemoveInternalLabelProperty); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(DumpTest, MultipleVertices) { storage::Storage db; { auto dba = db.Access(); CreateVertex(&dba, {}, {}, false); CreateVertex(&dba, {}, {}, false); CreateVertex(&dba, {}, {}, false); ASSERT_FALSE(dba.Commit().HasError()); } { ResultStreamFaker stream(&db); query::AnyStream query_stream(&stream, utils::NewDeleteResource()); { auto acc = db.Access(); query::DbAccessor dba(&acc); query::DumpDatabaseToCypherQueries(&dba, &query_stream); } VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0});", "CREATE (:__mg_vertex__ {__mg_id__: 1});", "CREATE (:__mg_vertex__ {__mg_id__: 2});", kDropInternalIndex, kRemoveInternalLabelProperty); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(DumpTest, SingleEdge) { storage::Storage db; { auto dba = db.Access(); auto u = CreateVertex(&dba, {}, {}, false); auto v = CreateVertex(&dba, {}, {}, false); CreateEdge(&dba, &u, &v, "EdgeType", {}, false); ASSERT_FALSE(dba.Commit().HasError()); } { ResultStreamFaker stream(&db); query::AnyStream query_stream(&stream, utils::NewDeleteResource()); { auto acc = db.Access(); query::DbAccessor dba(&acc); query::DumpDatabaseToCypherQueries(&dba, &query_stream); } VerifyQueries( stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0});", "CREATE (:__mg_vertex__ {__mg_id__: 1});", "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 0 AND " "v.__mg_id__ = 1 CREATE (u)-[:EdgeType]->(v);", kDropInternalIndex, kRemoveInternalLabelProperty); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(DumpTest, MultipleEdges) { storage::Storage 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); ASSERT_FALSE(dba.Commit().HasError()); } { ResultStreamFaker stream(&db); query::AnyStream query_stream(&stream, utils::NewDeleteResource()); { auto acc = db.Access(); query::DbAccessor dba(&acc); query::DumpDatabaseToCypherQueries(&dba, &query_stream); } VerifyQueries( stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0});", "CREATE (:__mg_vertex__ {__mg_id__: 1});", "CREATE (:__mg_vertex__ {__mg_id__: 2});", "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 0 AND " "v.__mg_id__ = 1 CREATE (u)-[:EdgeType]->(v);", "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 1 AND " "v.__mg_id__ = 0 CREATE (u)-[:EdgeType]->(v);", "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 1 AND " "v.__mg_id__ = 2 CREATE (u)-[:EdgeType]->(v);", kDropInternalIndex, kRemoveInternalLabelProperty); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(DumpTest, EdgeWithProperties) { storage::Storage db; { auto dba = db.Access(); auto u = CreateVertex(&dba, {}, {}, false); auto v = CreateVertex(&dba, {}, {}, false); CreateEdge(&dba, &u, &v, "EdgeType", {{"prop", storage::PropertyValue(13)}}, false); ASSERT_FALSE(dba.Commit().HasError()); } { ResultStreamFaker stream(&db); query::AnyStream query_stream(&stream, utils::NewDeleteResource()); { auto acc = db.Access(); query::DbAccessor dba(&acc); query::DumpDatabaseToCypherQueries(&dba, &query_stream); } VerifyQueries( stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0});", "CREATE (:__mg_vertex__ {__mg_id__: 1});", "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 0 AND " "v.__mg_id__ = 1 CREATE (u)-[:EdgeType {prop: 13}]->(v);", kDropInternalIndex, kRemoveInternalLabelProperty); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(DumpTest, IndicesKeys) { storage::Storage db; { auto dba = db.Access(); CreateVertex(&dba, {"Label1", "Label2"}, {{"p", storage::PropertyValue(1)}}, false); ASSERT_FALSE(dba.Commit().HasError()); } ASSERT_TRUE( db.CreateIndex(db.NameToLabel("Label1"), db.NameToProperty("prop"))); ASSERT_TRUE( db.CreateIndex(db.NameToLabel("Label2"), db.NameToProperty("prop"))); { ResultStreamFaker stream(&db); query::AnyStream query_stream(&stream, utils::NewDeleteResource()); { auto acc = db.Access(); query::DbAccessor dba(&acc); query::DumpDatabaseToCypherQueries(&dba, &query_stream); } VerifyQueries(stream.GetResults(), "CREATE INDEX ON :Label1(prop);", "CREATE INDEX ON :Label2(prop);", kCreateInternalIndex, "CREATE (:__mg_vertex__:Label1:Label2 {__mg_id__: 0, p: 1});", kDropInternalIndex, kRemoveInternalLabelProperty); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(DumpTest, ExistenceConstraints) { storage::Storage db; { auto dba = db.Access(); CreateVertex(&dba, {"Label"}, {{"prop", storage::PropertyValue(1)}}, false); ASSERT_FALSE(dba.Commit().HasError()); } { auto res = db.CreateExistenceConstraint(db.NameToLabel("Label"), db.NameToProperty("prop")); ASSERT_TRUE(res.HasValue()); ASSERT_TRUE(res.GetValue()); } { ResultStreamFaker stream(&db); query::AnyStream query_stream(&stream, utils::NewDeleteResource()); { auto acc = db.Access(); query::DbAccessor dba(&acc); query::DumpDatabaseToCypherQueries(&dba, &query_stream); } VerifyQueries(stream.GetResults(), "CREATE CONSTRAINT ON (u:Label) ASSERT EXISTS (u.prop);", kCreateInternalIndex, "CREATE (:__mg_vertex__:Label {__mg_id__: 0, prop: 1});", kDropInternalIndex, kRemoveInternalLabelProperty); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(DumpTest, CheckStateVertexWithMultipleProperties) { storage::Storage db; { auto dba = db.Access(); std::map prop1 = { {"nested1", storage::PropertyValue(1337)}, {"nested2", storage::PropertyValue(3.14)}}; CreateVertex(&dba, {"Label1", "Label2"}, {{"prop1", storage::PropertyValue(prop1)}, {"prop2", storage::PropertyValue("$'\t'")}}); ASSERT_FALSE(dba.Commit().HasError()); } storage::Storage db_dump; { ResultStreamFaker stream(&db); query::AnyStream query_stream(&stream, utils::NewDeleteResource()); { auto acc = db.Access(); query::DbAccessor dba(&acc); query::DumpDatabaseToCypherQueries(&dba, &query_stream); } const auto &results = stream.GetResults(); ASSERT_GE(results.size(), 1); for (const auto &item : results) { ASSERT_EQ(item.size(), 1); ASSERT_TRUE(item[0].IsString()); Execute(&db_dump, item[0].ValueString()); } } ASSERT_EQ(GetState(&db), GetState(&db_dump)); } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(DumpTest, CheckStateSimpleGraph) { storage::Storage db; { auto dba = db.Access(); auto u = CreateVertex(&dba, {"Person"}, {{"name", storage::PropertyValue("Ivan")}}); auto v = CreateVertex(&dba, {"Person"}, {{"name", storage::PropertyValue("Josko")}}); auto w = CreateVertex(&dba, {"Person"}, {{"name", storage::PropertyValue("Bosko")}, {"id", storage::PropertyValue(0)}}); auto z = CreateVertex(&dba, {"Person"}, {{"name", storage::PropertyValue("Buha")}, {"id", storage::PropertyValue(1)}}); CreateEdge(&dba, &u, &v, "Knows", {}); CreateEdge(&dba, &v, &w, "Knows", {{"how_long", storage::PropertyValue(5)}}); CreateEdge(&dba, &w, &u, "Knows", {{"how", storage::PropertyValue("distant past")}}); CreateEdge(&dba, &v, &u, "Knows", {}); CreateEdge(&dba, &v, &u, "Likes", {}); CreateEdge(&dba, &z, &u, "Knows", {}); CreateEdge(&dba, &w, &z, "Knows", {{"how", storage::PropertyValue("school")}}); CreateEdge(&dba, &w, &z, "Likes", {{"how", storage::PropertyValue("very much")}}); ASSERT_FALSE(dba.Commit().HasError()); } { auto ret = db.CreateExistenceConstraint(db.NameToLabel("Person"), db.NameToProperty("name")); ASSERT_TRUE(ret.HasValue()); ASSERT_TRUE(ret.GetValue()); } ASSERT_TRUE( db.CreateIndex(db.NameToLabel("Person"), db.NameToProperty("id"))); ASSERT_TRUE(db.CreateIndex(db.NameToLabel("Person"), db.NameToProperty("unexisting_property"))); const auto &db_initial_state = GetState(&db); storage::Storage db_dump; { ResultStreamFaker stream(&db); query::AnyStream query_stream(&stream, utils::NewDeleteResource()); { auto acc = db.Access(); query::DbAccessor dba(&acc); query::DumpDatabaseToCypherQueries(&dba, &query_stream); } const auto &results = stream.GetResults(); // Indices and constraints are 3 queries and there must be at least one more // query for the data. ASSERT_GE(results.size(), 4); for (const auto &item : results) { ASSERT_EQ(item.size(), 1); ASSERT_TRUE(item[0].IsString()); Execute(&db_dump, item[0].ValueString()); } } ASSERT_EQ(GetState(&db), GetState(&db_dump)); // Make sure that dump function doesn't make changes on the database. ASSERT_EQ(GetState(&db), db_initial_state); } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(DumpTest, ExecuteDumpDatabase) { storage::Storage db; { auto dba = db.Access(); CreateVertex(&dba, {}, {}, false); ASSERT_FALSE(dba.Commit().HasError()); } { auto stream = Execute(&db, "DUMP DATABASE"); const auto &header = stream.GetHeader(); const auto &results = stream.GetResults(); ASSERT_EQ(header.size(), 1U); EXPECT_EQ(header[0], "QUERY"); EXPECT_EQ(results.size(), 4U); for (const auto &item : results) { EXPECT_EQ(item.size(), 1); EXPECT_TRUE(item[0].IsString()); } EXPECT_EQ(results[0][0].ValueString(), "CREATE INDEX ON :__mg_vertex__(__mg_id__);"); EXPECT_EQ(results[1][0].ValueString(), "CREATE (:__mg_vertex__ {__mg_id__: 0});"); EXPECT_EQ(results[2][0].ValueString(), "DROP INDEX ON :__mg_vertex__(__mg_id__);"); EXPECT_EQ(results[3][0].ValueString(), "MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__;"); } }