c8340ad120
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
446 lines
16 KiB
C++
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);
|
|
}
|