From 329818b1b07d9a6e3d7493ea531cc2f697c7a2df Mon Sep 17 00:00:00 2001 From: Matej Ferencevic <matej.ferencevic@memgraph.io> Date: Tue, 12 Nov 2019 10:47:02 +0100 Subject: [PATCH] Make the Cypher dumper a function Summary: The dumper is now a function and doesn't have to worry about any state. The function streams the Cypher queries directly to the client. This diff also makes the dumper work with storage v2. Reviewers: teon.banek Reviewed By: teon.banek Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D2545 --- src/CMakeLists.txt | 4 +- src/database/single_node/dump.hpp | 126 ---- src/{database/single_node => query}/dump.cpp | 165 ++--- src/query/dump.hpp | 12 + .../interpret/awesome_memgraph_functions.cpp | 3 - src/query/interpreter.cpp | 14 +- src/query/interpreter.hpp | 46 +- src/query/stream.hpp | 56 ++ tests/unit/CMakeLists.txt | 6 +- tests/unit/database_dump.cpp | 596 ----------------- tests/unit/query_dump.cpp | 632 ++++++++++++++++++ 11 files changed, 799 insertions(+), 861 deletions(-) delete mode 100644 src/database/single_node/dump.hpp rename src/{database/single_node => query}/dump.cpp (66%) create mode 100644 src/query/dump.hpp create mode 100644 src/query/stream.hpp delete mode 100644 tests/unit/database_dump.cpp create mode 100644 tests/unit/query_dump.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 25cad88ba..019be6617 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -36,7 +36,6 @@ set(mg_single_node_sources audit/log.cpp data_structures/concurrent/skiplist_gc.cpp database/single_node/config.cpp - database/single_node/dump.cpp database/single_node/graph_db.cpp database/single_node/graph_db_accessor.cpp durability/single_node/state_delta.cpp @@ -47,6 +46,7 @@ set(mg_single_node_sources glue/auth.cpp glue/communication.cpp query/common.cpp + query/dump.cpp query/frontend/ast/cypher_main_visitor.cpp query/frontend/ast/pretty_print.cpp query/frontend/parsing.cpp @@ -120,10 +120,10 @@ target_link_libraries(mg-single-node "-Wl,--dynamic-list=${CMAKE_SOURCE_DIR}/inc set(mg_single_node_v2_sources ${lcp_common_cpp_files} audit/log.cpp - database/single_node/dump.cpp glue/auth.cpp glue/communication.cpp query/common.cpp + query/dump.cpp query/frontend/ast/cypher_main_visitor.cpp query/frontend/ast/pretty_print.cpp query/frontend/parsing.cpp diff --git a/src/database/single_node/dump.hpp b/src/database/single_node/dump.hpp deleted file mode 100644 index d6d80c42f..000000000 --- a/src/database/single_node/dump.hpp +++ /dev/null @@ -1,126 +0,0 @@ -#pragma once - -#include <ostream> - -// TODO: Move this whole file to query folder -#include "query/db_accessor.hpp" -#ifndef MG_SINGLE_NODE_V2 -#include "storage/common/constraints/unique_constraints.hpp" -#endif - -namespace database { - -/// Class which generates sequence of openCypher queries which can be used to -/// dump the database state. -/// -/// Currently only dumps index keys, vertices and edges, one-by-one in multiple -/// queries. -class CypherDumpGenerator { - public: - explicit CypherDumpGenerator(query::DbAccessor *dba); - - CypherDumpGenerator(const CypherDumpGenerator &other) = delete; - // NOLINTNEXTLINE(performance-noexcept-move-constructor) - CypherDumpGenerator(CypherDumpGenerator &&other) = default; - CypherDumpGenerator &operator=(const CypherDumpGenerator &other) = delete; - CypherDumpGenerator &operator=(CypherDumpGenerator &&other) = delete; - ~CypherDumpGenerator() = default; - - bool NextQuery(std::ostream *os); - - private: - // A helper class that keeps container and its iterators. - template <typename TContainer> - class ContainerState { - public: - explicit ContainerState(TContainer container) - : container_(std::move(container)), - current_(container_.begin()), - end_(container_.end()), - empty_(current_ == end_) {} - - ContainerState(const ContainerState &other) = delete; - // NOLINTNEXTLINE(hicpp-noexcept-move,performance-noexcept-move-constructor) - ContainerState(ContainerState &&other) = default; - ContainerState &operator=(const ContainerState &other) = delete; - ContainerState &operator=(ContainerState &&other) = delete; - ~ContainerState() = default; - - auto GetCurrentAndAdvance() { - auto to_be_returned = current_; - if (current_ != end_) ++current_; - return to_be_returned; - } - - bool ReachedEnd() const { return current_ == end_; } - - // Returns true if the container is empty. - bool Empty() const { return empty_; } - - private: - TContainer container_; - - using TIterator = decltype(container_.begin()); - TIterator current_; - TIterator end_; - - bool empty_; - }; - - class EdgesState { - private: - using TVertices = decltype(std::declval<query::DbAccessor>().Vertices( - std::declval<storage::View>())); - - public: - explicit EdgesState(TVertices vertices) - : vertices_state_(std::move(vertices)) { - FindNext(); - } - - EdgesState(const EdgesState &other) = delete; - // NOLINTNEXTLINE(hicpp-noexcept-move,performance-noexcept-move-constructor) - EdgesState(EdgesState &&other) = default; - EdgesState &operator=(const EdgesState &other) = delete; - EdgesState &operator=(EdgesState &&other) = delete; - ~EdgesState() = default; - - auto GetCurrentAndAdvance() { - auto edge = *current_edge_; - FindNext(); - return edge; - } - - bool ReachedEnd() const { return !current_edge_; } - - // Returns true if the container is empty. - bool Empty() const { return !current_edge_; } - - private: - void FindNext(); - - std::optional<ContainerState<TVertices>> vertices_state_; - std::optional<ContainerState<std::vector<query::EdgeAccessor>>> - edges_list_state_; - std::optional<query::EdgeAccessor> current_edge_; - }; - - query::DbAccessor *dba_; - - bool created_internal_index_; - bool cleaned_internal_index_; - bool cleaned_internal_label_property_; - -#ifndef MG_SINGLE_NODE_V2 - std::optional<ContainerState<std::vector<LabelPropertyIndex::Key>>> - indices_state_; - std::optional< - ContainerState<std::vector<storage::constraints::ConstraintEntry>>> - unique_constraints_state_; -#endif - std::optional<ContainerState<decltype(dba_->Vertices(storage::View::OLD))>> - vertices_state_; - std::optional<EdgesState> edges_state_; -}; - -} // namespace database diff --git a/src/database/single_node/dump.cpp b/src/query/dump.cpp similarity index 66% rename from src/database/single_node/dump.cpp rename to src/query/dump.cpp index 2084ccfaf..c32463209 100644 --- a/src/database/single_node/dump.cpp +++ b/src/query/dump.cpp @@ -1,4 +1,4 @@ -#include "database/single_node/dump.hpp" +#include "query/dump.hpp" #include <iomanip> #include <limits> @@ -14,7 +14,7 @@ #include "utils/algorithm.hpp" #include "utils/string.hpp" -namespace database { +namespace query { namespace { @@ -164,9 +164,28 @@ void DumpEdge(std::ostream *os, query::DbAccessor *dba, *os << "]->(v);"; } -#ifndef MG_SINGLE_NODE_V2 +#ifdef MG_SINGLE_NODE_V2 +void DumpLabelIndex(std::ostream *os, query::DbAccessor *dba, + const storage::LabelId label) { + *os << "CREATE INDEX ON :" << dba->LabelToName(label) << ";"; +} + +void DumpLabelPropertyIndex(std::ostream *os, query::DbAccessor *dba, + storage::LabelId label, + storage::PropertyId property) { + *os << "CREATE INDEX ON :" << dba->LabelToName(label) << "(" + << dba->PropertyToName(property) << ");"; +} + +void DumpExistenceConstraint(std::ostream *os, query::DbAccessor *dba, + storage::LabelId label, + storage::PropertyId property) { + *os << "CREATE CONSTRAINT ON (u:" << dba->LabelToName(label) + << ") ASSERT EXISTS (u." << dba->PropertyToName(property) << ");"; +} +#else void DumpIndexKey(std::ostream *os, query::DbAccessor *dba, - const LabelPropertyIndex::Key &key) { + const database::LabelPropertyIndex::Key &key) { *os << "CREATE INDEX ON :" << dba->LabelToName(key.label_) << "(" << dba->PropertyToName(key.property_) << ");"; } @@ -186,85 +205,79 @@ void DumpUniqueConstraint( } // namespace -CypherDumpGenerator::CypherDumpGenerator(query::DbAccessor *dba) - : dba_(dba), - created_internal_index_(false), - cleaned_internal_index_(false), - cleaned_internal_label_property_(false) { - CHECK(dba); +void DumpDatabaseToCypherQueries(query::DbAccessor *dba, AnyStream *stream) { #ifdef MG_SINGLE_NODE_V2 - throw utils::NotYetImplemented("Dumping indices and constraints"); -#else - indices_state_.emplace(dba->GetIndicesKeys()); - unique_constraints_state_.emplace(dba->ListUniqueConstraints()); -#endif - vertices_state_.emplace(dba->Vertices(storage::View::OLD)); - edges_state_.emplace(dba->Vertices(storage::View::OLD)); -} - -bool CypherDumpGenerator::NextQuery(std::ostream *os) { -#ifdef MG_SINGLE_NODE_V2 - if (!vertices_state_->Empty() && !created_internal_index_) { -#else - if (!indices_state_->ReachedEnd()) { - DumpIndexKey(os, dba_, *indices_state_->GetCurrentAndAdvance()); - return true; - } else if (!vertices_state_->Empty() && !created_internal_index_) { -#endif - *os << "CREATE INDEX ON :" << kInternalVertexLabel << "(" - << kInternalPropertyId << ");"; - created_internal_index_ = true; - return true; -#ifndef MG_SINGLE_NODE_V2 - } else if (!unique_constraints_state_->ReachedEnd()) { - DumpUniqueConstraint(os, dba_, - *unique_constraints_state_->GetCurrentAndAdvance()); - return true; -#endif - } else if (!vertices_state_->ReachedEnd()) { - DumpVertex(os, dba_, *vertices_state_->GetCurrentAndAdvance()); - return true; - } else if (!edges_state_->ReachedEnd()) { - DumpEdge(os, dba_, edges_state_->GetCurrentAndAdvance()); - return true; - } else if (!vertices_state_->Empty() && !cleaned_internal_index_) { - *os << "DROP INDEX ON :" << kInternalVertexLabel << "(" - << kInternalPropertyId << ");"; - cleaned_internal_index_ = true; - return true; - } else if (!vertices_state_->Empty() && !cleaned_internal_label_property_) { - *os << "MATCH (u) REMOVE u:" << kInternalVertexLabel << ", u." - << kInternalPropertyId << ";"; - cleaned_internal_label_property_ = true; - return true; + { + auto info = dba->ListAllIndices(); + for (const auto &item : info.label) { + std::ostringstream os; + DumpLabelIndex(&os, dba, item); + stream->Result({TypedValue(os.str())}); + } + for (const auto &item : info.label_property) { + std::ostringstream os; + DumpLabelPropertyIndex(&os, dba, item.first, item.second); + stream->Result({TypedValue(os.str())}); + } } - - return false; -} - -void CypherDumpGenerator::EdgesState::FindNext() { - current_edge_ = std::nullopt; - if (edges_list_state_ && !edges_list_state_->ReachedEnd()) { - current_edge_ = *edges_list_state_->GetCurrentAndAdvance(); - return; + { + auto info = dba->ListAllConstraints(); + for (const auto &item : info.existence) { + std::ostringstream os; + DumpExistenceConstraint(&os, dba, item.first, item.second); + stream->Result({TypedValue(os.str())}); + } } - while (!vertices_state_->ReachedEnd()) { - auto vertex = *vertices_state_->GetCurrentAndAdvance(); +#else + for (const auto &item : dba->GetIndicesKeys()) { + std::ostringstream os; + DumpIndexKey(&os, dba, item); + stream->Result({TypedValue(os.str())}); + } + for (const auto &item : dba->ListUniqueConstraints()) { + std::ostringstream os; + DumpUniqueConstraint(&os, dba, item); + stream->Result({TypedValue(os.str())}); + } +#endif + + auto vertices = dba->Vertices(storage::View::OLD); + bool internal_index_created = false; + if (vertices.begin() != vertices.end()) { + std::ostringstream os; + os << "CREATE INDEX ON :" << kInternalVertexLabel << "(" + << kInternalPropertyId << ");"; + stream->Result({TypedValue(os.str())}); + internal_index_created = true; + } + for (const auto &vertex : vertices) { + std::ostringstream os; + DumpVertex(&os, dba, vertex); + stream->Result({TypedValue(os.str())}); + } + for (const auto &vertex : vertices) { auto maybe_edges = vertex.OutEdges(storage::View::OLD); CHECK(maybe_edges.HasValue()) << "Invalid database state!"; - auto &edges = maybe_edges.GetValue(); - // We convert the itertools object to a list of edge accessors here because - // itertools have suspicious object lifetime handling. - std::vector<query::EdgeAccessor> edges_list; - for (auto edge : edges) { - edges_list.push_back(edge); + for (const auto &edge : *maybe_edges) { + std::ostringstream os; + DumpEdge(&os, dba, edge); + stream->Result({TypedValue(os.str())}); } - if (!edges_list.empty()) { - edges_list_state_.emplace(std::move(edges_list)); - current_edge_ = *edges_list_state_->GetCurrentAndAdvance(); - break; + } + if (internal_index_created) { + { + std::ostringstream os; + os << "DROP INDEX ON :" << kInternalVertexLabel << "(" + << kInternalPropertyId << ");"; + stream->Result({TypedValue(os.str())}); + } + { + std::ostringstream os; + os << "MATCH (u) REMOVE u:" << kInternalVertexLabel << ", u." + << kInternalPropertyId << ";"; + stream->Result({TypedValue(os.str())}); } } } -} // namespace database +} // namespace query diff --git a/src/query/dump.hpp b/src/query/dump.hpp new file mode 100644 index 000000000..216916a5c --- /dev/null +++ b/src/query/dump.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include <ostream> + +#include "query/db_accessor.hpp" +#include "query/stream.hpp" + +namespace query { + +void DumpDatabaseToCypherQueries(query::DbAccessor *dba, AnyStream *stream); + +} // namespace query diff --git a/src/query/interpret/awesome_memgraph_functions.cpp b/src/query/interpret/awesome_memgraph_functions.cpp index 16a8981ea..f088f33e1 100644 --- a/src/query/interpret/awesome_memgraph_functions.cpp +++ b/src/query/interpret/awesome_memgraph_functions.cpp @@ -7,9 +7,6 @@ #include <functional> #include <random> -#ifndef MG_SINGLE_NODE_V2 -#include "database/single_node/dump.hpp" -#endif #include "query/db_accessor.hpp" #include "query/exceptions.hpp" #include "query/typed_value.hpp" diff --git a/src/query/interpreter.cpp b/src/query/interpreter.cpp index a035ab49d..ee09b674c 100644 --- a/src/query/interpreter.cpp +++ b/src/query/interpreter.cpp @@ -5,11 +5,11 @@ #include <glog/logging.h> #include "auth/auth.hpp" -#ifndef MG_SINGLE_NODE_HA -#include "database/single_node/dump.hpp" -#endif #include "glue/auth.hpp" #include "glue/communication.hpp" +#ifndef MG_SINGLE_NODE_HA +#include "query/dump.hpp" +#endif #include "query/exceptions.hpp" #include "query/frontend/ast/cypher_main_visitor.hpp" #include "query/frontend/opencypher/parser.hpp" @@ -797,13 +797,7 @@ PreparedQuery PrepareDumpQuery( [interpreter_context](AnyStream *stream) { auto dba = interpreter_context->db->Access(); query::DbAccessor query_dba{&dba}; - std::ostringstream oss; - database::CypherDumpGenerator dump_generator{&query_dba}; - - while (dump_generator.NextQuery(&oss)) { - stream->Result({TypedValue(oss.str())}); - } - + DumpDatabaseToCypherQueries(&query_dba, stream); return QueryHandlerResult::NOTHING; }}; #else diff --git a/src/query/interpreter.hpp b/src/query/interpreter.hpp index ea58f04d7..14d7c747b 100644 --- a/src/query/interpreter.hpp +++ b/src/query/interpreter.hpp @@ -11,6 +11,7 @@ #include "query/frontend/stripped.hpp" #include "query/interpret/frame.hpp" #include "query/plan/operator.hpp" +#include "query/stream.hpp" #include "utils/memory.hpp" #include "utils/skip_list.hpp" #include "utils/spin_lock.hpp" @@ -30,51 +31,6 @@ static constexpr size_t kExecutionMemoryBlockSize = 1U * 1024U * 1024U; enum class QueryHandlerResult { COMMIT, ABORT, NOTHING }; -/** - * `AnyStream` can wrap *any* type implementing the `Stream` concept into a - * single type. - * - * The type erasure technique is used. The original type which an `AnyStream` - * was constructed from is "erased", as `AnyStream` is not a class template and - * doesn't use the type in any way. Client code can then program just for - * `AnyStream`, rather than using static polymorphism to handle any type - * implementing the `Stream` concept. - */ -class AnyStream final { - public: - template <class TStream> - AnyStream(TStream *stream, utils::MemoryResource *memory_resource) - : content_{utils::Allocator<GenericWrapper<TStream>>{memory_resource} - .template new_object<GenericWrapper<TStream>>(stream), - [memory_resource](Wrapper *ptr) { - utils::Allocator<GenericWrapper<TStream>>{memory_resource} - .template delete_object<GenericWrapper<TStream>>( - static_cast<GenericWrapper<TStream> *>(ptr)); - }} {} - - void Result(const std::vector<TypedValue> &values) { - content_->Result(values); - } - - private: - struct Wrapper { - virtual void Result(const std::vector<TypedValue> &values) = 0; - }; - - template <class TStream> - struct GenericWrapper final : public Wrapper { - explicit GenericWrapper(TStream *stream) : stream_{stream} {} - - void Result(const std::vector<TypedValue> &values) override { - stream_->Result(values); - } - - TStream *stream_; - }; - - std::unique_ptr<Wrapper, std::function<void(Wrapper *)>> content_; -}; - /** * A container for data related to the preparation of a query. */ diff --git a/src/query/stream.hpp b/src/query/stream.hpp new file mode 100644 index 000000000..c7d5de6a3 --- /dev/null +++ b/src/query/stream.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include <memory> +#include <vector> + +#include "query/typed_value.hpp" +#include "utils/memory.hpp" + +namespace query { + +/** + * `AnyStream` can wrap *any* type implementing the `Stream` concept into a + * single type. + * + * The type erasure technique is used. The original type which an `AnyStream` + * was constructed from is "erased", as `AnyStream` is not a class template and + * doesn't use the type in any way. Client code can then program just for + * `AnyStream`, rather than using static polymorphism to handle any type + * implementing the `Stream` concept. + */ +class AnyStream final { + public: + template <class TStream> + AnyStream(TStream *stream, utils::MemoryResource *memory_resource) + : content_{utils::Allocator<GenericWrapper<TStream>>{memory_resource} + .template new_object<GenericWrapper<TStream>>(stream), + [memory_resource](Wrapper *ptr) { + utils::Allocator<GenericWrapper<TStream>>{memory_resource} + .template delete_object<GenericWrapper<TStream>>( + static_cast<GenericWrapper<TStream> *>(ptr)); + }} {} + + void Result(const std::vector<TypedValue> &values) { + content_->Result(values); + } + + private: + struct Wrapper { + virtual void Result(const std::vector<TypedValue> &values) = 0; + }; + + template <class TStream> + struct GenericWrapper final : public Wrapper { + explicit GenericWrapper(TStream *stream) : stream_{stream} {} + + void Result(const std::vector<TypedValue> &values) override { + stream_->Result(values); + } + + TStream *stream_; + }; + + std::unique_ptr<Wrapper, std::function<void(Wrapper *)>> content_; +}; + +} // namespace query diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 3b85418bb..d4c107f19 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -36,9 +36,6 @@ target_link_libraries(${test_prefix}concurrent_map mg-single-node kvstore_dummy_ add_unit_test(cypher_main_visitor.cpp) target_link_libraries(${test_prefix}cypher_main_visitor mg-single-node kvstore_dummy_lib) -add_unit_test(database_dump.cpp) -target_link_libraries(${test_prefix}database_dump mg-single-node kvstore_dummy_lib) - add_unit_test(database_key_index.cpp) target_link_libraries(${test_prefix}database_key_index mg-single-node kvstore_dummy_lib) @@ -117,6 +114,9 @@ target_link_libraries(${test_prefix}query_expression_evaluator mg-single-node kv add_unit_test(plan_pretty_print.cpp) target_link_libraries(${test_prefix}plan_pretty_print mg-single-node kvstore_dummy_lib) +add_unit_test(query_dump.cpp) +target_link_libraries(${test_prefix}query_dump mg-single-node kvstore_dummy_lib) + add_unit_test(query_pretty_print.cpp) target_link_libraries(${test_prefix}query_pretty_print mg-single-node kvstore_dummy_lib) diff --git a/tests/unit/database_dump.cpp b/tests/unit/database_dump.cpp deleted file mode 100644 index 2312c1ca9..000000000 --- a/tests/unit/database_dump.cpp +++ /dev/null @@ -1,596 +0,0 @@ -#include <gtest/gtest.h> - -#include <map> -#include <set> -#include <vector> - -#include <glog/logging.h> - -#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<std::string> labels; - std::map<std::string, PropertyValue> props; - }; - - struct Edge { - int64_t from, to; - std::string edge_type; - std::map<std::string, PropertyValue> props; - }; - - struct IndexKey { - std::string label; - std::string property; - }; - - struct UniqueConstraint { - std::string label; - std::set<std::string> props; - }; - - std::set<Vertex> vertices; - std::set<Edge> edges; - std::set<IndexKey> indices; - std::set<UniqueConstraint> 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: - DatabaseEnvironment() - : interpreter_context_{&db_}, interpreter_{&interpreter_context_} {} - - GraphDbAccessor Access() { return db_.Access(); } - - DatabaseState GetState() { - // Capture all vertices - std::map<storage::Gid, int64_t> gid_mapping; - std::set<DatabaseState::Vertex> vertices; - auto dba = db_.Access(); - for (const auto &vertex : dba.Vertices(false)) { - std::set<std::string> labels; - for (const auto &label : vertex.labels()) { - labels.insert(dba.LabelName(label)); - } - std::map<std::string, PropertyValue> 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<DatabaseState::Edge> edges; - for (const auto &edge : dba.Edges(false)) { - const auto &edge_type_name = dba.EdgeTypeName(edge.EdgeType()); - std::map<std::string, PropertyValue> 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<DatabaseState::IndexKey> indices; - for (const auto &key : dba.GetIndicesKeys()) { - indices.insert( - {dba.LabelName(key.label_), dba.PropertyName(key.property_)}); - } - - // Capture all unique constraints - std::set<DatabaseState::UniqueConstraint> constraints; - for (const auto &constraint : dba.ListUniqueConstraints()) { - std::set<std::string> props; - for (const auto &prop : constraint.properties) { - props.insert(dba.PropertyName(prop)); - } - constraints.insert({dba.LabelName(constraint.label), props}); - } - - return {vertices, edges, indices, constraints}; - } - - /** - * Execute the given query and commit the transaction. - * - * Return the query stream. - */ - auto Execute(const std::string &query) { - ResultStreamFaker stream; - - auto [header, _] = interpreter_.Prepare(query, {}); - stream.Header(header); - auto summary = interpreter_.PullAll(&stream); - stream.Summary(summary); - - return stream; - } - - private: - database::GraphDb db_; - query::InterpreterContext interpreter_context_; - query::Interpreter interpreter_; -}; - -VertexAccessor CreateVertex(GraphDbAccessor *dba, - const std::vector<std::string> &labels, - const std::map<std::string, PropertyValue> &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<std::string, PropertyValue> &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); - dba.BuildIndex(dba.Label("Label1"), dba.Property("prop")); - dba.BuildIndex(dba.Label("Label2"), dba.Property("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); - dba.BuildUniqueConstraint(dba.Label("Label"), {dba.Property("prop")}); - // Create one with multiple properties. - dba.BuildUniqueConstraint(dba.Label("Label"), - {dba.Property("prop1"), dba.Property("prop2")}); - 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<std::string, PropertyValue> 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()) { - db_dump.Execute(cmd); - } - } - 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 - dba.BuildUniqueConstraint(dba.Label("Person"), {dba.Property("name")}); - dba.BuildIndex(dba.Label("Person"), dba.Property("id")); - dba.BuildIndex(dba.Label("Person"), dba.Property("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()) { - db_dump.Execute(cmd); - } - } - 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 stream = db.Execute("DUMP DATABASE"); - EXPECT_EQ(stream.GetResults().size(), 4U); - ASSERT_EQ(stream.GetHeader().size(), 1U); - EXPECT_EQ(stream.GetHeader()[0], "QUERY"); - } -} diff --git a/tests/unit/query_dump.cpp b/tests/unit/query_dump.cpp new file mode 100644 index 000000000..851870303 --- /dev/null +++ b/tests/unit/query_dump.cpp @@ -0,0 +1,632 @@ +#include <gtest/gtest.h> + +#include <map> +#include <set> +#include <vector> + +#include <glog/logging.h> + +#include "communication/result_stream_faker.hpp" +#include "database/graph_db.hpp" +#include "database/graph_db_accessor.hpp" +#include "query/dump.hpp" +#include "query/interpreter.hpp" +#include "query/typed_value.hpp" +#include "storage/common/types/property_value.hpp" + +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<std::string> labels; + std::map<std::string, PropertyValue> props; + }; + + struct Edge { + int64_t from, to; + std::string edge_type; + std::map<std::string, PropertyValue> props; + }; + + struct IndexKey { + std::string label; + std::string property; + }; + + struct UniqueConstraint { + std::string label; + std::set<std::string> props; + }; + + std::set<Vertex> vertices; + std::set<Edge> edges; + std::set<IndexKey> indices; + std::set<UniqueConstraint> 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; +} + +DatabaseState GetState(database::GraphDb *db) { + // Capture all vertices + std::map<storage::Gid, int64_t> gid_mapping; + std::set<DatabaseState::Vertex> vertices; + auto dba = db->Access(); + for (const auto &vertex : dba.Vertices(false)) { + std::set<std::string> labels; + for (const auto &label : vertex.labels()) { + labels.insert(dba.LabelName(label)); + } + std::map<std::string, PropertyValue> 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<DatabaseState::Edge> edges; + for (const auto &edge : dba.Edges(false)) { + const auto &edge_type_name = dba.EdgeTypeName(edge.EdgeType()); + std::map<std::string, PropertyValue> 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<DatabaseState::IndexKey> indices; + for (const auto &key : dba.GetIndicesKeys()) { + indices.insert( + {dba.LabelName(key.label_), dba.PropertyName(key.property_)}); + } + + // Capture all unique constraints + std::set<DatabaseState::UniqueConstraint> constraints; + for (const auto &constraint : dba.ListUniqueConstraints()) { + std::set<std::string> props; + for (const auto &prop : constraint.properties) { + props.insert(dba.PropertyName(prop)); + } + constraints.insert({dba.LabelName(constraint.label), props}); + } + + return {vertices, edges, indices, constraints}; +} + +auto Execute(database::GraphDb *db, const std::string &query) { + query::InterpreterContext context(db); + query::Interpreter interpreter(&context); + ResultStreamFaker stream; + + auto [header, _] = interpreter.Prepare(query, {}); + stream.Header(header); + auto summary = interpreter.PullAll(&stream); + stream.Summary(summary); + + return stream; +} + +VertexAccessor CreateVertex(GraphDbAccessor *dba, + const std::vector<std::string> &labels, + const std::map<std::string, PropertyValue> &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<std::string, PropertyValue> &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; +} + +template <class... TArgs> +void VerifyQueries( + const std::vector<std::vector<communication::bolt::Value>> &results, + TArgs &&... args) { + std::vector<std::string> expected{std::forward<TArgs>(args)...}; + std::vector<std::string> 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) { + database::GraphDb db; + ResultStreamFaker stream; + 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) { + database::GraphDb db; + { + auto dba = db.Access(); + CreateVertex(&dba, {}, {}, false); + dba.Commit(); + } + + { + ResultStreamFaker stream; + 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) { + database::GraphDb db; + { + auto dba = db.Access(); + CreateVertex(&dba, {"Label1"}, {}, false); + dba.Commit(); + } + + { + ResultStreamFaker stream; + 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) { + database::GraphDb db; + { + auto dba = db.Access(); + CreateVertex(&dba, {"Label1", "Label2"}, {}, false); + dba.Commit(); + } + + { + ResultStreamFaker stream; + 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) { + database::GraphDb db; + { + auto dba = db.Access(); + CreateVertex(&dba, {}, {{"prop", PropertyValue(42)}}, false); + dba.Commit(); + } + + { + ResultStreamFaker stream; + 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) { + database::GraphDb db; + { + auto dba = db.Access(); + CreateVertex(&dba, {}, {}, false); + CreateVertex(&dba, {}, {}, false); + CreateVertex(&dba, {}, {}, false); + dba.Commit(); + } + + { + ResultStreamFaker stream; + 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) { + database::GraphDb db; + { + auto dba = db.Access(); + auto u = CreateVertex(&dba, {}, {}, false); + auto v = CreateVertex(&dba, {}, {}, false); + CreateEdge(&dba, u, v, "EdgeType", {}, false); + dba.Commit(); + } + + { + ResultStreamFaker stream; + 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) { + database::GraphDb 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(); + } + + { + ResultStreamFaker stream; + 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) { + database::GraphDb 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(); + } + + { + ResultStreamFaker stream; + 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) { + database::GraphDb db; + { + auto dba = db.Access(); + CreateVertex(&dba, {"Label1", "Label2"}, {{"p", PropertyValue(1)}}, false); + dba.BuildIndex(dba.Label("Label1"), dba.Property("prop")); + dba.BuildIndex(dba.Label("Label2"), dba.Property("prop")); + dba.Commit(); + } + + { + ResultStreamFaker stream; + 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, UniqueConstraints) { + database::GraphDb db; + { + auto dba = db.Access(); + CreateVertex(&dba, {"Label"}, {{"prop", PropertyValue(1)}}, false); + dba.BuildUniqueConstraint(dba.Label("Label"), {dba.Property("prop")}); + // Create one with multiple properties. + dba.BuildUniqueConstraint(dba.Label("Label"), + {dba.Property("prop1"), dba.Property("prop2")}); + dba.Commit(); + } + + { + ResultStreamFaker stream; + 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 u.prop IS UNIQUE;", + "CREATE CONSTRAINT ON (u:Label) ASSERT u.prop1, u.prop2 IS UNIQUE;", + kCreateInternalIndex, + "CREATE (:__mg_vertex__:Label {__mg_id__: 0, prop: 1});", + kDropInternalIndex, kRemoveInternalLabelProperty); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(DumpTest, CheckStateVertexWithMultipleProperties) { + database::GraphDb db; + { + auto dba = db.Access(); + std::map<std::string, PropertyValue> prop1 = { + {"nested1", PropertyValue(1337)}, {"nested2", PropertyValue(3.14)}}; + CreateVertex( + &dba, {"Label1", "Label2"}, + {{"prop1", PropertyValue(prop1)}, {"prop2", PropertyValue("$'\t'")}}); + dba.Commit(); + } + + database::GraphDb db_dump; + { + ResultStreamFaker stream; + 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) { + database::GraphDb 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")}}); + dba.Commit(); + } + { + auto dba = db.Access(); + dba.BuildUniqueConstraint(dba.Label("Person"), {dba.Property("name")}); + dba.BuildIndex(dba.Label("Person"), dba.Property("id")); + dba.BuildIndex(dba.Label("Person"), dba.Property("unexisting_property")); + dba.Commit(); + } + + const auto &db_initial_state = GetState(&db); + database::GraphDb db_dump; + { + ResultStreamFaker stream; + 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) { + database::GraphDb db; + { + auto dba = db.Access(); + CreateVertex(&dba, {}, {}, false); + dba.Commit(); + } + + { + 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__;"); + } +}