memgraph/tests/unit/graph_db_accessor_index_api.cpp
Teon Banek c8340ad120 Merge old and new PropertyValue implementations
Summary:
This effectively replaces the old PropertyValue implementation from the
one in storage/v2

Depends on D2333

Reviewers: mferencevic, ipaljak

Reviewed By: mferencevic

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D2335
2019-09-02 16:43:50 +02:00

446 lines
16 KiB
C++

#include <atomic>
#include <memory>
#include <optional>
#include <thread>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "database/single_node/graph_db.hpp"
#include "database/single_node/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::GraphDb db;
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, PropertyValue(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, LabelPropertyIndexDelete) {
dba.BuildIndex(label, property);
Commit();
EXPECT_TRUE(dba.LabelPropertyIndexExists(label, property));
dba.DeleteIndex(label, property);
Commit();
EXPECT_FALSE(dba.LabelPropertyIndexExists(label, property));
}
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 THREAD_COUNT = 10;
std::vector<std::thread> threads;
database::GraphDb db;
std::atomic<bool> failed{false};
for (int index = 0; index < THREAD_COUNT; ++index) {
threads.emplace_back([&db, &failed, index]() {
// If we fail to create a new transaction, don't bother.
try {
auto dba = db.Access();
try {
// This could either pass or throw.
dba.BuildIndex(dba.Label("l" + std::to_string(index)),
dba.Property("p" + std::to_string(index)));
// If it throws, make sure the exception is right.
} catch (const database::TransactionException &e) {
// Nothing to see here, move along.
} catch (...) {
failed.store(true);
}
} catch (...) {
// Ignore this one also.
}
});
}
for (auto &thread : threads) {
if (thread.joinable()) {
thread.join();
}
}
EXPECT_FALSE(failed.load());
}
#define EXPECT_WITH_MARGIN(x, center) \
EXPECT_THAT( \
x, testing::AllOf(testing::Ge(center - 2), testing::Le(center + 2)));
template <class TValue>
auto Inclusive(TValue value) {
return std::make_optional(utils::MakeBoundInclusive(PropertyValue(value)));
}
template <class TValue>
auto Exclusive(TValue value) {
return std::make_optional(utils::MakeBoundExclusive(PropertyValue(value)));
}
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, PropertyValue(10)), 10);
EXPECT_WITH_MARGIN(dba.VerticesCount(label, property, PropertyValue(14)), 10);
EXPECT_WITH_MARGIN(dba.VerticesCount(label, property, PropertyValue(30)),
100);
EXPECT_WITH_MARGIN(dba.VerticesCount(label, property, PropertyValue(39)),
100);
EXPECT_EQ(dba.VerticesCount(label, property, PropertyValue(40)), 0);
// helper functions
auto VerticesCount = [this](auto lower, auto upper) {
return dba.VerticesCount(label, property, lower, upper);
};
using std::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, PropertyValue(12), false)), 0);
EXPECT_EQ(Count(dba.Vertices(label, property, PropertyValue(12), true)), 10);
Commit();
EXPECT_EQ(Count(dba.Vertices(label, property, PropertyValue(12), false)), 10);
EXPECT_EQ(Count(dba.Vertices(label, property, PropertyValue(12), true)), 10);
}
TEST_F(GraphDbAccessorIndex, LabelPropertyValueSorting) {
dba.BuildIndex(label, property);
Commit();
std::vector<PropertyValue> expected_property_value(50, PropertyValue(0));
// 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, PropertyValue(static_cast<bool>(i / 5)));
expected_property_value[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, PropertyValue(i));
expected_property_value[10 + 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,
PropertyValue(static_cast<double>(i + 0.5)));
expected_property_value[10 + 2 * i + 1] = vertex_accessor.PropsAt(property);
}
// strings
for (int i = 0; i < 10; ++i) {
auto vertex_accessor = dba.InsertVertex();
vertex_accessor.add_label(label);
vertex_accessor.PropsSet(property, PropertyValue(std::to_string(i)));
expected_property_value[30 + i] = 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, PropertyValue(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", PropertyValue(12)}},
{{"b", PropertyValue(12)}, {"a", PropertyValue(77)}},
{{"a", PropertyValue(77)}, {"c", PropertyValue(0)}},
{{"a", PropertyValue(78)}, {"b", PropertyValue(12)}}};
for (const auto &map : maps) expected_property_value.emplace_back(map);
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, PropertyValue(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.ValueBool(),
expected_property_value[cnt].ValueBool());
break;
case PropertyValue::Type::Double:
EXPECT_EQ(property_value.ValueDouble(),
expected_property_value[cnt].ValueDouble());
break;
case PropertyValue::Type::Int:
EXPECT_EQ(property_value.ValueInt(),
expected_property_value[cnt].ValueInt());
break;
case PropertyValue::Type::String:
EXPECT_EQ(property_value.ValueString(),
expected_property_value[cnt].ValueString());
break;
case PropertyValue::Type::List: {
auto received_value = property_value.ValueList();
auto expected_value = expected_property_value[cnt].ValueList();
EXPECT_EQ(received_value.size(), expected_value.size());
EXPECT_EQ(received_value.size(), 1);
EXPECT_EQ(received_value[0].ValueInt(), expected_value[0].ValueInt());
break;
}
case PropertyValue::Type::Map: {
auto received_value = property_value.ValueMap();
auto expected_value = expected_property_value[cnt].ValueMap();
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.ValueInt(), found->second.ValueInt());
}
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::optional<utils::Bound<PropertyValue>> lower,
std::optional<utils::Bound<PropertyValue>> upper,
bool current_state = false) {
return dba.Vertices(label, property, lower, upper, current_state);
}
};
TEST_F(GraphDbAccessorIndexRange, RangeIteration) {
using std::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::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::nullopt;
// using PropertyValue set to Null as a bound fails with an assertion
::testing::FLAGS_gtest_death_test_style = "threadsafe";
EXPECT_DEATH(Vertices(nullopt, Inclusive(PropertyValue())),
"not a valid index bound");
EXPECT_DEATH(Vertices(Inclusive(PropertyValue()), nullopt),
"not a valid index bound");
std::vector<PropertyValue> incompatible_with_int{
PropertyValue("string"), PropertyValue(true),
PropertyValue(std::vector<PropertyValue>{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);
}