Add function that dumps vertices and edges of the graph.

Summary: Fix lint errors

Reviewers: teon.banek

Reviewed By: teon.banek

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D1996
This commit is contained in:
Tonko Sabolcec 2019-05-07 15:10:28 +02:00
parent 5c244c1ad4
commit 4d8f8be1ab
6 changed files with 425 additions and 0 deletions

View File

@ -19,6 +19,7 @@ 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

View File

@ -0,0 +1,127 @@
#include "database/single_node/dump.hpp"
#include <map>
#include <ostream>
#include <utility>
#include <vector>
#include <glog/logging.h>
#include "database/graph_db_accessor.hpp"
#include "utils/algorithm.hpp"
#include "utils/string.hpp"
namespace database {
namespace {
void DumpPropertyValue(std::ostream *os, const PropertyValue &value) {
switch (value.type()) {
case PropertyValue::Type::Null:
*os << "Null";
return;
case PropertyValue::Type::Bool:
*os << (value.Value<bool>() ? "true" : "false");
return;
case PropertyValue::Type::String:
*os << ::utils::Escape(value.Value<std::string>());
return;
case PropertyValue::Type::Int:
*os << value.Value<int64_t>();
return;
case PropertyValue::Type::Double:
// TODO(tsabolcec): By default, this will output only 6 significant digits
// of the number. We should increase that number to avoid precision loss.
*os << value.Value<double>();
return;
case PropertyValue::Type::List: {
*os << "[";
const auto &list = value.Value<std::vector<PropertyValue>>();
utils::PrintIterable(*os, list, ", ", [](auto &os, const auto &item) {
DumpPropertyValue(&os, item);
});
*os << "]";
return;
}
case PropertyValue::Type::Map: {
*os << "{";
const auto &map = value.Value<std::map<std::string, PropertyValue>>();
utils::PrintIterable(*os, map, ", ", [](auto &os, const auto &kv) {
os << kv.first << ": ";
DumpPropertyValue(&os, kv.second);
});
*os << "}";
return;
}
}
}
void DumpProperties(std::ostream *os, GraphDbAccessor *dba,
const PropertyValueStore &store) {
*os << "{";
utils::PrintIterable(*os, store, ", ", [&dba](auto &os, const auto &kv) {
os << dba->PropertyName(kv.first) << ": ";
DumpPropertyValue(&os, kv.second);
});
*os << "}";
}
void DumpVertex(std::ostream *os, GraphDbAccessor *dba,
const VertexAccessor &vertex) {
*os << "(n" << vertex.gid();
for (const auto &label : vertex.labels()) {
*os << ":" << dba->LabelName(label);
}
const auto &props = vertex.Properties();
if (props.size() > 0) {
*os << " ";
DumpProperties(os, dba, props);
}
*os << ")";
}
void DumpVertices(std::ostream *os, GraphDbAccessor *dba) {
auto vertices = dba->Vertices(false);
utils::PrintIterable(
os, vertices.begin(), vertices.end(), ", ",
[&dba](auto &os, const auto &vertex) { DumpVertex(&os, dba, vertex); });
}
void DumpEdge(std::ostream *os, GraphDbAccessor *dba,
const EdgeAccessor &edge) {
*os << "(n" << edge.from().gid() << ")-[";
*os << ":" << dba->EdgeTypeName(edge.EdgeType());
const auto &props = edge.Properties();
if (props.size() > 0) {
*os << " ";
DumpProperties(os, dba, props);
}
*os << "]->(n" << edge.to().gid() << ")";
}
void DumpEdges(std::ostream *os, GraphDbAccessor *dba) {
auto edges = dba->Edges(false);
utils::PrintIterable(
os, edges.begin(), edges.end(), ", ",
[&dba](auto &os, const auto &edge) { DumpEdge(&os, dba, edge); });
}
} // namespace
void DumpToCypher(std::ostream *os, GraphDbAccessor *dba) {
CHECK(os);
CHECK(dba);
if (dba->VerticesCount() > 0) {
*os << "CREATE ";
DumpVertices(os, dba);
if (dba->EdgesCount() > 0) {
*os << ", ";
DumpEdges(os, dba);
}
*os << ";";
}
// TODO(tsabolcec): Dump other data as well.
}
} // namespace database

