memgraph/tests/unit/graph_db_accessor.cpp
Teon Banek 9f460914ed Separate distributed implementation of GraphDbAccessor
Summary:
GraphDbAccessor is now constructed only through GraphDb. This allows the
concrete GraphDb to instantiate a concrete GraphDbAccessor. This allows
us to use virtual calls, so that the implementation may be kept
separate. The major downside of doing things this way is heap allocation
of GraphDbAccessor. In case it turns out to be a real performance
issues, another solution with pointer to static implementation may be
used.

InsertVertexIntoRemote is now a non-member function, which reduces
coupling. It made no sense for it to be member function because it used
only the public parts of GraphDbAccessor.

Reviewers: msantl, mtomic, mferencevic

Reviewed By: msantl

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D1504
2018-07-26 09:16:39 +02:00

398 lines
12 KiB
C++

#include <experimental/optional>
#include "gtest/gtest.h"
#include "database/graph_db.hpp"
#include "database/graph_db_accessor.hpp"
#include "storage/edge_accessor.hpp"
#include "storage/types.hpp"
#include "storage/vertex_accessor.hpp"
using namespace database;
using namespace storage;
template <typename TIterable>
auto Count(TIterable iterable) {
return std::distance(iterable.begin(), iterable.end());
}
TEST(GraphDbAccessorTest, InsertVertex) {
SingleNode db;
auto accessor = db.Access();
gid::Generator generator(0);
EXPECT_EQ(Count(accessor->Vertices(false)), 0);
EXPECT_EQ(accessor->InsertVertex().gid(), generator.Next());
EXPECT_EQ(Count(accessor->Vertices(false)), 0);
EXPECT_EQ(Count(accessor->Vertices(true)), 1);
accessor->AdvanceCommand();
EXPECT_EQ(Count(accessor->Vertices(false)), 1);
EXPECT_EQ(accessor->InsertVertex().gid(), generator.Next());
EXPECT_EQ(Count(accessor->Vertices(false)), 1);
EXPECT_EQ(Count(accessor->Vertices(true)), 2);
accessor->AdvanceCommand();
EXPECT_EQ(Count(accessor->Vertices(false)), 2);
}
TEST(GraphDbAccessorTest, UniqueVertexId) {
SingleNode db;
SkipList<int64_t> ids;
std::vector<std::thread> threads;
for (int i = 0; i < 50; i++) {
threads.emplace_back([&db, &ids]() {
auto dba = db.Access();
auto access = ids.access();
for (int i = 0; i < 200; i++) access.insert(dba->InsertVertex().gid());
});
}
for (auto &thread : threads) thread.join();
EXPECT_EQ(ids.access().size(), 50 * 200);
}
TEST(GraphDbAccessorTest, RemoveVertexSameTransaction) {
SingleNode db;
auto accessor = db.Access();
EXPECT_EQ(Count(accessor->Vertices(false)), 0);
auto va1 = accessor->InsertVertex();
accessor->AdvanceCommand();
EXPECT_EQ(Count(accessor->Vertices(false)), 1);
EXPECT_TRUE(accessor->RemoveVertex(va1));
EXPECT_EQ(Count(accessor->Vertices(false)), 1);
EXPECT_EQ(Count(accessor->Vertices(true)), 0);
accessor->AdvanceCommand();
EXPECT_EQ(Count(accessor->Vertices(false)), 0);
EXPECT_EQ(Count(accessor->Vertices(true)), 0);
}
TEST(GraphDbAccessorTest, RemoveVertexDifferentTransaction) {
SingleNode db;
// first transaction creates a vertex
{
auto accessor = db.Access();
accessor->InsertVertex();
accessor->Commit();
}
// second transaction checks that it sees it, and deletes it
{
auto accessor = db.Access();
EXPECT_EQ(Count(accessor->Vertices(false)), 1);
EXPECT_EQ(Count(accessor->Vertices(true)), 1);
for (auto vertex_accessor : accessor->Vertices(false))
accessor->RemoveVertex(vertex_accessor);
accessor->Commit();
}
// third transaction checks that it does not see the vertex
{
auto accessor = db.Access();
EXPECT_EQ(Count(accessor->Vertices(false)), 0);
EXPECT_EQ(Count(accessor->Vertices(true)), 0);
}
}
TEST(GraphDbAccessorTest, InsertEdge) {
SingleNode db;
auto dba = db.Access();
auto va1 = dba->InsertVertex();
auto va2 = dba->InsertVertex();
dba->AdvanceCommand();
EXPECT_EQ(va1.in_degree(), 0);
EXPECT_EQ(va1.out_degree(), 0);
EXPECT_EQ(va2.in_degree(), 0);
EXPECT_EQ(va2.out_degree(), 0);
// setup (v1) - [:likes] -> (v2)
dba->InsertEdge(va1, va2, dba->EdgeType("likes"));
EXPECT_EQ(Count(dba->Edges(false)), 0);
EXPECT_EQ(Count(dba->Edges(true)), 1);
dba->AdvanceCommand();
EXPECT_EQ(Count(dba->Edges(false)), 1);
EXPECT_EQ(Count(dba->Edges(true)), 1);
EXPECT_EQ(va1.out().begin()->to(), va2);
EXPECT_EQ(va2.in().begin()->from(), va1);
EXPECT_EQ(va1.in_degree(), 0);
EXPECT_EQ(va1.out_degree(), 1);
EXPECT_EQ(va2.in_degree(), 1);
EXPECT_EQ(va2.out_degree(), 0);
// setup (v1) - [:likes] -> (v2) <- [:hates] - (v3)
auto va3 = dba->InsertVertex();
dba->InsertEdge(va3, va2, dba->EdgeType("hates"));
EXPECT_EQ(Count(dba->Edges(false)), 1);
EXPECT_EQ(Count(dba->Edges(true)), 2);
dba->AdvanceCommand();
EXPECT_EQ(Count(dba->Edges(false)), 2);
EXPECT_EQ(va3.out().begin()->to(), va2);
EXPECT_EQ(va1.in_degree(), 0);
EXPECT_EQ(va1.out_degree(), 1);
EXPECT_EQ(va2.in_degree(), 2);
EXPECT_EQ(va2.out_degree(), 0);
EXPECT_EQ(va3.in_degree(), 0);
EXPECT_EQ(va3.out_degree(), 1);
}
TEST(GraphDbAccessorTest, UniqueEdgeId) {
SingleNode db;
SkipList<int64_t> ids;
std::vector<std::thread> threads;
for (int i = 0; i < 50; i++) {
threads.emplace_back([&db, &ids]() {
auto dba = db.Access();
auto v1 = dba->InsertVertex();
auto v2 = dba->InsertVertex();
auto edge_type = dba->EdgeType("edge_type");
auto access = ids.access();
for (int i = 0; i < 200; i++)
access.insert(dba->InsertEdge(v1, v2, edge_type).gid());
});
}
for (auto &thread : threads) thread.join();
EXPECT_EQ(ids.access().size(), 50 * 200);
}
TEST(GraphDbAccessorTest, RemoveEdge) {
SingleNode db;
auto dba = db.Access();
// setup (v1) - [:likes] -> (v2) <- [:hates] - (v3)
auto va1 = dba->InsertVertex();
auto va2 = dba->InsertVertex();
auto va3 = dba->InsertVertex();
dba->InsertEdge(va1, va2, dba->EdgeType("likes"));
dba->InsertEdge(va3, va2, dba->EdgeType("hates"));
dba->AdvanceCommand();
EXPECT_EQ(Count(dba->Edges(false)), 2);
EXPECT_EQ(Count(dba->Edges(true)), 2);
// remove all [:hates] edges
for (auto edge : dba->Edges(false))
if (edge.EdgeType() == dba->EdgeType("hates")) dba->RemoveEdge(edge);
EXPECT_EQ(Count(dba->Edges(false)), 2);
EXPECT_EQ(Count(dba->Edges(true)), 1);
// current state: (v1) - [:likes] -> (v2), (v3)
dba->AdvanceCommand();
EXPECT_EQ(Count(dba->Edges(false)), 1);
EXPECT_EQ(Count(dba->Edges(true)), 1);
EXPECT_EQ(Count(dba->Vertices(false)), 3);
EXPECT_EQ(Count(dba->Vertices(true)), 3);
for (auto edge : dba->Edges(false)) {
EXPECT_EQ(edge.EdgeType(), dba->EdgeType("likes"));
auto v1 = edge.from();
auto v2 = edge.to();
// ensure correct connectivity for all the vertices
for (auto vertex : dba->Vertices(false)) {
if (vertex == v1) {
EXPECT_EQ(vertex.in_degree(), 0);
EXPECT_EQ(vertex.out_degree(), 1);
} else if (vertex == v2) {
EXPECT_EQ(vertex.in_degree(), 1);
EXPECT_EQ(vertex.out_degree(), 0);
} else {
EXPECT_EQ(vertex.in_degree(), 0);
EXPECT_EQ(vertex.out_degree(), 0);
}
}
}
}
TEST(GraphDbAccessorTest, DetachRemoveVertex) {
SingleNode db;
auto dba = db.Access();
// setup (v0)- []->(v1)<-[]-(v2)<-[]-(v3)
std::vector<VertexAccessor> vertices;
for (int i = 0; i < 4; ++i) vertices.emplace_back(dba->InsertVertex());
auto edge_type = dba->EdgeType("type");
dba->InsertEdge(vertices[0], vertices[1], edge_type);
dba->InsertEdge(vertices[2], vertices[1], edge_type);
dba->InsertEdge(vertices[3], vertices[2], edge_type);
dba->AdvanceCommand();
for (auto &vertex : vertices) vertex.Reconstruct();
// ensure that plain remove does NOT work
EXPECT_EQ(Count(dba->Vertices(false)), 4);
EXPECT_EQ(Count(dba->Edges(false)), 3);
EXPECT_FALSE(dba->RemoveVertex(vertices[0]));
EXPECT_FALSE(dba->RemoveVertex(vertices[1]));
EXPECT_FALSE(dba->RemoveVertex(vertices[2]));
EXPECT_EQ(Count(dba->Vertices(false)), 4);
EXPECT_EQ(Count(dba->Edges(false)), 3);
dba->DetachRemoveVertex(vertices[2]);
EXPECT_EQ(Count(dba->Vertices(false)), 4);
EXPECT_EQ(Count(dba->Vertices(true)), 3);
EXPECT_EQ(Count(dba->Edges(false)), 3);
EXPECT_EQ(Count(dba->Edges(true)), 1);
dba->AdvanceCommand();
for (auto &vertex : vertices) vertex.Reconstruct();
EXPECT_EQ(Count(dba->Vertices(false)), 3);
EXPECT_EQ(Count(dba->Edges(false)), 1);
EXPECT_TRUE(dba->RemoveVertex(vertices[3]));
EXPECT_EQ(Count(dba->Vertices(true)), 2);
EXPECT_EQ(Count(dba->Vertices(false)), 3);
dba->AdvanceCommand();
for (auto &vertex : vertices) vertex.Reconstruct();
EXPECT_EQ(Count(dba->Vertices(false)), 2);
EXPECT_EQ(Count(dba->Edges(false)), 1);
for (auto va : dba->Vertices(false)) EXPECT_FALSE(dba->RemoveVertex(va));
dba->AdvanceCommand();
for (auto &vertex : vertices) vertex.Reconstruct();
EXPECT_EQ(Count(dba->Vertices(false)), 2);
EXPECT_EQ(Count(dba->Edges(false)), 1);
for (auto va : dba->Vertices(false)) {
EXPECT_FALSE(dba->RemoveVertex(va));
dba->DetachRemoveVertex(va);
break;
}
EXPECT_EQ(Count(dba->Vertices(true)), 1);
EXPECT_EQ(Count(dba->Vertices(false)), 2);
dba->AdvanceCommand();
for (auto &vertex : vertices) vertex.Reconstruct();
EXPECT_EQ(Count(dba->Vertices(false)), 1);
EXPECT_EQ(Count(dba->Edges(false)), 0);
// remove the last vertex, it has no connections
// so that should work
for (auto va : dba->Vertices(false)) EXPECT_TRUE(dba->RemoveVertex(va));
dba->AdvanceCommand();
EXPECT_EQ(Count(dba->Vertices(false)), 0);
EXPECT_EQ(Count(dba->Edges(false)), 0);
}
TEST(GraphDbAccessorTest, DetachRemoveVertexMultiple) {
// This test checks that we can detach remove the
// same vertex / edge multiple times
SingleNode db;
auto dba = db.Access();
// setup: make a fully connected N graph
// with cycles too!
int N = 7;
std::vector<VertexAccessor> vertices;
auto edge_type = dba->EdgeType("edge");
for (int i = 0; i < N; ++i) vertices.emplace_back(dba->InsertVertex());
for (int j = 0; j < N; ++j)
for (int k = 0; k < N; ++k)
dba->InsertEdge(vertices[j], vertices[k], edge_type);
dba->AdvanceCommand();
for (auto &vertex : vertices) vertex.Reconstruct();
EXPECT_EQ(Count(dba->Vertices(false)), N);
EXPECT_EQ(Count(dba->Edges(false)), N * N);
// detach delete one edge
dba->DetachRemoveVertex(vertices[0]);
dba->AdvanceCommand();
for (auto &vertex : vertices) vertex.Reconstruct();
EXPECT_EQ(Count(dba->Vertices(false)), N - 1);
EXPECT_EQ(Count(dba->Edges(false)), (N - 1) * (N - 1));
// detach delete two neighboring edges
dba->DetachRemoveVertex(vertices[1]);
dba->DetachRemoveVertex(vertices[2]);
dba->AdvanceCommand();
for (auto &vertex : vertices) vertex.Reconstruct();
EXPECT_EQ(Count(dba->Vertices(false)), N - 3);
EXPECT_EQ(Count(dba->Edges(false)), (N - 3) * (N - 3));
// detach delete everything, buwahahahaha
for (int l = 3; l < N; ++l) dba->DetachRemoveVertex(vertices[l]);
dba->AdvanceCommand();
for (auto &vertex : vertices) vertex.Reconstruct();
EXPECT_EQ(Count(dba->Vertices(false)), 0);
EXPECT_EQ(Count(dba->Edges(false)), 0);
}
TEST(GraphDbAccessorTest, Labels) {
SingleNode db;
auto dba = db.Access();
Label label_friend = dba->Label("friend");
EXPECT_EQ(label_friend, dba->Label("friend"));
EXPECT_NE(label_friend, dba->Label("friend2"));
EXPECT_EQ(dba->LabelName(label_friend), "friend");
// test that getting labels through a different accessor works
EXPECT_EQ(label_friend, db.Access()->Label("friend"));
EXPECT_NE(label_friend, db.Access()->Label("friend2"));
}
TEST(GraphDbAccessorTest, EdgeTypes) {
SingleNode db;
auto dba = db.Access();
EdgeType edge_type = dba->EdgeType("likes");
EXPECT_EQ(edge_type, dba->EdgeType("likes"));
EXPECT_NE(edge_type, dba->EdgeType("hates"));
EXPECT_EQ(dba->EdgeTypeName(edge_type), "likes");
// test that getting labels through a different accessor works
EXPECT_EQ(edge_type, db.Access()->EdgeType("likes"));
EXPECT_NE(edge_type, db.Access()->EdgeType("hates"));
}
TEST(GraphDbAccessorTest, Properties) {
SingleNode db;
auto dba = db.Access();
Property prop = dba->Property("name");
EXPECT_EQ(prop, dba->Property("name"));
EXPECT_NE(prop, dba->Property("surname"));
EXPECT_EQ(dba->PropertyName(prop), "name");
// test that getting labels through a different accessor works
EXPECT_EQ(prop, db.Access()->Property("name"));
EXPECT_NE(prop, db.Access()->Property("surname"));
}
TEST(GraphDbAccessorTest, Transfer) {
SingleNode db;
auto dba1 = db.Access();
auto prop = dba1->Property("property");
VertexAccessor v1 = dba1->InsertVertex();
v1.PropsSet(prop, 1);
VertexAccessor v2 = dba1->InsertVertex();
v2.PropsSet(prop, 2);
EdgeAccessor e12 = dba1->InsertEdge(v1, v2, dba1->EdgeType("et"));
e12.PropsSet(prop, 12);
// make dba2 that has dba1 in it's snapshot, so data isn't visible
auto dba2 = db.Access();
EXPECT_EQ(dba2->Transfer(v1), std::experimental::nullopt);
EXPECT_EQ(dba2->Transfer(e12), std::experimental::nullopt);
// make dba3 that does not have dba1 in it's snapshot
dba1->Commit();
auto dba3 = db.Access();
// we can transfer accessors even though the GraphDbAccessor they
// belong to is not alive anymore
EXPECT_EQ(dba3->Transfer(v1)->PropsAt(prop).Value<int64_t>(), 1);
EXPECT_EQ(dba3->Transfer(e12)->PropsAt(prop).Value<int64_t>(), 12);
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
// ::testing::GTEST_FLAG(filter) = "*.DetachRemoveVertex";
return RUN_ALL_TESTS();
}