memgraph/tests/unit/query_dump.cpp
Matej Ferencevic 6628d20e5a Make DUMP DATABASE work correctly in explicit transactions
Summary:
`DUMP DATABASE` used a separate transaction to read database data. That
wouldn't be an issue if the query was correctly disallowed in multicommand
transactions. Because it was allowed the output wasn't transactionally correct.
Instead of disabling `DUMP DATABASE` in multicommand transactions this change
fixes it so that it works properly in multicommand transactions.

Reviewers: buda

Reviewed By: buda

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D2781
2020-06-08 15:56:16 +02:00

875 lines
29 KiB
C++

#include <gtest/gtest.h>
#include <map>
#include <set>
#include <vector>
#include <glog/logging.h>
#include "communication/result_stream_faker.hpp"
#include "query/dump.hpp"
#include "query/interpreter.hpp"
#include "query/typed_value.hpp"
#include "storage/v2/storage.hpp"
const char *kPropertyId = "property_id";
const char *kCreateInternalIndex = "CREATE INDEX ON :__mg_vertex__(__mg_id__);";
const char *kDropInternalIndex = "DROP INDEX ON :__mg_vertex__(__mg_id__);";
const char *kRemoveInternalLabelProperty =
"MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__;";
// A helper struct that contains info about database that is used to compare
// two databases (to check if their states are the same). It is assumed that
// each vertex and each edge have unique integer property under key
// `kPropertyId`.
struct DatabaseState {
struct Vertex {
int64_t id;
std::set<std::string> labels;
std::map<std::string, storage::PropertyValue> props;
};
struct Edge {
int64_t from, to;
std::string edge_type;
std::map<std::string, storage::PropertyValue> props;
};
struct LabelItem {
std::string label;
};
struct LabelPropertyItem {
std::string label;
std::string property;
};
struct LabelPropertiesItem {
std::string label;
std::set<std::string> properties;
};
std::set<Vertex> vertices;
std::set<Edge> edges;
std::set<LabelItem> label_indices;
std::set<LabelPropertyItem> label_property_indices;
std::set<LabelPropertyItem> existence_constraints;
std::set<LabelPropertiesItem> unique_constraints;
};
bool operator<(const DatabaseState::Vertex &first,
const DatabaseState::Vertex &second) {
if (first.id != second.id) return first.id < second.id;
if (first.labels != second.labels) return first.labels < second.labels;
return first.props < second.props;
}
bool operator<(const DatabaseState::Edge &first,
const DatabaseState::Edge &second) {
if (first.from != second.from) return first.from < second.from;
if (first.to != second.to) return first.to < second.to;
if (first.edge_type != second.edge_type)
return first.edge_type < second.edge_type;
return first.props < second.props;
}
bool operator<(const DatabaseState::LabelItem &first,
const DatabaseState::LabelItem &second) {
return first.label < second.label;
}
bool operator<(const DatabaseState::LabelPropertyItem &first,
const DatabaseState::LabelPropertyItem &second) {
if (first.label != second.label) return first.label < second.label;
return first.property < second.property;
}
bool operator<(const DatabaseState::LabelPropertiesItem &first,
const DatabaseState::LabelPropertiesItem &second) {
if (first.label != second.label) return first.label < second.label;
return first.properties < second.properties;
}
bool operator==(const DatabaseState::Vertex &first,
const DatabaseState::Vertex &second) {
return first.id == second.id && first.labels == second.labels &&
first.props == second.props;
}
bool operator==(const DatabaseState::Edge &first,
const DatabaseState::Edge &second) {
return first.from == second.from && first.to == second.to &&
first.edge_type == second.edge_type && first.props == second.props;
}
bool operator==(const DatabaseState::LabelItem &first,
const DatabaseState::LabelItem &second) {
return first.label == second.label;
}
bool operator==(const DatabaseState::LabelPropertyItem &first,
const DatabaseState::LabelPropertyItem &second) {
return first.label == second.label && first.property == second.property;
}
bool operator==(const DatabaseState::LabelPropertiesItem &first,
const DatabaseState::LabelPropertiesItem &second) {
return first.label == second.label && first.properties == second.properties;
}
bool operator==(const DatabaseState &first, const DatabaseState &second) {
return first.vertices == second.vertices && first.edges == second.edges &&
first.label_indices == second.label_indices &&
first.label_property_indices == second.label_property_indices &&
first.existence_constraints == second.existence_constraints &&
first.unique_constraints == second.unique_constraints;
}
DatabaseState GetState(storage::Storage *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(storage::View::NEW)) {
std::set<std::string> labels;
auto maybe_labels = vertex.Labels(storage::View::NEW);
CHECK(maybe_labels.HasValue());
for (const auto &label : *maybe_labels) {
labels.insert(dba.LabelToName(label));
}
std::map<std::string, storage::PropertyValue> props;
auto maybe_properties = vertex.Properties(storage::View::NEW);
CHECK(maybe_properties.HasValue());
for (const auto &kv : *maybe_properties) {
props.emplace(dba.PropertyToName(kv.first), kv.second);
}
CHECK(props.count(kPropertyId) == 1);
const auto id = props[kPropertyId].ValueInt();
gid_mapping[vertex.Gid()] = id;
vertices.insert({id, labels, props});
}
// Capture all edges
std::set<DatabaseState::Edge> edges;
for (const auto &vertex : dba.Vertices(storage::View::NEW)) {
auto maybe_edges = vertex.OutEdges(storage::View::NEW);
CHECK(maybe_edges.HasValue());
for (const auto &edge : *maybe_edges) {
const auto &edge_type_name = dba.EdgeTypeToName(edge.EdgeType());
std::map<std::string, storage::PropertyValue> props;
auto maybe_properties = edge.Properties(storage::View::NEW);
CHECK(maybe_properties.HasValue());
for (const auto &kv : *maybe_properties) {
props.emplace(dba.PropertyToName(kv.first), kv.second);
}
const auto from = gid_mapping[edge.FromVertex().Gid()];
const auto to = gid_mapping[edge.ToVertex().Gid()];
edges.insert({from, to, edge_type_name, props});
}
}
// Capture all indices
std::set<DatabaseState::LabelItem> label_indices;
std::set<DatabaseState::LabelPropertyItem> label_property_indices;
{
auto info = dba.ListAllIndices();
for (const auto &item : info.label) {
label_indices.insert({dba.LabelToName(item)});
}
for (const auto &item : info.label_property) {
label_property_indices.insert(
{dba.LabelToName(item.first), dba.PropertyToName(item.second)});
}
}
// Capture all constraints
std::set<DatabaseState::LabelPropertyItem> existence_constraints;
std::set<DatabaseState::LabelPropertiesItem> unique_constraints;
{
auto info = dba.ListAllConstraints();
for (const auto &item : info.existence) {
existence_constraints.insert(
{dba.LabelToName(item.first), dba.PropertyToName(item.second)});
}
for (const auto &item : info.unique) {
std::set<std::string> properties;
for (const auto &property : item.second) {
properties.insert(dba.PropertyToName(property));
}
unique_constraints.insert(
{dba.LabelToName(item.first), std::move(properties)});
}
}
return {vertices,
edges,
label_indices,
label_property_indices,
existence_constraints,
unique_constraints};
}
auto Execute(storage::Storage *db, const std::string &query) {
query::InterpreterContext context(db);
query::Interpreter interpreter(&context);
ResultStreamFaker stream(db);
auto [header, _] = interpreter.Prepare(query, {});
stream.Header(header);
auto summary = interpreter.PullAll(&stream);
stream.Summary(summary);
return stream;
}
storage::VertexAccessor CreateVertex(
storage::Storage::Accessor *dba, const std::vector<std::string> &labels,
const std::map<std::string, storage::PropertyValue> &props,
bool add_property_id = true) {
CHECK(dba);
auto vertex = dba->CreateVertex();
for (const auto &label_name : labels) {
CHECK(vertex.AddLabel(dba->NameToLabel(label_name)).HasValue());
}
for (const auto &kv : props) {
CHECK(vertex.SetProperty(dba->NameToProperty(kv.first), kv.second)
.HasValue());
}
if (add_property_id) {
CHECK(vertex
.SetProperty(dba->NameToProperty(kPropertyId),
storage::PropertyValue(vertex.Gid().AsInt()))
.HasValue());
}
return vertex;
}
storage::EdgeAccessor CreateEdge(
storage::Storage::Accessor *dba, storage::VertexAccessor *from,
storage::VertexAccessor *to, const std::string &edge_type_name,
const std::map<std::string, storage::PropertyValue> &props,
bool add_property_id = true) {
CHECK(dba);
auto edge = dba->CreateEdge(from, to, dba->NameToEdgeType(edge_type_name));
CHECK(edge.HasValue());
for (const auto &kv : props) {
CHECK(
edge->SetProperty(dba->NameToProperty(kv.first), kv.second).HasValue());
}
if (add_property_id) {
CHECK(edge->SetProperty(dba->NameToProperty(kPropertyId),
storage::PropertyValue(edge->Gid().AsInt()))
.HasValue());
}
return *edge;
}
template <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) {
storage::Storage db;
ResultStreamFaker stream(&db);
query::AnyStream query_stream(&stream, utils::NewDeleteResource());
{
auto acc = db.Access();
query::DbAccessor dba(&acc);
query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
ASSERT_EQ(stream.GetResults().size(), 0);
}
// NOLINTNEXTLINE(hicpp-special-member-functions)
TEST(DumpTest, SingleVertex) {
storage::Storage db;
{
auto dba = db.Access();
CreateVertex(&dba, {}, {}, false);
ASSERT_FALSE(dba.Commit().HasError());
}
{
ResultStreamFaker stream(&db);
query::AnyStream query_stream(&stream, utils::NewDeleteResource());
{
auto acc = db.Access();
query::DbAccessor dba(&acc);
query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
VerifyQueries(stream.GetResults(), kCreateInternalIndex,
"CREATE (:__mg_vertex__ {__mg_id__: 0});", kDropInternalIndex,
kRemoveInternalLabelProperty);
}
}
// NOLINTNEXTLINE(hicpp-special-member-functions)
TEST(DumpTest, VertexWithSingleLabel) {
storage::Storage db;
{
auto dba = db.Access();
CreateVertex(&dba, {"Label1"}, {}, false);
ASSERT_FALSE(dba.Commit().HasError());
}
{
ResultStreamFaker stream(&db);
query::AnyStream query_stream(&stream, utils::NewDeleteResource());
{
auto acc = db.Access();
query::DbAccessor dba(&acc);
query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
VerifyQueries(stream.GetResults(), kCreateInternalIndex,
"CREATE (:__mg_vertex__:`Label1` {__mg_id__: 0});",
kDropInternalIndex, kRemoveInternalLabelProperty);
}
}
// NOLINTNEXTLINE(hicpp-special-member-functions)
TEST(DumpTest, VertexWithMultipleLabels) {
storage::Storage db;
{
auto dba = db.Access();
CreateVertex(&dba, {"Label1", "Label 2"}, {}, false);
ASSERT_FALSE(dba.Commit().HasError());
}
{
ResultStreamFaker stream(&db);
query::AnyStream query_stream(&stream, utils::NewDeleteResource());
{
auto acc = db.Access();
query::DbAccessor dba(&acc);
query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
VerifyQueries(stream.GetResults(), kCreateInternalIndex,
"CREATE (:__mg_vertex__:`Label1`:`Label 2` {__mg_id__: 0});",
kDropInternalIndex, kRemoveInternalLabelProperty);
}
}
// NOLINTNEXTLINE(hicpp-special-member-functions)
TEST(DumpTest, VertexWithSingleProperty) {
storage::Storage db;
{
auto dba = db.Access();
CreateVertex(&dba, {}, {{"prop", storage::PropertyValue(42)}}, false);
ASSERT_FALSE(dba.Commit().HasError());
}
{
ResultStreamFaker stream(&db);
query::AnyStream query_stream(&stream, utils::NewDeleteResource());
{
auto acc = db.Access();
query::DbAccessor dba(&acc);
query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
VerifyQueries(stream.GetResults(), kCreateInternalIndex,
"CREATE (:__mg_vertex__ {__mg_id__: 0, `prop`: 42});",
kDropInternalIndex, kRemoveInternalLabelProperty);
}
}
// NOLINTNEXTLINE(hicpp-special-member-functions)
TEST(DumpTest, MultipleVertices) {
storage::Storage db;
{
auto dba = db.Access();
CreateVertex(&dba, {}, {}, false);
CreateVertex(&dba, {}, {}, false);
CreateVertex(&dba, {}, {}, false);
ASSERT_FALSE(dba.Commit().HasError());
}
{
ResultStreamFaker stream(&db);
query::AnyStream query_stream(&stream, utils::NewDeleteResource());
{
auto acc = db.Access();
query::DbAccessor dba(&acc);
query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
VerifyQueries(stream.GetResults(), kCreateInternalIndex,
"CREATE (:__mg_vertex__ {__mg_id__: 0});",
"CREATE (:__mg_vertex__ {__mg_id__: 1});",
"CREATE (:__mg_vertex__ {__mg_id__: 2});", kDropInternalIndex,
kRemoveInternalLabelProperty);
}
}
TEST(DumpTest, PropertyValue) {
storage::Storage db;
{
auto dba = db.Access();
auto null_value = storage::PropertyValue();
auto int_value = storage::PropertyValue(13);
auto bool_value = storage::PropertyValue(true);
auto double_value = storage::PropertyValue(-1.2);
auto str_value = storage::PropertyValue("hello 'world'");
auto map_value = storage::PropertyValue(
{{"prop 1", int_value}, {"prop`2`", bool_value}});
auto list_value =
storage::PropertyValue({map_value, null_value, double_value});
CreateVertex(&dba, {}, {{"p1", list_value}, {"p2", str_value}}, false);
ASSERT_FALSE(dba.Commit().HasError());
}
{
ResultStreamFaker stream(&db);
query::AnyStream query_stream(&stream, utils::NewDeleteResource());
{
auto acc = db.Access();
query::DbAccessor dba(&acc);
query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
VerifyQueries(
stream.GetResults(), kCreateInternalIndex,
"CREATE (:__mg_vertex__ {__mg_id__: 0, `p1`: [{`prop 1`: 13, "
"`prop``2```: true}, Null, -1.2], `p2`: \"hello \\'world\\'\"});",
kDropInternalIndex, kRemoveInternalLabelProperty);
}
}
// NOLINTNEXTLINE(hicpp-special-member-functions)
TEST(DumpTest, SingleEdge) {
storage::Storage db;
{
auto dba = db.Access();
auto u = CreateVertex(&dba, {}, {}, false);
auto v = CreateVertex(&dba, {}, {}, false);
CreateEdge(&dba, &u, &v, "EdgeType", {}, false);
ASSERT_FALSE(dba.Commit().HasError());
}
{
ResultStreamFaker stream(&db);
query::AnyStream query_stream(&stream, utils::NewDeleteResource());
{
auto acc = db.Access();
query::DbAccessor dba(&acc);
query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
VerifyQueries(
stream.GetResults(), kCreateInternalIndex,
"CREATE (:__mg_vertex__ {__mg_id__: 0});",
"CREATE (:__mg_vertex__ {__mg_id__: 1});",
"MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 0 AND "
"v.__mg_id__ = 1 CREATE (u)-[:`EdgeType`]->(v);",
kDropInternalIndex, kRemoveInternalLabelProperty);
}
}
// NOLINTNEXTLINE(hicpp-special-member-functions)
TEST(DumpTest, MultipleEdges) {
storage::Storage db;
{
auto dba = db.Access();
auto u = CreateVertex(&dba, {}, {}, false);
auto v = CreateVertex(&dba, {}, {}, false);
auto w = CreateVertex(&dba, {}, {}, false);
CreateEdge(&dba, &u, &v, "EdgeType", {}, false);
CreateEdge(&dba, &v, &u, "EdgeType 2", {}, false);
CreateEdge(&dba, &v, &w, "EdgeType `!\"", {}, false);
ASSERT_FALSE(dba.Commit().HasError());
}
{
ResultStreamFaker stream(&db);
query::AnyStream query_stream(&stream, utils::NewDeleteResource());
{
auto acc = db.Access();
query::DbAccessor dba(&acc);
query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
VerifyQueries(
stream.GetResults(), kCreateInternalIndex,
"CREATE (:__mg_vertex__ {__mg_id__: 0});",
"CREATE (:__mg_vertex__ {__mg_id__: 1});",
"CREATE (:__mg_vertex__ {__mg_id__: 2});",
"MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 0 AND "
"v.__mg_id__ = 1 CREATE (u)-[:`EdgeType`]->(v);",
"MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 1 AND "
"v.__mg_id__ = 0 CREATE (u)-[:`EdgeType 2`]->(v);",
"MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 1 AND "
"v.__mg_id__ = 2 CREATE (u)-[:`EdgeType ``!\"`]->(v);",
kDropInternalIndex, kRemoveInternalLabelProperty);
}
}
// NOLINTNEXTLINE(hicpp-special-member-functions)
TEST(DumpTest, EdgeWithProperties) {
storage::Storage db;
{
auto dba = db.Access();
auto u = CreateVertex(&dba, {}, {}, false);
auto v = CreateVertex(&dba, {}, {}, false);
CreateEdge(&dba, &u, &v, "EdgeType", {{"prop", storage::PropertyValue(13)}},
false);
ASSERT_FALSE(dba.Commit().HasError());
}
{
ResultStreamFaker stream(&db);
query::AnyStream query_stream(&stream, utils::NewDeleteResource());
{
auto acc = db.Access();
query::DbAccessor dba(&acc);
query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
VerifyQueries(
stream.GetResults(), kCreateInternalIndex,
"CREATE (:__mg_vertex__ {__mg_id__: 0});",
"CREATE (:__mg_vertex__ {__mg_id__: 1});",
"MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 0 AND "
"v.__mg_id__ = 1 CREATE (u)-[:`EdgeType` {`prop`: 13}]->(v);",
kDropInternalIndex, kRemoveInternalLabelProperty);
}
}
// NOLINTNEXTLINE(hicpp-special-member-functions)
TEST(DumpTest, IndicesKeys) {
storage::Storage db;
{
auto dba = db.Access();
CreateVertex(&dba, {"Label1", "Label 2"},
{{"p", storage::PropertyValue(1)}}, false);
ASSERT_FALSE(dba.Commit().HasError());
}
ASSERT_TRUE(
db.CreateIndex(db.NameToLabel("Label1"), db.NameToProperty("prop")));
ASSERT_TRUE(
db.CreateIndex(db.NameToLabel("Label 2"), db.NameToProperty("prop `")));
{
ResultStreamFaker stream(&db);
query::AnyStream query_stream(&stream, utils::NewDeleteResource());
{
auto acc = db.Access();
query::DbAccessor dba(&acc);
query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
VerifyQueries(
stream.GetResults(), "CREATE INDEX ON :`Label1`(`prop`);",
"CREATE INDEX ON :`Label 2`(`prop ```);", kCreateInternalIndex,
"CREATE (:__mg_vertex__:`Label1`:`Label 2` {__mg_id__: 0, `p`: 1});",
kDropInternalIndex, kRemoveInternalLabelProperty);
}
}
// NOLINTNEXTLINE(hicpp-special-member-functions)
TEST(DumpTest, ExistenceConstraints) {
storage::Storage db;
{
auto dba = db.Access();
CreateVertex(&dba, {"L`abel 1"}, {{"prop", storage::PropertyValue(1)}},
false);
ASSERT_FALSE(dba.Commit().HasError());
}
{
auto res = db.CreateExistenceConstraint(db.NameToLabel("L`abel 1"),
db.NameToProperty("prop"));
ASSERT_TRUE(res.HasValue());
ASSERT_TRUE(res.GetValue());
}
{
ResultStreamFaker stream(&db);
query::AnyStream query_stream(&stream, utils::NewDeleteResource());
{
auto acc = db.Access();
query::DbAccessor dba(&acc);
query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
VerifyQueries(
stream.GetResults(),
"CREATE CONSTRAINT ON (u:`L``abel 1`) ASSERT EXISTS (u.`prop`);",
kCreateInternalIndex,
"CREATE (:__mg_vertex__:`L``abel 1` {__mg_id__: 0, `prop`: 1});",
kDropInternalIndex, kRemoveInternalLabelProperty);
}
}
TEST(DumpTest, UniqueConstraints) {
storage::Storage db;
{
auto dba = db.Access();
CreateVertex(&dba, {"Label"},
{{"prop", storage::PropertyValue(1)},
{"prop2", storage::PropertyValue(2)}},
false);
CreateVertex(&dba, {"Label"},
{{"prop", storage::PropertyValue(2)},
{"prop2", storage::PropertyValue(2)}},
false);
ASSERT_FALSE(dba.Commit().HasError());
}
{
auto res = db.CreateUniqueConstraint(
db.NameToLabel("Label"),
{db.NameToProperty("prop"), db.NameToProperty("prop2")});
ASSERT_TRUE(res.HasValue());
ASSERT_EQ(res.GetValue(),
storage::UniqueConstraints::CreationStatus::SUCCESS);
}
{
ResultStreamFaker stream(&db);
query::AnyStream query_stream(&stream, utils::NewDeleteResource());
{
auto acc = db.Access();
query::DbAccessor dba(&acc);
query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
VerifyQueries(stream.GetResults(),
"CREATE CONSTRAINT ON (u:`Label`) ASSERT u.`prop`, u.`prop2` "
"IS UNIQUE;",
kCreateInternalIndex,
"CREATE (:__mg_vertex__:`Label` {__mg_id__: 0, `prop`: 1, "
"`prop2`: 2});",
"CREATE (:__mg_vertex__:`Label` {__mg_id__: 1, `prop`: 2, "
"`prop2`: 2});",
kDropInternalIndex, kRemoveInternalLabelProperty);
}
}
// NOLINTNEXTLINE(hicpp-special-member-functions)
TEST(DumpTest, CheckStateVertexWithMultipleProperties) {
storage::Storage db;
{
auto dba = db.Access();
std::map<std::string, storage::PropertyValue> prop1 = {
{"nested1", storage::PropertyValue(1337)},
{"nested2", storage::PropertyValue(3.14)}};
CreateVertex(&dba, {"Label1", "Label2"},
{{"prop1", storage::PropertyValue(prop1)},
{"prop2", storage::PropertyValue("$'\t'")}});
ASSERT_FALSE(dba.Commit().HasError());
}
storage::Storage db_dump;
{
ResultStreamFaker stream(&db);
query::AnyStream query_stream(&stream, utils::NewDeleteResource());
{
auto acc = db.Access();
query::DbAccessor dba(&acc);
query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
const auto &results = stream.GetResults();
ASSERT_GE(results.size(), 1);
for (const auto &item : results) {
ASSERT_EQ(item.size(), 1);
ASSERT_TRUE(item[0].IsString());
Execute(&db_dump, item[0].ValueString());
}
}
ASSERT_EQ(GetState(&db), GetState(&db_dump));
}
// NOLINTNEXTLINE(hicpp-special-member-functions)
TEST(DumpTest, CheckStateSimpleGraph) {
storage::Storage db;
{
auto dba = db.Access();
auto u = CreateVertex(&dba, {"Person"},
{{"name", storage::PropertyValue("Ivan")}});
auto v = CreateVertex(&dba, {"Person"},
{{"name", storage::PropertyValue("Josko")}});
auto w = CreateVertex(&dba, {"Person"},
{{"name", storage::PropertyValue("Bosko")},
{"id", storage::PropertyValue(0)}});
auto z = CreateVertex(&dba, {"Person"},
{{"name", storage::PropertyValue("Buha")},
{"id", storage::PropertyValue(1)}});
CreateEdge(&dba, &u, &v, "Knows", {});
CreateEdge(&dba, &v, &w, "Knows",
{{"how_long", storage::PropertyValue(5)}});
CreateEdge(&dba, &w, &u, "Knows",
{{"how", storage::PropertyValue("distant past")}});
CreateEdge(&dba, &v, &u, "Knows", {});
CreateEdge(&dba, &v, &u, "Likes", {});
CreateEdge(&dba, &z, &u, "Knows", {});
CreateEdge(&dba, &w, &z, "Knows",
{{"how", storage::PropertyValue("school")}});
CreateEdge(&dba, &w, &z, "Likes",
{{"how", storage::PropertyValue("very much")}});
ASSERT_FALSE(dba.Commit().HasError());
}
{
auto ret = db.CreateExistenceConstraint(db.NameToLabel("Person"),
db.NameToProperty("name"));
ASSERT_TRUE(ret.HasValue());
ASSERT_TRUE(ret.GetValue());
}
{
auto ret = db.CreateUniqueConstraint(db.NameToLabel("Person"),
{db.NameToProperty("name")});
ASSERT_TRUE(ret.HasValue());
ASSERT_EQ(ret.GetValue(),
storage::UniqueConstraints::CreationStatus::SUCCESS);
}
ASSERT_TRUE(
db.CreateIndex(db.NameToLabel("Person"), db.NameToProperty("id")));
ASSERT_TRUE(db.CreateIndex(db.NameToLabel("Person"),
db.NameToProperty("unexisting_property")));
const auto &db_initial_state = GetState(&db);
storage::Storage db_dump;
{
ResultStreamFaker stream(&db);
query::AnyStream query_stream(&stream, utils::NewDeleteResource());
{
auto acc = db.Access();
query::DbAccessor dba(&acc);
query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
const auto &results = stream.GetResults();
// Indices and constraints are 4 queries and there must be at least one more
// query for the data.
ASSERT_GE(results.size(), 5);
for (const auto &item : results) {
ASSERT_EQ(item.size(), 1);
ASSERT_TRUE(item[0].IsString());
Execute(&db_dump, item[0].ValueString());
}
}
ASSERT_EQ(GetState(&db), GetState(&db_dump));
// Make sure that dump function doesn't make changes on the database.
ASSERT_EQ(GetState(&db), db_initial_state);
}
// NOLINTNEXTLINE(hicpp-special-member-functions)
TEST(DumpTest, ExecuteDumpDatabase) {
storage::Storage db;
{
auto dba = db.Access();
CreateVertex(&dba, {}, {}, false);
ASSERT_FALSE(dba.Commit().HasError());
}
{
auto stream = Execute(&db, "DUMP DATABASE");
const auto &header = stream.GetHeader();
const auto &results = stream.GetResults();
ASSERT_EQ(header.size(), 1U);
EXPECT_EQ(header[0], "QUERY");
EXPECT_EQ(results.size(), 4U);
for (const auto &item : results) {
EXPECT_EQ(item.size(), 1);
EXPECT_TRUE(item[0].IsString());
}
EXPECT_EQ(results[0][0].ValueString(),
"CREATE INDEX ON :__mg_vertex__(__mg_id__);");
EXPECT_EQ(results[1][0].ValueString(),
"CREATE (:__mg_vertex__ {__mg_id__: 0});");
EXPECT_EQ(results[2][0].ValueString(),
"DROP INDEX ON :__mg_vertex__(__mg_id__);");
EXPECT_EQ(results[3][0].ValueString(),
"MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__;");
}
}
class StatefulInterpreter {
public:
explicit StatefulInterpreter(storage::Storage *db)
: db_(db), context_(db_), interpreter_(&context_) {}
auto Execute(const std::string &query) {
ResultStreamFaker stream(db_);
auto [header, _] = interpreter_.Prepare(query, {});
stream.Header(header);
auto summary = interpreter_.PullAll(&stream);
stream.Summary(summary);
return stream;
}
private:
storage::Storage *db_;
query::InterpreterContext context_;
query::Interpreter interpreter_;
};
// NOLINTNEXTLINE(hicpp-special-member-functions)
TEST(DumpTest, ExecuteDumpDatabaseInMulticommandTransaction) {
storage::Storage db;
StatefulInterpreter interpreter(&db);
// Begin the transaction before the vertex is created.
interpreter.Execute("BEGIN");
// Verify that nothing is dumped.
{
auto stream = interpreter.Execute("DUMP DATABASE");
const auto &header = stream.GetHeader();
const auto &results = stream.GetResults();
ASSERT_EQ(header.size(), 1U);
ASSERT_EQ(header[0], "QUERY");
ASSERT_EQ(results.size(), 0U);
}
// Create the vertex.
{
auto dba = db.Access();
CreateVertex(&dba, {}, {}, false);
ASSERT_FALSE(dba.Commit().HasError());
}
// Verify that nothing is dumped.
{
auto stream = interpreter.Execute("DUMP DATABASE");
const auto &header = stream.GetHeader();
const auto &results = stream.GetResults();
ASSERT_EQ(header.size(), 1U);
ASSERT_EQ(header[0], "QUERY");
ASSERT_EQ(results.size(), 0U);
}
// Rollback the transaction.
interpreter.Execute("ROLLBACK");
// Start a new transaction, this transaction should see the vertex.
interpreter.Execute("BEGIN");
// Verify that the vertex is dumped.
{
auto stream = interpreter.Execute("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__;");
}
// Rollback the transaction.
interpreter.Execute("ROLLBACK");
}