View File

@ -0,0 +1,18 @@
#pragma once
#include <ostream>
#include "database/graph_db_accessor.hpp"
namespace database {
/// Dumps database state to output stream as openCypher queries.
///
/// Currently, it only dumps vertices and edges of the graph. In the future,
/// it should also dump indexes, constraints, roles, etc.
///
/// @param os Output stream
/// @param dba Database accessor
void DumpToCypher(std::ostream *os, GraphDbAccessor *dba);
} // namespace database

View File

@ -6,6 +6,7 @@
#include <functional>
#include <random>
#include "database/single_node/dump.hpp"
#include "query/context.hpp"
#include "query/exceptions.hpp"
#include "utils/string.hpp"
@ -895,6 +896,18 @@ TypedValue Substring(TypedValue *args, int64_t nargs, const EvaluationContext &,
return start < str.size() ? str.substr(start, len) : "";
}
#if MG_SINGLE_NODE
TypedValue Dump(TypedValue *args, int64_t nargs, const EvaluationContext &,
database::GraphDbAccessor *dba) {
if (nargs != 0) {
throw QueryRuntimeException("'dump' does not expect any arguments.");
}
std::ostringstream oss;
database::DumpToCypher(&oss, dba);
return oss.str();
}
#endif // MG_SINGLE_NODE
} // namespace
std::function<TypedValue(TypedValue *, int64_t, const EvaluationContext &,
@ -972,6 +985,9 @@ NameToFunction(const std::string &function_name) {
if (function_name == "ASSERT") return Assert;
if (function_name == "COUNTER") return Counter;
if (function_name == "COUNTERSET") return CounterSet;
#ifdef MG_SINGLE_NODE
if (function_name == "DUMP") return Dump;
#endif
#ifdef MG_DISTRIBUTED
if (function_name == "WORKERID") return WorkerId;
#endif

View File

@ -45,6 +45,9 @@ target_link_libraries(${test_prefix}counters mg-distributed kvstore_dummy_lib)
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)

View File

@ -0,0 +1,260 @@
#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"
const char *kPropertyId = "property_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;
};
std::set<Vertex> vertices;
std::set<Edge> edges;
};
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::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 &first, const DatabaseState &second) {
return first.vertices == second.vertices && first.edges == second.edges;
}
class DatabaseEnvironment {
public:
std::string DumpStr() {
auto dba = db_.Access();
std::ostringstream oss;
database::DumpToCypher(&oss, &dba);
return oss.str();
}
void Execute(const std::string &query) {
auto dba = db_.Access();
ResultStreamFaker<query::TypedValue> results;
query::Interpreter()(query, dba, {}, false).PullAll(results);
dba.Commit();
}
VertexAccessor CreateVertex(const std::vector<std::string> &labels,
const std::map<std::string, PropertyValue> &props,
bool add_property_id = true) {
auto dba = db_.Access();
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(static_cast<int64_t>(vertex.gid())));
}
dba.Commit();
return vertex;
}
EdgeAccessor CreateEdge(VertexAccessor from, VertexAccessor to,
const std::string &edge_type_name,
const std::map<std::string, PropertyValue> &props,
bool add_property_id = true) {
auto dba = db_.Access();
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(static_cast<int64_t>(edge.gid())));
}
dba.Commit();
return edge;
}
DatabaseState GetState() {
// Capture all vertices
std::map<gid::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].Value<int64_t>();
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});
}
return {vertices, edges};
}
private:
database::GraphDb db_;
};
TEST(DumpTest, EmptyGraph) {
DatabaseEnvironment db;
EXPECT_EQ("", db.DumpStr());
}
TEST(DumpTest, SingleVertex) {
DatabaseEnvironment db;
db.CreateVertex({}, {}, false);
EXPECT_EQ(db.DumpStr(), "CREATE (n0);");
}
TEST(DumpTest, VertexWithSingleLabel) {
DatabaseEnvironment db;
db.CreateVertex({"Label1"}, {}, false);
EXPECT_EQ(db.DumpStr(), "CREATE (n0:Label1);");
}
TEST(DumpTest, VertexWithMultipleLabels) {
DatabaseEnvironment db;
db.CreateVertex({"Label1", "Label2"}, {}, false);
EXPECT_EQ(db.DumpStr(), "CREATE (n0:Label1:Label2);");
}
TEST(DumpTest, VertexWithSingleProperty) {
DatabaseEnvironment db;
db.CreateVertex({}, {{"prop", PropertyValue(42)}}, false);
EXPECT_EQ(db.DumpStr(), "CREATE (n0 {prop: 42});");
}
TEST(DumpTest, MultipleVertices) {
DatabaseEnvironment db;
db.CreateVertex({}, {}, false);
db.CreateVertex({}, {}, false);
db.CreateVertex({}, {}, false);
EXPECT_EQ(db.DumpStr(), "CREATE (n0), (n1), (n2);");
}
TEST(DumpTest, SingleEdge) {
DatabaseEnvironment db;
auto u = db.CreateVertex({}, {}, false);
auto v = db.CreateVertex({}, {}, false);
db.CreateEdge(u, v, "EdgeType", {}, false);
EXPECT_EQ(db.DumpStr(), "CREATE (n0), (n1), (n0)-[:EdgeType]->(n1);");
}
TEST(DumpTest, MultipleEdges) {
DatabaseEnvironment db;
auto u = db.CreateVertex({}, {}, false);
auto v = db.CreateVertex({}, {}, false);
auto w = db.CreateVertex({}, {}, false);
db.CreateEdge(u, v, "EdgeType", {}, false);
db.CreateEdge(v, u, "EdgeType", {}, false);
db.CreateEdge(v, w, "EdgeType", {}, false);
const char *expected =
"CREATE (n0), (n1), (n2), (n0)-[:EdgeType]->(n1), "
"(n1)-[:EdgeType]->(n0), (n1)-[:EdgeType]->(n2);";
EXPECT_EQ(db.DumpStr(), expected);
}
TEST(DumpTest, EdgeWithProperties) {
DatabaseEnvironment db;
auto u = db.CreateVertex({}, {}, false);
auto v = db.CreateVertex({}, {}, false);
db.CreateEdge(u, v, "EdgeType", {{"prop", PropertyValue(13)}}, false);
EXPECT_EQ(db.DumpStr(),
"CREATE (n0), (n1), (n0)-[:EdgeType {prop: 13}]->(n1);");
}
TEST(DumpTest, CheckStateVertexWithMultipleProperties) {
DatabaseEnvironment db;
std::map<std::string, PropertyValue> prop1 = {
{"nested1", PropertyValue(1337)}, {"nested2", PropertyValue(3.14)}};
db.CreateVertex({"Label1", "Label2"},
{{"prop1", prop1}, {"prop2", PropertyValue("$'\t'")}});
DatabaseEnvironment db_dump;
db_dump.Execute(db.DumpStr());
EXPECT_EQ(db.GetState(), db_dump.GetState());
}
TEST(DumpTest, CheckStateSimpleGraph) {
DatabaseEnvironment db;
auto u = db.CreateVertex({"Person"}, {{"name", "Ivan"}});
auto v = db.CreateVertex({"Person"}, {{"name", "Josko"}});
auto w = db.CreateVertex({"Person"}, {{"name", "Bosko"}});
auto z = db.CreateVertex({"Person"}, {{"name", "Buha"}});
db.CreateEdge(u, v, "Knows", {});
db.CreateEdge(v, w, "Knows", {{"how_long", 5}});
db.CreateEdge(w, u, "Knows", {{"how", "distant past"}});
db.CreateEdge(v, u, "Knows", {});
db.CreateEdge(v, u, "Likes", {});
db.CreateEdge(z, u, "Knows", {});
db.CreateEdge(w, z, "Knows", {{"how", "school"}});
db.CreateEdge(w, z, "Likes", {{"how", "very much"}});
DatabaseEnvironment db_dump;
db_dump.Execute(db.DumpStr());
EXPECT_EQ(db.GetState(), db_dump.GetState());
}