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
This commit is contained in:
parent
e4edb2be99
commit
329818b1b0
@ -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
|
||||
|
@ -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
|
@ -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
|
12
src/query/dump.hpp
Normal file
12
src/query/dump.hpp
Normal file
@ -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
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
*/
|
||||
|
56
src/query/stream.hpp
Normal file
56
src/query/stream.hpp
Normal file
@ -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
|
@ -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)
|
||||
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
632
tests/unit/query_dump.cpp
Normal file
632
tests/unit/query_dump.cpp
Normal file
@ -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__;");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user