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:
Matej Ferencevic 2019-11-12 10:47:02 +01:00
parent e4edb2be99
commit 329818b1b0
11 changed files with 799 additions and 861 deletions

View File

@ -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

View File

@ -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

View File

@ -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
View 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

View File

@ -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"

View File

@ -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

View File

@ -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
View 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

View File

@ -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)

View File

@ -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
View 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__;");
}
}