9f460914ed
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
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::unique_ptr<database::GraphDbAccessor> dba{db.Access()};
|
|
storage::Property property = dba->Property("property");
|
|
storage::Label label = dba->Label("label");
|
|
storage::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 = db.Access();
|
|
}
|
|
};
|
|
|
|
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]() {
|
|
auto dba = db.Access();
|
|
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);
|
|
}
|