memgraph/tests/unit/graph_db_accessor_index_api.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

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);
}