9291a5fc4d
Reviewers: teon.banek, buda Reviewed By: teon.banek Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D1974
399 lines
12 KiB
C++
399 lines
12 KiB
C++
#include <optional>
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include "database/single_node/graph_db.hpp"
|
|
#include "database/single_node/graph_db_accessor.hpp"
|
|
#include "storage/common/types/types.hpp"
|
|
#include "storage/edge_accessor.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) {
|
|
GraphDb db;
|
|
auto accessor = db.Access();
|
|
gid::Generator generator;
|
|
|
|
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) {
|
|
GraphDb 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) {
|
|
GraphDb 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) {
|
|
GraphDb 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) {
|
|
GraphDb 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) {
|
|
GraphDb 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) {
|
|
GraphDb 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) {
|
|
GraphDb 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
|
|
|
|
GraphDb 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) {
|
|
GraphDb 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) {
|
|
GraphDb 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) {
|
|
GraphDb 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) {
|
|
GraphDb 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::nullopt);
|
|
EXPECT_EQ(dba2.Transfer(e12), std::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();
|
|
}
|