6fc6a27288
Summary: GraphDb is refactored to become an API exposing different parts necessary for the database to function. These different parts can have different implementations in SingleNode or distributed Master/Server GraphDb implementations. Interally GraphDb is implemented using two class heirarchies. One contains all the members and correct wiring for each situation. The other takes care of initialization and shutdown. This architecture is practical because it can guarantee that the initialization of the object structure is complete, before initializing state. Reviewers: buda, mislav.bradac, dgleich, teon.banek Reviewed By: teon.banek Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D1093
429 lines
15 KiB
C++
429 lines
15 KiB
C++
#include <experimental/optional>
|
|
#include <memory>
|
|
#include <thread>
|
|
|
|
#include <gmock/gmock.h>
|
|
#include <gtest/gtest.h>
|
|
|
|
#include "database/graph_db.hpp"
|
|
#include "database/graph_db_accessor.hpp"
|
|
#include "utils/bound.hpp"
|
|
|
|
using testing::UnorderedElementsAreArray;
|
|
|
|
template <typename TIterable>
|
|
auto Count(TIterable iterable) {
|
|
return std::distance(iterable.begin(), iterable.end());
|
|
}
|
|
|
|
/**
|
|
* A test fixture that contains a database, accessor,
|
|
* label, property and an edge_type.
|
|
*/
|
|
class GraphDbAccessorIndex : public testing::Test {
|
|
protected:
|
|
database::SingleNode db;
|
|
std::experimental::optional<database::GraphDbAccessor> dba{db};
|
|
database::Property property = dba->Property("property");
|
|
database::Label label = dba->Label("label");
|
|
database::EdgeType edge_type = dba->EdgeType("edge_type");
|
|
|
|
auto AddVertex() {
|
|
auto vertex = dba->InsertVertex();
|
|
vertex.add_label(label);
|
|
return vertex;
|
|
}
|
|
|
|
auto AddVertex(int property_value) {
|
|
auto vertex = dba->InsertVertex();
|
|
vertex.add_label(label);
|
|
vertex.PropsSet(property, property_value);
|
|
return vertex;
|
|
}
|
|
|
|
// commits the current dba, and replaces it with a new one
|
|
void Commit() {
|
|
dba->Commit();
|
|
dba.emplace(db);
|
|
}
|
|
};
|
|
|
|
TEST_F(GraphDbAccessorIndex, LabelIndexCount) {
|
|
auto label2 = dba->Label("label2");
|
|
EXPECT_EQ(dba->VerticesCount(label), 0);
|
|
EXPECT_EQ(dba->VerticesCount(label2), 0);
|
|
EXPECT_EQ(dba->VerticesCount(), 0);
|
|
for (int i = 0; i < 11; ++i) dba->InsertVertex().add_label(label);
|
|
for (int i = 0; i < 17; ++i) dba->InsertVertex().add_label(label2);
|
|
// even though xxx_count functions in database::GraphDbAccessor can
|
|
// over-estaimate in this situation they should be exact (nothing was ever
|
|
// deleted)
|
|
EXPECT_EQ(dba->VerticesCount(label), 11);
|
|
EXPECT_EQ(dba->VerticesCount(label2), 17);
|
|
EXPECT_EQ(dba->VerticesCount(), 28);
|
|
}
|
|
|
|
TEST_F(GraphDbAccessorIndex, LabelIndexIteration) {
|
|
// add 10 vertices, check visibility
|
|
for (int i = 0; i < 10; i++) AddVertex();
|
|
EXPECT_EQ(Count(dba->Vertices(label, false)), 0);
|
|
EXPECT_EQ(Count(dba->Vertices(label, true)), 10);
|
|
Commit();
|
|
EXPECT_EQ(Count(dba->Vertices(label, false)), 10);
|
|
EXPECT_EQ(Count(dba->Vertices(label, true)), 10);
|
|
|
|
// remove 3 vertices, check visibility
|
|
int deleted = 0;
|
|
for (auto vertex : dba->Vertices(false)) {
|
|
dba->RemoveVertex(vertex);
|
|
if (++deleted >= 3) break;
|
|
}
|
|
EXPECT_EQ(Count(dba->Vertices(label, false)), 10);
|
|
EXPECT_EQ(Count(dba->Vertices(label, true)), 7);
|
|
Commit();
|
|
EXPECT_EQ(Count(dba->Vertices(label, false)), 7);
|
|
EXPECT_EQ(Count(dba->Vertices(label, true)), 7);
|
|
}
|
|
|
|
TEST_F(GraphDbAccessorIndex, EdgesCount) {
|
|
auto edge_type2 = dba->EdgeType("edge_type2");
|
|
EXPECT_EQ(dba->EdgesCount(), 0);
|
|
|
|
auto v1 = AddVertex();
|
|
auto v2 = AddVertex();
|
|
for (int i = 0; i < 11; ++i) dba->InsertEdge(v1, v2, edge_type);
|
|
for (int i = 0; i < 17; ++i) dba->InsertEdge(v1, v2, edge_type2);
|
|
// even though xxx_count functions in database::GraphDbAccessor can
|
|
// over-estaimate in this situation they should be exact (nothing was ever
|
|
// deleted)
|
|
EXPECT_EQ(dba->EdgesCount(), 28);
|
|
}
|
|
|
|
TEST_F(GraphDbAccessorIndex, LabelPropertyIndexBuild) {
|
|
AddVertex(0);
|
|
|
|
Commit();
|
|
dba->BuildIndex(label, property);
|
|
Commit();
|
|
|
|
EXPECT_EQ(dba->VerticesCount(label, property), 1);
|
|
|
|
// confirm there is a differentiation of indexes based on (label, property)
|
|
auto label2 = dba->Label("label2");
|
|
auto property2 = dba->Property("property2");
|
|
dba->BuildIndex(label2, property);
|
|
dba->BuildIndex(label, property2);
|
|
Commit();
|
|
|
|
EXPECT_EQ(dba->VerticesCount(label, property), 1);
|
|
EXPECT_EQ(dba->VerticesCount(label2, property), 0);
|
|
EXPECT_EQ(dba->VerticesCount(label, property2), 0);
|
|
}
|
|
|
|
TEST_F(GraphDbAccessorIndex, LabelPropertyIndexBuildTwice) {
|
|
dba->BuildIndex(label, property);
|
|
EXPECT_THROW(dba->BuildIndex(label, property), utils::BasicException);
|
|
}
|
|
|
|
TEST_F(GraphDbAccessorIndex, LabelPropertyIndexCount) {
|
|
dba->BuildIndex(label, property);
|
|
EXPECT_EQ(dba->VerticesCount(label, property), 0);
|
|
EXPECT_EQ(Count(dba->Vertices(label, property, true)), 0);
|
|
for (int i = 0; i < 14; ++i) AddVertex(0);
|
|
EXPECT_EQ(dba->VerticesCount(label, property), 14);
|
|
EXPECT_EQ(Count(dba->Vertices(label, property, true)), 14);
|
|
}
|
|
|
|
TEST(GraphDbAccessorIndexApi, LabelPropertyBuildIndexConcurrent) {
|
|
const int ITER_COUNT = 10;
|
|
for (int iter = 0; iter < ITER_COUNT; ++iter) {
|
|
database::SingleNode db;
|
|
const int THREAD_COUNT = 10;
|
|
std::vector<std::thread> threads;
|
|
for (int index = 0; index < THREAD_COUNT; ++index) {
|
|
threads.emplace_back([&db, index]() {
|
|
database::GraphDbAccessor dba(db);
|
|
dba.BuildIndex(dba.Label("l" + std::to_string(index)),
|
|
dba.Property("p" + std::to_string(index)));
|
|
|
|
});
|
|
}
|
|
// All threads should end and there shouldn't be any deadlock
|
|
for (auto &thread : threads) thread.join();
|
|
}
|
|
}
|
|
|
|
#define EXPECT_WITH_MARGIN(x, center) \
|
|
EXPECT_THAT( \
|
|
x, testing::AllOf(testing::Ge(center - 2), testing::Le(center + 2)));
|
|
|
|
TEST_F(GraphDbAccessorIndex, LabelPropertyValueCount) {
|
|
dba->BuildIndex(label, property);
|
|
|
|
// add some vertices without the property
|
|
for (int i = 0; i < 20; i++) AddVertex();
|
|
|
|
// add vertices with prop values [0, 29), ten vertices for each value
|
|
for (int i = 0; i < 300; i++) AddVertex(i / 10);
|
|
// add verties in t he [30, 40) range, 100 vertices for each value
|
|
for (int i = 0; i < 1000; i++) AddVertex(30 + i / 100);
|
|
|
|
// test estimates for exact value count
|
|
EXPECT_WITH_MARGIN(dba->VerticesCount(label, property, 10), 10);
|
|
EXPECT_WITH_MARGIN(dba->VerticesCount(label, property, 14), 10);
|
|
EXPECT_WITH_MARGIN(dba->VerticesCount(label, property, 30), 100);
|
|
EXPECT_WITH_MARGIN(dba->VerticesCount(label, property, 39), 100);
|
|
EXPECT_EQ(dba->VerticesCount(label, property, 40), 0);
|
|
|
|
// helper functions
|
|
auto Inclusive = [](int64_t value) {
|
|
return std::experimental::make_optional(
|
|
utils::MakeBoundInclusive(PropertyValue(value)));
|
|
};
|
|
auto Exclusive = [](int64_t value) {
|
|
return std::experimental::make_optional(
|
|
utils::MakeBoundExclusive(PropertyValue(value)));
|
|
};
|
|
auto VerticesCount = [this](auto lower, auto upper) {
|
|
return dba->VerticesCount(label, property, lower, upper);
|
|
};
|
|
|
|
using std::experimental::nullopt;
|
|
::testing::FLAGS_gtest_death_test_style = "threadsafe";
|
|
EXPECT_DEATH(VerticesCount(nullopt, nullopt), "bound must be provided");
|
|
EXPECT_WITH_MARGIN(VerticesCount(nullopt, Exclusive(4)), 40);
|
|
EXPECT_WITH_MARGIN(VerticesCount(nullopt, Inclusive(4)), 50);
|
|
EXPECT_WITH_MARGIN(VerticesCount(Exclusive(13), nullopt), 160 + 1000);
|
|
EXPECT_WITH_MARGIN(VerticesCount(Inclusive(13), nullopt), 170 + 1000);
|
|
EXPECT_WITH_MARGIN(VerticesCount(Inclusive(13), Exclusive(14)), 10);
|
|
EXPECT_WITH_MARGIN(VerticesCount(Exclusive(13), Inclusive(14)), 10);
|
|
EXPECT_WITH_MARGIN(VerticesCount(Exclusive(13), Exclusive(13)), 0);
|
|
EXPECT_WITH_MARGIN(VerticesCount(Inclusive(20), Exclusive(13)), 0);
|
|
}
|
|
|
|
#undef EXPECT_WITH_MARGIN
|
|
|
|
TEST_F(GraphDbAccessorIndex, LabelPropertyValueIteration) {
|
|
dba->BuildIndex(label, property);
|
|
Commit();
|
|
|
|
// insert 10 verties and and check visibility
|
|
for (int i = 0; i < 10; i++) AddVertex(12);
|
|
EXPECT_EQ(Count(dba->Vertices(label, property, 12, false)), 0);
|
|
EXPECT_EQ(Count(dba->Vertices(label, property, 12, true)), 10);
|
|
Commit();
|
|
EXPECT_EQ(Count(dba->Vertices(label, property, 12, false)), 10);
|
|
EXPECT_EQ(Count(dba->Vertices(label, property, 12, true)), 10);
|
|
}
|
|
|
|
TEST_F(GraphDbAccessorIndex, LabelPropertyValueSorting) {
|
|
dba->BuildIndex(label, property);
|
|
Commit();
|
|
|
|
std::vector<PropertyValue> expected_property_value(50, 0);
|
|
|
|
// strings
|
|
for (int i = 0; i < 10; ++i) {
|
|
auto vertex_accessor = dba->InsertVertex();
|
|
vertex_accessor.add_label(label);
|
|
vertex_accessor.PropsSet(property,
|
|
static_cast<std::string>(std::to_string(i)));
|
|
expected_property_value[i] = vertex_accessor.PropsAt(property);
|
|
}
|
|
// bools - insert in reverse to check for comparison between values.
|
|
for (int i = 9; i >= 0; --i) {
|
|
auto vertex_accessor = dba->InsertVertex();
|
|
vertex_accessor.add_label(label);
|
|
vertex_accessor.PropsSet(property, static_cast<bool>(i / 5));
|
|
expected_property_value[10 + i] = vertex_accessor.PropsAt(property);
|
|
}
|
|
|
|
// integers
|
|
for (int i = 0; i < 10; ++i) {
|
|
auto vertex_accessor = dba->InsertVertex();
|
|
vertex_accessor.add_label(label);
|
|
vertex_accessor.PropsSet(property, i);
|
|
expected_property_value[20 + 2 * i] = vertex_accessor.PropsAt(property);
|
|
}
|
|
// doubles
|
|
for (int i = 0; i < 10; ++i) {
|
|
auto vertex_accessor = dba->InsertVertex();
|
|
vertex_accessor.add_label(label);
|
|
vertex_accessor.PropsSet(property, static_cast<double>(i + 0.5));
|
|
expected_property_value[20 + 2 * i + 1] = vertex_accessor.PropsAt(property);
|
|
}
|
|
|
|
// lists of ints - insert in reverse to check for comparision between
|
|
// lists.
|
|
for (int i = 9; i >= 0; --i) {
|
|
auto vertex_accessor = dba->InsertVertex();
|
|
vertex_accessor.add_label(label);
|
|
std::vector<PropertyValue> value;
|
|
value.push_back(PropertyValue(i));
|
|
vertex_accessor.PropsSet(property, value);
|
|
expected_property_value[40 + i] = vertex_accessor.PropsAt(property);
|
|
}
|
|
|
|
// Maps. Declare a vector in the expected order, then shuffle when setting on
|
|
// vertices.
|
|
std::vector<std::map<std::string, PropertyValue>> maps{
|
|
{{"b", 12}},
|
|
{{"b", 12}, {"a", 77}},
|
|
{{"a", 77}, {"c", 0}},
|
|
{{"a", 78}, {"b", 12}}};
|
|
expected_property_value.insert(expected_property_value.end(), maps.begin(),
|
|
maps.end());
|
|
auto shuffled = maps;
|
|
std::random_shuffle(shuffled.begin(), shuffled.end());
|
|
for (const auto &map : shuffled) {
|
|
auto vertex_accessor = dba->InsertVertex();
|
|
vertex_accessor.add_label(label);
|
|
vertex_accessor.PropsSet(property, map);
|
|
}
|
|
|
|
EXPECT_EQ(Count(dba->Vertices(label, property, false)), 0);
|
|
EXPECT_EQ(Count(dba->Vertices(label, property, true)), 54);
|
|
|
|
int cnt = 0;
|
|
for (auto vertex : dba->Vertices(label, property, true)) {
|
|
const PropertyValue &property_value = vertex.PropsAt(property);
|
|
EXPECT_EQ(property_value.type(), expected_property_value[cnt].type());
|
|
switch (property_value.type()) {
|
|
case PropertyValue::Type::Bool:
|
|
EXPECT_EQ(property_value.Value<bool>(),
|
|
expected_property_value[cnt].Value<bool>());
|
|
break;
|
|
case PropertyValue::Type::Double:
|
|
EXPECT_EQ(property_value.Value<double>(),
|
|
expected_property_value[cnt].Value<double>());
|
|
break;
|
|
case PropertyValue::Type::Int:
|
|
EXPECT_EQ(property_value.Value<int64_t>(),
|
|
expected_property_value[cnt].Value<int64_t>());
|
|
break;
|
|
case PropertyValue::Type::String:
|
|
EXPECT_EQ(property_value.Value<std::string>(),
|
|
expected_property_value[cnt].Value<std::string>());
|
|
break;
|
|
case PropertyValue::Type::List: {
|
|
auto received_value =
|
|
property_value.Value<std::vector<PropertyValue>>();
|
|
auto expected_value =
|
|
expected_property_value[cnt].Value<std::vector<PropertyValue>>();
|
|
EXPECT_EQ(received_value.size(), expected_value.size());
|
|
EXPECT_EQ(received_value.size(), 1);
|
|
EXPECT_EQ(received_value[0].Value<int64_t>(),
|
|
expected_value[0].Value<int64_t>());
|
|
break;
|
|
}
|
|
case PropertyValue::Type::Map: {
|
|
auto received_value =
|
|
property_value.Value<std::map<std::string, PropertyValue>>();
|
|
auto expected_value =
|
|
expected_property_value[cnt]
|
|
.Value<std::map<std::string, PropertyValue>>();
|
|
EXPECT_EQ(received_value.size(), expected_value.size());
|
|
for (const auto &kv : expected_value) {
|
|
auto found = expected_value.find(kv.first);
|
|
EXPECT_NE(found, expected_value.end());
|
|
EXPECT_EQ(kv.second.Value<int64_t>(), found->second.Value<int64_t>());
|
|
}
|
|
break;
|
|
}
|
|
case PropertyValue::Type::Null:
|
|
ASSERT_FALSE("Invalid value type.");
|
|
}
|
|
++cnt;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A test fixture that contains a database, accessor,
|
|
* (label, property) index and 100 vertices, 10 for
|
|
* each of [0, 10) property values.
|
|
*/
|
|
class GraphDbAccessorIndexRange : public GraphDbAccessorIndex {
|
|
protected:
|
|
void SetUp() override {
|
|
dba->BuildIndex(label, property);
|
|
for (int i = 0; i < 100; i++) AddVertex(i / 10);
|
|
|
|
ASSERT_EQ(Count(dba->Vertices(false)), 0);
|
|
ASSERT_EQ(Count(dba->Vertices(true)), 100);
|
|
Commit();
|
|
ASSERT_EQ(Count(dba->Vertices(false)), 100);
|
|
}
|
|
|
|
auto Vertices(std::experimental::optional<utils::Bound<PropertyValue>> lower,
|
|
std::experimental::optional<utils::Bound<PropertyValue>> upper,
|
|
bool current_state = false) {
|
|
return dba->Vertices(label, property, lower, upper, current_state);
|
|
}
|
|
|
|
auto Inclusive(PropertyValue value) {
|
|
return std::experimental::make_optional(
|
|
utils::MakeBoundInclusive(PropertyValue(value)));
|
|
}
|
|
|
|
auto Exclusive(int value) {
|
|
return std::experimental::make_optional(
|
|
utils::MakeBoundExclusive(PropertyValue(value)));
|
|
}
|
|
};
|
|
|
|
TEST_F(GraphDbAccessorIndexRange, RangeIteration) {
|
|
using std::experimental::nullopt;
|
|
EXPECT_EQ(Count(Vertices(nullopt, Inclusive(7))), 80);
|
|
EXPECT_EQ(Count(Vertices(nullopt, Exclusive(7))), 70);
|
|
EXPECT_EQ(Count(Vertices(Inclusive(7), nullopt)), 30);
|
|
EXPECT_EQ(Count(Vertices(Exclusive(7), nullopt)), 20);
|
|
EXPECT_EQ(Count(Vertices(Exclusive(3), Exclusive(6))), 20);
|
|
EXPECT_EQ(Count(Vertices(Inclusive(3), Inclusive(6))), 40);
|
|
EXPECT_EQ(Count(Vertices(Inclusive(6), Inclusive(3))), 0);
|
|
::testing::FLAGS_gtest_death_test_style = "threadsafe";
|
|
EXPECT_DEATH(Vertices(nullopt, nullopt), "bound must be provided");
|
|
}
|
|
|
|
TEST_F(GraphDbAccessorIndexRange, RangeIterationCurrentState) {
|
|
using std::experimental::nullopt;
|
|
EXPECT_EQ(Count(Vertices(nullopt, Inclusive(7))), 80);
|
|
for (int i = 0; i < 20; i++) AddVertex(2);
|
|
EXPECT_EQ(Count(Vertices(nullopt, Inclusive(7))), 80);
|
|
EXPECT_EQ(Count(Vertices(nullopt, Inclusive(7), true)), 100);
|
|
Commit();
|
|
EXPECT_EQ(Count(Vertices(nullopt, Inclusive(7))), 100);
|
|
}
|
|
|
|
TEST_F(GraphDbAccessorIndexRange, RangeInterationIncompatibleTypes) {
|
|
using std::experimental::nullopt;
|
|
|
|
// using PropertyValue::Null as a bound fails with an assertion
|
|
::testing::FLAGS_gtest_death_test_style = "threadsafe";
|
|
EXPECT_DEATH(Vertices(nullopt, Inclusive(PropertyValue::Null)),
|
|
"not a valid index bound");
|
|
EXPECT_DEATH(Vertices(Inclusive(PropertyValue::Null), nullopt),
|
|
"not a valid index bound");
|
|
std::vector<PropertyValue> incompatible_with_int{
|
|
"string", true, std::vector<PropertyValue>{1}};
|
|
|
|
// using incompatible upper and lower bounds yields no results
|
|
EXPECT_EQ(Count(Vertices(Inclusive(2), Inclusive("string"))), 0);
|
|
|
|
// for incomparable bound and stored data,
|
|
// expect that no results are returned
|
|
ASSERT_EQ(Count(Vertices(Inclusive(0), nullopt)), 100);
|
|
for (PropertyValue value : incompatible_with_int) {
|
|
::testing::FLAGS_gtest_death_test_style = "threadsafe";
|
|
EXPECT_EQ(Count(Vertices(nullopt, Inclusive(value))), 0)
|
|
<< "Found vertices of type int for predicate value type: "
|
|
<< value.type();
|
|
EXPECT_EQ(Count(Vertices(Inclusive(value), nullopt)), 0)
|
|
<< "Found vertices of type int for predicate value type: "
|
|
<< value.type();
|
|
}
|
|
|
|
// we can compare int to double
|
|
EXPECT_EQ(Count(Vertices(nullopt, Inclusive(1000.0))), 100);
|
|
EXPECT_EQ(Count(Vertices(Inclusive(0.0), nullopt)), 100);
|
|
}
|