From 4d8f8be1ab900cd0d955ecefc3b2b85dd27c1aba Mon Sep 17 00:00:00 2001
From: Tonko Sabolcec <tonko.sabolcec@memgraph.io>
Date: Tue, 7 May 2019 15:10:28 +0200
Subject: [PATCH] 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
---
 src/CMakeLists.txt                            |   1 +
 src/database/single_node/dump.cpp             | 127 +++++++++
 src/database/single_node/dump.hpp             |  18 ++
 .../interpret/awesome_memgraph_functions.cpp  |  16 ++
 tests/unit/CMakeLists.txt                     |   3 +
 tests/unit/database_dump.cpp                  | 260 ++++++++++++++++++
 6 files changed, 425 insertions(+)
 create mode 100644 src/database/single_node/dump.cpp
 create mode 100644 src/database/single_node/dump.hpp
 create mode 100644 tests/unit/database_dump.cpp

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 172a2a6fd..6561c64b0 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -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
diff --git a/src/database/single_node/dump.cpp b/src/database/single_node/dump.cpp
new file mode 100644
index 000000000..ce3d13bb0
--- /dev/null
+++ b/src/database/single_node/dump.cpp
@@ -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
diff --git a/src/database/single_node/dump.hpp b/src/database/single_node/dump.hpp
new file mode 100644
index 000000000..e2480204c
--- /dev/null
+++ b/src/database/single_node/dump.hpp
@@ -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
diff --git a/src/query/interpret/awesome_memgraph_functions.cpp b/src/query/interpret/awesome_memgraph_functions.cpp
index a14205201..dd14cc3af 100644
--- a/src/query/interpret/awesome_memgraph_functions.cpp
+++ b/src/query/interpret/awesome_memgraph_functions.cpp
@@ -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
diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt
index 27ad4f069..5bed9fe15 100644
--- a/tests/unit/CMakeLists.txt
+++ b/tests/unit/CMakeLists.txt
@@ -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)
 
diff --git a/tests/unit/database_dump.cpp b/tests/unit/database_dump.cpp
new file mode 100644
index 000000000..c7824f832
--- /dev/null
+++ b/tests/unit/database_dump.cpp
@@ -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());
+}