GraphDbAccessor - index range API

Summary:
- GraphDbAccessor - index range API added
- index api tests refactored
- skiplist minor cleanup.

Reviewers: teon.banek, buda, mislav.bradac

Reviewed By: teon.banek

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D533
This commit is contained in:
florijan 2017-07-13 11:24:18 +02:00
parent da0fce1a84
commit a7d8b7124e
9 changed files with 492 additions and 386 deletions

View File

@ -517,14 +517,29 @@ class SkipList : private Lockable<lock_t> {
return skiplist->reverse(item);
}
template <class K>
ConstIterator find_or_larger(const K &item) const {
return static_cast<const SkipList &>(*skiplist).find_or_larger(item);
/**
* Returns an iterator pointing to the element equal to
* item, or the first larger element.
*
* @param item An item that is comparable to skiplist element type.
* @tparam TItem item type
*/
template <class TItem>
Iterator find_or_larger(const TItem &item) {
return skiplist->find_or_larger<Iterator, TItem>(item);
}
template <class It, class K>
It find_or_larger(const K &item) {
return skiplist->find_or_larger<It, K>(item);
/**
* Returns an iterator pointing to the element equal to
* item, or the first larger element.
*
* @param item An item that is comparable to skiplist element type.
* @tparam TItem item type
*/
template <class TItem>
ConstIterator find_or_larger(const TItem &item) const {
return static_cast<const SkipList &>(*skiplist)
.find_or_larger<ConstIterator, TItem>(item);
}
/**

View File

@ -100,6 +100,12 @@ int64_t GraphDbAccessor::vertices_count(
debug_assert(db_.label_property_index_.IndexExists(key),
"Index doesn't exist.");
debug_assert(lower || upper, "At least one bound must be provided");
debug_assert(
!lower || lower.value().value().type() != PropertyValue::Type::Null,
"Null value is not a valid index bound");
debug_assert(
!upper || upper.value().value().type() != PropertyValue::Type::Null,
"Null value is not a valid index bound");
if (!upper) {
auto lower_pac =

View File

@ -185,6 +185,48 @@ class GraphDbAccessor {
*transaction_, current_state));
}
/**
* Return an iterable over VertexAccessors which contain the
* given label and whose property value (for the given property)
* falls within the given (lower, upper) @c Bound.
*
* The returned iterator will only contain
* vertices/edges whose property value is comparable with the
* given bounds (w.r.t. type). This has implications on Cypher
* query execuction semantics which have not been resovled yet.
*
* At least one of the bounds must be specified. Bonds can't be
* @c PropertyValue::Null. If both bounds are
* specified, their PropertyValue elments must be of comparable
* types.
*
* @param label - label for which to return VertexAccessors
* @param property - property for which to return VertexAccessors
* @param lower - Lower bound of the interval.
* @param upper - Upper bound of the interval.
* @param value - property value for which to return VertexAccessors
* @param current_state If true then the graph state for the
* current transaction+command is returned (insertions, updates and
* deletions performed in the current transaction+command are not
* ignored).
* @return iterable collection of record accessors
* satisfy the bounds and are visible to the current transaction.
*/
auto vertices(
const GraphDbTypes::Label &label, const GraphDbTypes::Property &property,
const std::experimental::optional<utils::Bound<PropertyValue>> lower,
const std::experimental::optional<utils::Bound<PropertyValue>> upper,
bool current_state) {
debug_assert(db_.label_property_index_.IndexExists(
LabelPropertyIndex::Key(label, property)),
"Label+property index doesn't exist.");
return iter::imap([this, current_state](
auto vlist) { return VertexAccessor(*vlist, *this); },
db_.label_property_index_.GetVlists(
LabelPropertyIndex::Key(label, property), lower,
upper, *transaction_, current_state));
}
/**
* Creates a new Edge and returns an accessor to it.
*
@ -366,8 +408,9 @@ class GraphDbAccessor {
/**
* Returns approximate number of vertices that have the given label
* and whose vaue is in the range defined by upper and lower @c Bound.
* At least one bound must be specified. If lower bound is not specified,
* the whole upper bound prefix is returned.
*
* At least one bound must be specified. Neither can be
* PropertyValue::Null.
*
* Assumes that an index for that (label, property) exists.
*/

View File

@ -1,5 +1,7 @@
#pragma once
#include <experimental/optional>
#include "data_structures/concurrent/concurrent_map.hpp"
#include "database/graph_db.hpp"
#include "database/graph_db_datatypes.hpp"
@ -8,6 +10,7 @@
#include "storage/edge.hpp"
#include "storage/vertex.hpp"
#include "transactions/transaction.hpp"
#include "utils/bound.hpp"
#include "utils/total_ordering.hpp"
/**
@ -195,9 +198,10 @@ class LabelPropertyIndex {
const tx::Transaction &t, bool current_state) {
debug_assert(ready_for_use_.access().contains(key), "Index not yet ready.");
auto access = GetKeyStorage(key)->access();
auto start_iter =
access.find_or_larger<typename SkipList<IndexEntry>::Iterator,
IndexEntry>(IndexEntry(value, nullptr, nullptr));
auto min_ptr = std::numeric_limits<std::uintptr_t>::min();
auto start_iter = access.find_or_larger(IndexEntry(
value, reinterpret_cast<mvcc::VersionList<Vertex> *>(min_ptr),
reinterpret_cast<const Vertex *>(min_ptr)));
return IndexUtils::GetVlists<typename SkipList<IndexEntry>::Iterator,
IndexEntry, Vertex>(
std::move(access), start_iter,
@ -212,6 +216,97 @@ class LabelPropertyIndex {
current_state);
}
/**
* @brief - Get an iterable over all mvcc::VersionLists that
* are contained in this index and satisfy the given bounds.
*
* The returned iterator will only contain
* vertices/edges whose property value is comparable with the
* given bounds (w.r.t. type). This has implications on Cypher
* query execuction semantics which have not been resolved yet.
*
* At least one of the bounds must be specified. Bounds can't be
* @c PropertyValue::Null. If both bounds are
* specified, their PropertyValue elments must be of comparable
* types.
*
* @param key - Label+Property to query.
* @param lower - Lower bound of the interval.
* @param upper - Upper bound of the interval.
* @param t - current transaction, which determines visibility.
* @param current_state If true then the graph state for the
* current transaction+command is returned (insertions, updates and
* deletions performed in the current transaction+command are not
* ignored).
* @return iterable collection of mvcc:VersionLists pointers that
* satisfy the bounds and are visible to the given transaction.
*/
auto GetVlists(
const Key &key,
const std::experimental::optional<utils::Bound<PropertyValue>> lower,
const std::experimental::optional<utils::Bound<PropertyValue>> upper,
const tx::Transaction &transaction, bool current_state) {
debug_assert(ready_for_use_.access().contains(key), "Index not yet ready.");
auto type = [](const auto &bound) { return bound.value().value().type(); };
debug_assert(lower || upper, "At least one bound must be provided");
debug_assert(!lower || type(lower) != PropertyValue::Type::Null,
"Null value is not a valid index bound");
debug_assert(!upper || type(upper) != PropertyValue::Type::Null,
"Null value is not a valid index bound");
// helper function for creating a bound with an IndexElement
auto make_index_bound = [](const auto &optional_bound, bool bottom) {
std::uintptr_t ptr_bound =
bottom ? std::numeric_limits<std::uintptr_t>::min()
: std::numeric_limits<std::uintptr_t>::max();
return IndexEntry(
optional_bound.value().value(),
reinterpret_cast<mvcc::VersionList<Vertex> *>(ptr_bound),
reinterpret_cast<const Vertex *>(ptr_bound));
};
auto access = GetKeyStorage(key)->access();
// create the iterator startpoint based on the lower bound
auto start_iter = lower
? access.find_or_larger(make_index_bound(
lower, lower.value().IsInclusive()))
: access.begin();
// a function that defines if an entry staisfies the filtering predicate.
// since we already handled the lower bound, we only need to deal with the
// upper bound and value type
std::function<bool(const IndexEntry &entry)> predicate;
if (lower && upper &&
!PropertyValue::AreComparableTypes(type(lower), type(upper)))
predicate = [](const IndexEntry &) { return false; };
else if (upper) {
auto upper_index_entry =
make_index_bound(upper, upper.value().IsExclusive());
predicate = [upper_index_entry](const IndexEntry &entry) {
return PropertyValue::AreComparableTypes(
entry.value_.type(), upper_index_entry.value_.type()) &&
entry < upper_index_entry;
};
} else {
auto lower_type = type(lower);
make_index_bound(lower, lower.value().IsExclusive());
predicate = [lower_type](const IndexEntry &entry) {
return PropertyValue::AreComparableTypes(entry.value_.type(),
lower_type);
};
}
return IndexUtils::GetVlists<typename SkipList<IndexEntry>::Iterator,
IndexEntry, Vertex>(
std::move(access), start_iter, predicate, transaction,
[key](const IndexEntry &entry, const Vertex *const vertex) {
return LabelPropertyIndex::Exists(key, entry.value_, vertex);
},
current_state);
}
/**
* @brief - Check for existance of index.
* @param key - Index key
@ -328,8 +423,7 @@ class LabelPropertyIndex {
* than the second one
*/
static bool Less(const PropertyValue &a, const PropertyValue &b) {
if (a.type() != b.type() &&
!(IsCastableToDouble(a) && IsCastableToDouble(b)))
if (!PropertyValue::AreComparableTypes(a.type(), b.type()))
return a.type() < b.type();
if (a.type() == b.type()) {
@ -356,30 +450,18 @@ class LabelPropertyIndex {
}
}
// helper for getting a double from PropertyValue, if possible
auto get_double = [](const PropertyValue &value) {
debug_assert(value.type() == PropertyValue::Type::Int ||
value.type() == PropertyValue::Type::Double,
"Invalid data type.");
if (value.type() == PropertyValue::Type::Int)
return static_cast<double>(value.Value<int64_t>());
return value.Value<double>();
};
// Types are int and double - convert int to double
return GetDouble(a) < GetDouble(b);
}
/**
* @brief - Return value casted to double. This is only possible for
* integers and doubles.
*/
static double GetDouble(const PropertyValue &value) {
debug_assert(value.type() == PropertyValue::Type::Int ||
value.type() == PropertyValue::Type::Double,
"Invalid data type.");
if (value.type() == PropertyValue::Type::Int)
return static_cast<double>(value.Value<int64_t>());
return value.Value<double>();
}
/**
* @brief - Return if this value is castable to double (returns true for
* integers and doubles).
*/
static bool IsCastableToDouble(const PropertyValue &value) {
return value.type() == PropertyValue::Type::Int ||
value.type() == PropertyValue::Type::Double;
return get_double(a) < get_double(b);
}
/**

View File

@ -297,21 +297,14 @@ std::unique_ptr<Cursor> ScanAllByLabelPropertyRange::MakeCursor(
auto vertices = [this, &db, is_less](Frame &frame,
const SymbolTable &symbol_table) {
ExpressionEvaluator evaluator(frame, symbol_table, db, graph_view_);
auto lower_val = lower_bound_ ? lower_bound_->value()->Accept(evaluator)
: TypedValue::Null;
auto upper_val = upper_bound_ ? upper_bound_->value()->Accept(evaluator)
: TypedValue::Null;
return iter::filter(
[this, lower_val, upper_val, is_less](const VertexAccessor &vertex) {
TypedValue value = vertex.PropsAt(property_);
debug_assert(!value.IsNull(), "Unexpected property with Null value");
if (lower_bound_ && is_less(value, lower_val, lower_bound_->type()))
return false;
if (upper_bound_ && is_less(upper_val, value, upper_bound_->type()))
return false;
return true;
},
db.vertices(label_, property_, graph_view_ == GraphView::NEW));
auto convert = [&evaluator](const auto &bound)
-> std::experimental::optional<utils::Bound<PropertyValue>> {
if (!bound) return std::experimental::nullopt;
return std::experimental::make_optional(utils::Bound<PropertyValue>(
bound.value().value()->Accept(evaluator), bound.value().type()));
};
return db.vertices(label_, property_, convert(lower_bound()),
convert(upper_bound()), graph_view_ == GraphView::NEW);
};
return std::make_unique<ScanAllCursor<decltype(vertices)>>(
output_symbol_, input_->MakeCursor(db), std::move(vertices), db);

View File

@ -28,6 +28,15 @@ class PropertyValue {
// single static reference to Null, used whenever Null should be returned
static const PropertyValue Null;
/** Checks if the given PropertyValue::Types are comparable */
static bool AreComparableTypes(Type a, Type b) {
auto is_numeric = [](const Type t) {
return t == Type::Int || t == Type::Double;
};
return a == b || (is_numeric(a) && is_numeric(b));
}
// constructors for primitive types
PropertyValue(bool value) : type_(Type::Bool) { bool_v = value; }
PropertyValue(int value) : type_(Type::Int) { int_v = value; }

View File

@ -25,7 +25,7 @@ TEST(SkipList, HangDuringFindOrLarger) {
threads.emplace_back([&iter, &skiplist]() {
auto accessor = skiplist.access();
for (int i = 0; i < iter; ++i)
accessor.find_or_larger<SkipList<int>::ConstIterator>(rand() % 3);
accessor.find_or_larger(rand() % 3);
});
}
for (auto &thread : threads) thread.join();

View File

@ -15,100 +15,143 @@ auto Count(TIterable iterable) {
return std::distance(iterable.begin(), iterable.end());
}
TEST(GraphDbAccessor, VertexByLabelCount) {
/**
* A test fixture that contains a database, accessor,
* label, property and an edge_type.
*/
class GraphDbAccessorIndex : public testing::Test {
protected:
Dbms dbms;
auto dba = dbms.active();
auto lab1 = dba->label("lab1");
auto lab2 = dba->label("lab2");
std::unique_ptr<GraphDbAccessor> dba = dbms.active();
GraphDbTypes::Property property = dba->property("property");
GraphDbTypes::Label label = dba->label("label");
GraphDbTypes::EdgeType edge_type = dba->edge_type("edge_type");
EXPECT_EQ(dba->vertices_count(lab1), 0);
EXPECT_EQ(dba->vertices_count(lab2), 0);
auto AddVertex() {
auto vertex = dba->insert_vertex();
vertex.add_label(label);
return vertex;
}
auto AddVertex(int property_value) {
auto vertex = dba->insert_vertex();
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();
auto dba2 = dbms.active();
dba.swap(dba2);
}
};
TEST_F(GraphDbAccessorIndex, LabelIndexCount) {
auto label2 = dba->label("label2");
EXPECT_EQ(dba->vertices_count(label), 0);
EXPECT_EQ(dba->vertices_count(label2), 0);
EXPECT_EQ(dba->vertices_count(), 0);
for (int i = 0; i < 11; ++i) dba->insert_vertex().add_label(lab1);
for (int i = 0; i < 17; ++i) dba->insert_vertex().add_label(lab2);
for (int i = 0; i < 11; ++i) dba->insert_vertex().add_label(label);
for (int i = 0; i < 17; ++i) dba->insert_vertex().add_label(label2);
// even though xxx_count functions in GraphDbAccessor can over-estaimate
// in this situation they should be exact (nothing was ever deleted)
EXPECT_EQ(dba->vertices_count(lab1), 11);
EXPECT_EQ(dba->vertices_count(lab2), 17);
EXPECT_EQ(dba->vertices_count(label), 11);
EXPECT_EQ(dba->vertices_count(label2), 17);
EXPECT_EQ(dba->vertices_count(), 28);
}
TEST(GraphDbAccessor, VertexByLabelPropertyCount) {
Dbms dbms;
auto dba = dbms.active();
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);
auto lab1 = dba->label("lab1");
auto lab2 = dba->label("lab2");
auto prop1 = dba->property("prop1");
auto prop2 = dba->property("prop2");
dba->BuildIndex(lab1, prop1);
dba->BuildIndex(lab1, prop2);
dba->BuildIndex(lab2, prop1);
dba->BuildIndex(lab2, prop2);
EXPECT_EQ(dba->vertices_count(lab1, prop1), 0);
EXPECT_EQ(dba->vertices_count(lab1, prop2), 0);
EXPECT_EQ(dba->vertices_count(lab2, prop1), 0);
EXPECT_EQ(dba->vertices_count(lab2, prop2), 0);
EXPECT_EQ(dba->vertices_count(), 0);
for (int i = 0; i < 14; ++i) {
VertexAccessor vertex = dba->insert_vertex();
vertex.add_label(lab1);
vertex.PropsSet(prop1, 1);
// remove 3 vertices, check visibility
int deleted = 0;
for (auto vertex : dba->vertices(false)) {
dba->remove_vertex(vertex);
if (++deleted >= 3) break;
}
for (int i = 0; i < 15; ++i) {
auto vertex = dba->insert_vertex();
vertex.add_label(lab1);
vertex.PropsSet(prop2, 2);
}
for (int i = 0; i < 16; ++i) {
auto vertex = dba->insert_vertex();
vertex.add_label(lab2);
vertex.PropsSet(prop1, 3);
}
for (int i = 0; i < 17; ++i) {
auto vertex = dba->insert_vertex();
vertex.add_label(lab2);
vertex.PropsSet(prop2, 4);
}
// even though xxx_count functions in GraphDbAccessor can over-estimate
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, EdgeTypeCount) {
auto edge_type2 = dba->edge_type("edge_type2");
EXPECT_EQ(dba->edges_count(edge_type), 0);
EXPECT_EQ(dba->edges_count(edge_type2), 0);
EXPECT_EQ(dba->edges_count(), 0);
auto v1 = AddVertex();
auto v2 = AddVertex();
for (int i = 0; i < 11; ++i) dba->insert_edge(v1, v2, edge_type);
for (int i = 0; i < 17; ++i) dba->insert_edge(v1, v2, edge_type2);
// even though xxx_count functions in GraphDbAccessor can over-estaimate
// in this situation they should be exact (nothing was ever deleted)
EXPECT_EQ(dba->vertices_count(lab1, prop1), 14);
EXPECT_EQ(dba->vertices_count(lab1, prop2), 15);
EXPECT_EQ(dba->vertices_count(lab2, prop1), 16);
EXPECT_EQ(dba->vertices_count(lab2, prop2), 17);
EXPECT_EQ(dba->vertices_count(), 14 + 15 + 16 + 17);
EXPECT_EQ(dba->edges_count(edge_type), 11);
EXPECT_EQ(dba->edges_count(edge_type2), 17);
EXPECT_EQ(dba->edges_count(), 28);
}
TEST_F(GraphDbAccessorIndex, LabelPropertyIndexBuild) {
AddVertex(0);
::testing::FLAGS_gtest_death_test_style = "threadsafe";
EXPECT_DEATH(dba->vertices_count(label, property), "Index doesn't exist.");
Commit();
dba->BuildIndex(label, property);
Commit();
EXPECT_EQ(dba->vertices_count(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->vertices_count(label, property), 1);
EXPECT_EQ(dba->vertices_count(label2, property), 0);
EXPECT_EQ(dba->vertices_count(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->vertices_count(label, property), 0);
EXPECT_EQ(Count(dba->vertices(label, property)), 0);
for (int i = 0; i < 14; ++i) AddVertex(0);
EXPECT_EQ(dba->vertices_count(label, property), 14);
EXPECT_EQ(Count(dba->vertices(label, property)), 14);
}
#define EXPECT_WITH_MARGIN(x, center) \
EXPECT_THAT( \
x, testing::AllOf(testing::Ge(center - 2), testing::Le(center + 2)));
TEST(GraphDbAccessor, VertexByLabelPropertyValueCount) {
Dbms dbms;
auto dba = dbms.active();
auto label = dba->label("label");
auto property = dba->property("property");
TEST_F(GraphDbAccessorIndex, LabelPropertyValueCount) {
dba->BuildIndex(label, property);
// add some vertices without the property
for (int i = 0; i < 20; i++) dba->insert_vertex();
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++) {
auto vertex = dba->insert_vertex();
vertex.add_label(label);
vertex.PropsSet(property, i / 10);
}
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++) {
auto vertex = dba->insert_vertex();
vertex.add_label(label);
vertex.PropsSet(property, 30 + i / 100);
}
for (int i = 0; i < 1000; i++) AddVertex(30 + i / 100);
// test estimates for exact value count
EXPECT_WITH_MARGIN(dba->vertices_count(label, property, 10), 10);
@ -126,127 +169,47 @@ TEST(GraphDbAccessor, VertexByLabelPropertyValueCount) {
return std::experimental::make_optional(
utils::MakeBoundExclusive(PropertyValue(value)));
};
auto Count = [&dba, label, property](auto lower, auto upper) {
auto vertices_count = [this](auto lower, auto upper) {
return dba->vertices_count(label, property, lower, upper);
};
using std::experimental::nullopt;
EXPECT_DEATH(Count(nullopt, nullopt), "bound must be provided");
EXPECT_WITH_MARGIN(Count(nullopt, Exclusive(4)), 40);
EXPECT_WITH_MARGIN(Count(nullopt, Inclusive(4)), 50);
EXPECT_WITH_MARGIN(Count(Exclusive(13), nullopt), 160 + 1000);
EXPECT_WITH_MARGIN(Count(Inclusive(13), nullopt), 170 + 1000);
EXPECT_WITH_MARGIN(Count(Inclusive(13), Exclusive(14)), 10);
EXPECT_WITH_MARGIN(Count(Exclusive(13), Inclusive(14)), 10);
EXPECT_WITH_MARGIN(Count(Exclusive(13), Exclusive(13)), 0);
EXPECT_WITH_MARGIN(Count(Inclusive(20), Exclusive(13)), 0);
::testing::FLAGS_gtest_death_test_style = "threadsafe";
EXPECT_DEATH(vertices_count(nullopt, nullopt), "bound must be provided");
EXPECT_WITH_MARGIN(vertices_count(nullopt, Exclusive(4)), 40);
EXPECT_WITH_MARGIN(vertices_count(nullopt, Inclusive(4)), 50);
EXPECT_WITH_MARGIN(vertices_count(Exclusive(13), nullopt), 160 + 1000);
EXPECT_WITH_MARGIN(vertices_count(Inclusive(13), nullopt), 170 + 1000);
EXPECT_WITH_MARGIN(vertices_count(Inclusive(13), Exclusive(14)), 10);
EXPECT_WITH_MARGIN(vertices_count(Exclusive(13), Inclusive(14)), 10);
EXPECT_WITH_MARGIN(vertices_count(Exclusive(13), Exclusive(13)), 0);
EXPECT_WITH_MARGIN(vertices_count(Inclusive(20), Exclusive(13)), 0);
}
#undef EXPECT_WITH_MARGIN
TEST(GraphDbAccessor, EdgeByEdgeTypeCount) {
Dbms dbms;
auto dba = dbms.active();
auto t1 = dba->edge_type("t1");
auto t2 = dba->edge_type("t2");
EXPECT_EQ(dba->edges_count(t1), 0);
EXPECT_EQ(dba->edges_count(t2), 0);
EXPECT_EQ(dba->edges_count(), 0);
auto v1 = dba->insert_vertex();
auto v2 = dba->insert_vertex();
for (int i = 0; i < 11; ++i) dba->insert_edge(v1, v2, t1);
for (int i = 0; i < 17; ++i) dba->insert_edge(v1, v2, t2);
// even though xxx_count functions in GraphDbAccessor can over-estaimate
// in this situation they should be exact (nothing was ever deleted)
EXPECT_EQ(dba->edges_count(t1), 11);
EXPECT_EQ(dba->edges_count(t2), 17);
EXPECT_EQ(dba->edges_count(), 28);
}
// Check if build index adds old vertex entries (ones before the index was
// created)
TEST(GraphDbAccessor, BuildIndexOnOld) {
Dbms dbms;
auto dba = dbms.active();
auto label = dba->label("lab1");
auto property = dba->property("prop1");
auto vertex_accessor = dba->insert_vertex();
vertex_accessor.add_label(label);
vertex_accessor.PropsSet(property, 0);
::testing::FLAGS_gtest_death_test_style = "threadsafe";
EXPECT_DEATH(dba->vertices_count(label, property), "Index doesn't exist.");
dba->commit();
auto dba2 = dbms.active();
dba2->BuildIndex(label, property);
dba2->commit();
auto dba3 = dbms.active();
// Index is built and vertex is automatically added inside
EXPECT_EQ(dba3->vertices_count(label, property), 1);
EXPECT_EQ(Count(dba3->vertices(label, property)), 1);
dba3->commit();
}
// Try to build index two times
TEST(GraphDbAccessor, BuildIndexDouble) {
Dbms dbms;
auto dba = dbms.active();
auto label = dba->label("lab1");
auto property = dba->property("prop1");
TEST_F(GraphDbAccessorIndex, LabelPropertyValueIteration) {
dba->BuildIndex(label, property);
EXPECT_THROW(dba->BuildIndex(label, property), utils::BasicException);
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);
}
// Inserts vertices with properties with integers and filters to get exact
// vertices with an exact integer.
TEST(GraphDbAccessor, FilterLabelPropertySpecificValue) {
Dbms dbms;
auto dba = dbms.active();
auto label = dba->label("lab1");
auto property = dba->property("prop1");
TEST_F(GraphDbAccessorIndex, LabelPropertyValueSorting) {
dba->BuildIndex(label, property);
dba->commit();
Commit();
auto dba2 = dbms.active();
for (int i = 1; i <= 5; ++i) {
for (int j = 1; j <= i; ++j) {
auto vertex = dba2->insert_vertex();
vertex.add_label(label);
vertex.PropsSet(property, i);
}
}
dba2->commit();
auto dba3 = dbms.active();
for (int i = 1; i <= 5; ++i)
EXPECT_EQ(Count(dba3->vertices(label, property, PropertyValue(i), false)),
i);
}
// Inserts integers, double, lists, booleans into index and check if they
// are
// sorted as they should be sorted.
TEST(GraphDbAccessor, SortedLabelPropertyEntries) {
Dbms dbms;
auto dba = dbms.active();
auto label = dba->label("lab1");
auto property = dba->property("prop1");
dba->BuildIndex(label, property);
dba->commit();
auto dba2 = dbms.active();
std::vector<PropertyValue> expected_property_value(50, 0);
// strings
for (int i = 0; i < 10; ++i) {
auto vertex_accessor = dba2->insert_vertex();
auto vertex_accessor = dba->insert_vertex();
vertex_accessor.add_label(label);
vertex_accessor.PropsSet(property,
static_cast<std::string>(std::to_string(i)));
@ -254,7 +217,7 @@ TEST(GraphDbAccessor, SortedLabelPropertyEntries) {
}
// bools - insert in reverse to check for comparison between values.
for (int i = 9; i >= 0; --i) {
auto vertex_accessor = dba2->insert_vertex();
auto vertex_accessor = dba->insert_vertex();
vertex_accessor.add_label(label);
vertex_accessor.PropsSet(property, static_cast<bool>(i / 5));
expected_property_value[10 + i] = vertex_accessor.PropsAt(property);
@ -262,14 +225,14 @@ TEST(GraphDbAccessor, SortedLabelPropertyEntries) {
// integers
for (int i = 0; i < 10; ++i) {
auto vertex_accessor = dba2->insert_vertex();
auto vertex_accessor = dba->insert_vertex();
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 = dba2->insert_vertex();
auto vertex_accessor = dba->insert_vertex();
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);
@ -278,7 +241,7 @@ TEST(GraphDbAccessor, SortedLabelPropertyEntries) {
// lists of ints - insert in reverse to check for comparision between
// lists.
for (int i = 9; i >= 0; --i) {
auto vertex_accessor = dba2->insert_vertex();
auto vertex_accessor = dba->insert_vertex();
vertex_accessor.add_label(label);
std::vector<PropertyValue> value;
value.push_back(PropertyValue(i));
@ -286,11 +249,11 @@ TEST(GraphDbAccessor, SortedLabelPropertyEntries) {
expected_property_value[40 + i] = vertex_accessor.PropsAt(property);
}
EXPECT_EQ(Count(dba2->vertices(label, property, false)), 0);
EXPECT_EQ(Count(dba2->vertices(label, property, true)), 50);
EXPECT_EQ(Count(dba->vertices(label, property, false)), 0);
EXPECT_EQ(Count(dba->vertices(label, property, true)), 50);
int cnt = 0;
for (auto vertex : dba2->vertices(label, property, true)) {
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()) {
@ -328,72 +291,92 @@ TEST(GraphDbAccessor, SortedLabelPropertyEntries) {
}
}
TEST(GraphDbAccessor, VisibilityAfterInsertion) {
Dbms dbms;
auto dba = dbms.active();
auto v1 = dba->insert_vertex();
auto v2 = dba->insert_vertex();
auto lab1 = dba->label("lab1");
auto lab2 = dba->label("lab2");
v1.add_label(lab1);
auto type1 = dba->edge_type("type1");
auto type2 = dba->edge_type("type2");
dba->insert_edge(v1, v2, type1);
/**
* A test fixture that contains a database, accessor,
* (label, property) index and 100 vertices, 10 for
* each of [0, 10) property values.
*/
class GraphDbAccesssorIndexRange : public GraphDbAccessorIndex {
protected:
void SetUp() override {
dba->BuildIndex(label, property);
for (int i = 0; i < 100; i++) AddVertex(i / 10);
EXPECT_EQ(Count(dba->vertices(lab1, false)), 0);
EXPECT_EQ(Count(dba->vertices(lab1, true)), 1);
EXPECT_EQ(Count(dba->vertices(lab2, false)), 0);
EXPECT_EQ(Count(dba->vertices(lab2, true)), 0);
EXPECT_EQ(Count(dba->edges(type1, false)), 0);
EXPECT_EQ(Count(dba->edges(type1, true)), 1);
EXPECT_EQ(Count(dba->edges(type2, false)), 0);
EXPECT_EQ(Count(dba->edges(type2, true)), 0);
dba->advance_command();
EXPECT_EQ(Count(dba->vertices(lab1, false)), 1);
EXPECT_EQ(Count(dba->vertices(lab1, true)), 1);
EXPECT_EQ(Count(dba->vertices(lab2, false)), 0);
EXPECT_EQ(Count(dba->vertices(lab2, true)), 0);
EXPECT_EQ(Count(dba->edges(type1, false)), 1);
EXPECT_EQ(Count(dba->edges(type1, true)), 1);
EXPECT_EQ(Count(dba->edges(type2, false)), 0);
EXPECT_EQ(Count(dba->edges(type2, true)), 0);
}
TEST(GraphDbAccessor, VisibilityAfterDeletion) {
Dbms dbms;
auto dba = dbms.active();
auto lab = dba->label("lab");
for (int i = 0; i < 5; ++i) dba->insert_vertex().add_label(lab);
dba->advance_command();
auto type = dba->edge_type("type");
for (int j = 0; j < 3; ++j) {
auto vertices_it = dba->vertices(false).begin();
dba->insert_edge(*vertices_it++, *vertices_it, type);
ASSERT_EQ(Count(dba->vertices(false)), 0);
ASSERT_EQ(Count(dba->vertices(true)), 100);
Commit();
ASSERT_EQ(Count(dba->vertices(false)), 100);
}
dba->advance_command();
EXPECT_EQ(Count(dba->vertices(lab, false)), 5);
EXPECT_EQ(Count(dba->vertices(lab, true)), 5);
EXPECT_EQ(Count(dba->edges(type, false)), 3);
EXPECT_EQ(Count(dba->edges(type, true)), 3);
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);
}
// delete two edges
auto edges_it = dba->edges(false).begin();
for (int k = 0; k < 2; ++k) dba->remove_edge(*edges_it++);
EXPECT_EQ(Count(dba->edges(type, false)), 3);
EXPECT_EQ(Count(dba->edges(type, true)), 1);
dba->advance_command();
EXPECT_EQ(Count(dba->edges(type, false)), 1);
EXPECT_EQ(Count(dba->edges(type, true)), 1);
auto Inclusive(PropertyValue value) {
return std::experimental::make_optional(
utils::MakeBoundInclusive(PropertyValue(value)));
}
// detach-delete 2 vertices
auto vertices_it = dba->vertices(false).begin();
for (int k = 0; k < 2; ++k) dba->detach_remove_vertex(*vertices_it++);
EXPECT_EQ(Count(dba->vertices(lab, false)), 5);
EXPECT_EQ(Count(dba->vertices(lab, true)), 3);
dba->advance_command();
EXPECT_EQ(Count(dba->vertices(lab, false)), 3);
EXPECT_EQ(Count(dba->vertices(lab, true)), 3);
auto Exclusive(int value) {
return std::experimental::make_optional(
utils::MakeBoundExclusive(PropertyValue(value)));
}
};
TEST_F(GraphDbAccesssorIndexRange, 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(GraphDbAccesssorIndexRange, 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(GraphDbAccesssorIndexRange, 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);
}

View File

@ -840,104 +840,79 @@ TEST(QueryPlan, ScanAllByLabelProperty) {
// Add 5 vertices with same label, but with different property values.
auto label = dba->label("label");
auto prop = dba->property("prop");
for (int i = 1; i <= 5; ++i) {
// vertex property values that will be stored into the DB
// clang-format off
std::vector<TypedValue> values{
true, false, "a", "b", "c", 0, 1, 2, 0.5, 1.5, 2.5,
std::vector<TypedValue>{0}, std::vector<TypedValue>{1},
std::vector<TypedValue>{2}};
// clang-format on
for (const auto &value : values) {
auto vertex = dba->insert_vertex();
vertex.add_label(label);
vertex.PropsSet(prop, i);
vertex.PropsSet(prop, value);
}
dba->commit();
dba = dbms.active();
dba->BuildIndex(label, prop);
dba = dbms.active();
EXPECT_EQ(5, CountIterable(dba->vertices(false)));
// MATCH (n :label) WHERE 2 <= n.prop < 4
AstTreeStorage storage;
SymbolTable symbol_table;
auto scan_all = MakeScanAllByLabelPropertyRange(
storage, symbol_table, "n", label, prop,
Bound{LITERAL(2), Bound::Type::INCLUSIVE},
Bound{LITERAL(4), Bound::Type::EXCLUSIVE});
// RETURN n
auto output = NEXPR("n", IDENT("n"));
auto produce = MakeProduce(scan_all.op_, output);
symbol_table[*output->expression_] = scan_all.sym_;
symbol_table[*output] = symbol_table.CreateSymbol("n", true);
auto results = CollectProduce(produce.get(), symbol_table, *dba);
EXPECT_EQ(results.size(), 2);
for (const auto &row : results) {
ASSERT_EQ(row.size(), 1);
auto vertex = row[0].Value<VertexAccessor>();
auto value = vertex.PropsAt(prop);
TypedValue::BoolEqual eq;
EXPECT_TRUE(eq(value, 2) || eq(value, 3));
}
}
TEST(QueryPlan, ScanAllByLabelPropertyCompareError) {
Dbms dbms;
auto dba = dbms.active();
// Add 2 vertices with same label, but with property values that cannot be
// compared.
auto label = dba->label("label");
auto prop = dba->property("prop");
auto number_vertex = dba->insert_vertex();
number_vertex.add_label(label);
number_vertex.PropsSet(prop, 42);
auto string_vertex = dba->insert_vertex();
string_vertex.add_label(label);
string_vertex.PropsSet(prop, "string");
dba->commit();
dba = dbms.active();
dba->BuildIndex(label, prop);
dba = dbms.active();
EXPECT_EQ(2, CountIterable(dba->vertices(false)));
// MATCH (n :label) WHERE 1 < n.prop
AstTreeStorage storage;
SymbolTable symbol_table;
auto scan_all = MakeScanAllByLabelPropertyRange(
storage, symbol_table, "n", label, prop,
Bound{LITERAL(1), Bound::Type::EXCLUSIVE}, std::experimental::nullopt);
// RETURN n
auto output = NEXPR("n", IDENT("n"));
auto produce = MakeProduce(scan_all.op_, output);
symbol_table[*output->expression_] = scan_all.sym_;
symbol_table[*output] = symbol_table.CreateSymbol("n", true);
EXPECT_THROW(CollectProduce(produce.get(), symbol_table, *dba),
QueryRuntimeException);
}
ASSERT_EQ(14, CountIterable(dba->vertices(false)));
TEST(QueryPlan, ScanAllByLabelPropertyRangeNull) {
Dbms dbms;
auto dba = dbms.active();
// Add 2 vertices with the same label, but with property values that cannot be
// compared. We are comparing to null, so no results should be produced.
auto label = dba->label("label");
auto prop = dba->property("prop");
auto number_vertex = dba->insert_vertex();
number_vertex.add_label(label);
number_vertex.PropsSet(prop, 42);
auto string_vertex = dba->insert_vertex();
string_vertex.add_label(label);
string_vertex.PropsSet(prop, "string");
dba->commit();
dba = dbms.active();
dba->BuildIndex(label, prop);
dba = dbms.active();
EXPECT_EQ(2, CountIterable(dba->vertices(false)));
// MATCH (n :label) WHERE null < n.prop
AstTreeStorage storage;
SymbolTable symbol_table;
auto scan_all = MakeScanAllByLabelPropertyRange(
storage, symbol_table, "n", label, prop,
Bound{LITERAL(TypedValue::Null), Bound::Type::EXCLUSIVE},
std::experimental::nullopt);
// RETURN n
auto output = NEXPR("n", IDENT("n"));
auto produce = MakeProduce(scan_all.op_, output);
symbol_table[*output->expression_] = scan_all.sym_;
symbol_table[*output] = symbol_table.CreateSymbol("n", true);
auto results = CollectProduce(produce.get(), symbol_table, *dba);
EXPECT_EQ(results.size(), 0);
auto check = [&dba, label, prop](TypedValue lower, Bound::Type lower_type,
TypedValue upper, Bound::Type upper_type,
const std::vector<TypedValue> &expected) {
AstTreeStorage storage;
SymbolTable symbol_table;
auto scan_all = MakeScanAllByLabelPropertyRange(
storage, symbol_table, "n", label, prop,
Bound{LITERAL(lower), lower_type}, Bound{LITERAL(upper), upper_type});
// RETURN n
auto output = NEXPR("n", IDENT("n"));
auto produce = MakeProduce(scan_all.op_, output);
symbol_table[*output->expression_] = scan_all.sym_;
symbol_table[*output] = symbol_table.CreateSymbol("n", true);
auto results = CollectProduce(produce.get(), symbol_table, *dba);
ASSERT_EQ(results.size(), expected.size());
for (int i = 0; i < expected.size(); i++) {
TypedValue equal =
results[i][0].Value<VertexAccessor>().PropsAt(prop) == expected[i];
ASSERT_EQ(equal.type(), TypedValue::Type::Bool);
EXPECT_TRUE(equal.Value<bool>());
}
};
// normal ranges that return something
check(false, Bound::Type::INCLUSIVE, true, Bound::Type::EXCLUSIVE, {false});
check(false, Bound::Type::EXCLUSIVE, true, Bound::Type::INCLUSIVE, {true});
check("a", Bound::Type::EXCLUSIVE, "c", Bound::Type::EXCLUSIVE, {"b"});
check(0, Bound::Type::EXCLUSIVE, 2, Bound::Type::INCLUSIVE, {0.5, 1, 1.5, 2});
check(1.5, Bound::Type::EXCLUSIVE, 2.5, Bound::Type::INCLUSIVE, {2, 2.5});
check(std::vector<TypedValue>{0.5}, Bound::Type::EXCLUSIVE,
std::vector<TypedValue>{1.5}, Bound::Type::INCLUSIVE,
{TypedValue(std::vector<TypedValue>{1})});
// when a range contains different types, nothing should get returned
for (const auto &value_a : values)
for (const auto &value_b : values) {
if (PropertyValue::AreComparableTypes(
static_cast<PropertyValue>(value_a).type(),
static_cast<PropertyValue>(value_b).type()))
continue;
check(value_a, Bound::Type::INCLUSIVE, value_b, Bound::Type::INCLUSIVE,
{});
}
// it's not allowed to have Null as a bound, we assert against that
::testing::FLAGS_gtest_death_test_style = "threadsafe";
EXPECT_DEATH(check(TypedValue::Null, Bound::Type::INCLUSIVE, 0,
Bound::Type::INCLUSIVE, {}),
"Null value is not a valid index bound");
EXPECT_DEATH(check(0, Bound::Type::INCLUSIVE, TypedValue::Null,
Bound::Type::INCLUSIVE, {}),
"Null value is not a valid index bound");
}
TEST(QueryPlan, ScanAllByLabelPropertyEqualityNoError) {