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:
parent
da0fce1a84
commit
a7d8b7124e
@ -517,14 +517,29 @@ class SkipList : private Lockable<lock_t> {
|
|||||||
return skiplist->reverse(item);
|
return skiplist->reverse(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class K>
|
/**
|
||||||
ConstIterator find_or_larger(const K &item) const {
|
* Returns an iterator pointing to the element equal to
|
||||||
return static_cast<const SkipList &>(*skiplist).find_or_larger(item);
|
* 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) {
|
* Returns an iterator pointing to the element equal to
|
||||||
return skiplist->find_or_larger<It, K>(item);
|
* 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -100,6 +100,12 @@ int64_t GraphDbAccessor::vertices_count(
|
|||||||
debug_assert(db_.label_property_index_.IndexExists(key),
|
debug_assert(db_.label_property_index_.IndexExists(key),
|
||||||
"Index doesn't exist.");
|
"Index doesn't exist.");
|
||||||
debug_assert(lower || upper, "At least one bound must be provided");
|
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) {
|
if (!upper) {
|
||||||
auto lower_pac =
|
auto lower_pac =
|
||||||
|
@ -185,6 +185,48 @@ class GraphDbAccessor {
|
|||||||
*transaction_, current_state));
|
*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.
|
* 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
|
* Returns approximate number of vertices that have the given label
|
||||||
* and whose vaue is in the range defined by upper and lower @c Bound.
|
* 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.
|
* Assumes that an index for that (label, property) exists.
|
||||||
*/
|
*/
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <experimental/optional>
|
||||||
|
|
||||||
#include "data_structures/concurrent/concurrent_map.hpp"
|
#include "data_structures/concurrent/concurrent_map.hpp"
|
||||||
#include "database/graph_db.hpp"
|
#include "database/graph_db.hpp"
|
||||||
#include "database/graph_db_datatypes.hpp"
|
#include "database/graph_db_datatypes.hpp"
|
||||||
@ -8,6 +10,7 @@
|
|||||||
#include "storage/edge.hpp"
|
#include "storage/edge.hpp"
|
||||||
#include "storage/vertex.hpp"
|
#include "storage/vertex.hpp"
|
||||||
#include "transactions/transaction.hpp"
|
#include "transactions/transaction.hpp"
|
||||||
|
#include "utils/bound.hpp"
|
||||||
#include "utils/total_ordering.hpp"
|
#include "utils/total_ordering.hpp"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -195,9 +198,10 @@ class LabelPropertyIndex {
|
|||||||
const tx::Transaction &t, bool current_state) {
|
const tx::Transaction &t, bool current_state) {
|
||||||
debug_assert(ready_for_use_.access().contains(key), "Index not yet ready.");
|
debug_assert(ready_for_use_.access().contains(key), "Index not yet ready.");
|
||||||
auto access = GetKeyStorage(key)->access();
|
auto access = GetKeyStorage(key)->access();
|
||||||
auto start_iter =
|
auto min_ptr = std::numeric_limits<std::uintptr_t>::min();
|
||||||
access.find_or_larger<typename SkipList<IndexEntry>::Iterator,
|
auto start_iter = access.find_or_larger(IndexEntry(
|
||||||
IndexEntry>(IndexEntry(value, nullptr, nullptr));
|
value, reinterpret_cast<mvcc::VersionList<Vertex> *>(min_ptr),
|
||||||
|
reinterpret_cast<const Vertex *>(min_ptr)));
|
||||||
return IndexUtils::GetVlists<typename SkipList<IndexEntry>::Iterator,
|
return IndexUtils::GetVlists<typename SkipList<IndexEntry>::Iterator,
|
||||||
IndexEntry, Vertex>(
|
IndexEntry, Vertex>(
|
||||||
std::move(access), start_iter,
|
std::move(access), start_iter,
|
||||||
@ -212,6 +216,97 @@ class LabelPropertyIndex {
|
|||||||
current_state);
|
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.
|
* @brief - Check for existance of index.
|
||||||
* @param key - Index key
|
* @param key - Index key
|
||||||
@ -328,8 +423,7 @@ class LabelPropertyIndex {
|
|||||||
* than the second one
|
* than the second one
|
||||||
*/
|
*/
|
||||||
static bool Less(const PropertyValue &a, const PropertyValue &b) {
|
static bool Less(const PropertyValue &a, const PropertyValue &b) {
|
||||||
if (a.type() != b.type() &&
|
if (!PropertyValue::AreComparableTypes(a.type(), b.type()))
|
||||||
!(IsCastableToDouble(a) && IsCastableToDouble(b)))
|
|
||||||
return a.type() < b.type();
|
return a.type() < b.type();
|
||||||
|
|
||||||
if (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
|
// Types are int and double - convert int to double
|
||||||
return GetDouble(a) < GetDouble(b);
|
return get_double(a) < get_double(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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -297,21 +297,14 @@ std::unique_ptr<Cursor> ScanAllByLabelPropertyRange::MakeCursor(
|
|||||||
auto vertices = [this, &db, is_less](Frame &frame,
|
auto vertices = [this, &db, is_less](Frame &frame,
|
||||||
const SymbolTable &symbol_table) {
|
const SymbolTable &symbol_table) {
|
||||||
ExpressionEvaluator evaluator(frame, symbol_table, db, graph_view_);
|
ExpressionEvaluator evaluator(frame, symbol_table, db, graph_view_);
|
||||||
auto lower_val = lower_bound_ ? lower_bound_->value()->Accept(evaluator)
|
auto convert = [&evaluator](const auto &bound)
|
||||||
: TypedValue::Null;
|
-> std::experimental::optional<utils::Bound<PropertyValue>> {
|
||||||
auto upper_val = upper_bound_ ? upper_bound_->value()->Accept(evaluator)
|
if (!bound) return std::experimental::nullopt;
|
||||||
: TypedValue::Null;
|
return std::experimental::make_optional(utils::Bound<PropertyValue>(
|
||||||
return iter::filter(
|
bound.value().value()->Accept(evaluator), bound.value().type()));
|
||||||
[this, lower_val, upper_val, is_less](const VertexAccessor &vertex) {
|
};
|
||||||
TypedValue value = vertex.PropsAt(property_);
|
return db.vertices(label_, property_, convert(lower_bound()),
|
||||||
debug_assert(!value.IsNull(), "Unexpected property with Null value");
|
convert(upper_bound()), graph_view_ == GraphView::NEW);
|
||||||
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));
|
|
||||||
};
|
};
|
||||||
return std::make_unique<ScanAllCursor<decltype(vertices)>>(
|
return std::make_unique<ScanAllCursor<decltype(vertices)>>(
|
||||||
output_symbol_, input_->MakeCursor(db), std::move(vertices), db);
|
output_symbol_, input_->MakeCursor(db), std::move(vertices), db);
|
||||||
|
@ -28,6 +28,15 @@ class PropertyValue {
|
|||||||
// single static reference to Null, used whenever Null should be returned
|
// single static reference to Null, used whenever Null should be returned
|
||||||
static const PropertyValue Null;
|
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
|
// constructors for primitive types
|
||||||
PropertyValue(bool value) : type_(Type::Bool) { bool_v = value; }
|
PropertyValue(bool value) : type_(Type::Bool) { bool_v = value; }
|
||||||
PropertyValue(int value) : type_(Type::Int) { int_v = value; }
|
PropertyValue(int value) : type_(Type::Int) { int_v = value; }
|
||||||
|
@ -25,7 +25,7 @@ TEST(SkipList, HangDuringFindOrLarger) {
|
|||||||
threads.emplace_back([&iter, &skiplist]() {
|
threads.emplace_back([&iter, &skiplist]() {
|
||||||
auto accessor = skiplist.access();
|
auto accessor = skiplist.access();
|
||||||
for (int i = 0; i < iter; ++i)
|
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();
|
for (auto &thread : threads) thread.join();
|
||||||
|
@ -15,100 +15,143 @@ auto Count(TIterable iterable) {
|
|||||||
return std::distance(iterable.begin(), iterable.end());
|
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;
|
Dbms dbms;
|
||||||
auto dba = dbms.active();
|
std::unique_ptr<GraphDbAccessor> dba = dbms.active();
|
||||||
auto lab1 = dba->label("lab1");
|
GraphDbTypes::Property property = dba->property("property");
|
||||||
auto lab2 = dba->label("lab2");
|
GraphDbTypes::Label label = dba->label("label");
|
||||||
|
GraphDbTypes::EdgeType edge_type = dba->edge_type("edge_type");
|
||||||
|
|
||||||
EXPECT_EQ(dba->vertices_count(lab1), 0);
|
auto AddVertex() {
|
||||||
EXPECT_EQ(dba->vertices_count(lab2), 0);
|
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);
|
EXPECT_EQ(dba->vertices_count(), 0);
|
||||||
for (int i = 0; i < 11; ++i) dba->insert_vertex().add_label(lab1);
|
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(lab2);
|
for (int i = 0; i < 17; ++i) dba->insert_vertex().add_label(label2);
|
||||||
// even though xxx_count functions in GraphDbAccessor can over-estaimate
|
// even though xxx_count functions in GraphDbAccessor can over-estaimate
|
||||||
// in this situation they should be exact (nothing was ever deleted)
|
// in this situation they should be exact (nothing was ever deleted)
|
||||||
EXPECT_EQ(dba->vertices_count(lab1), 11);
|
EXPECT_EQ(dba->vertices_count(label), 11);
|
||||||
EXPECT_EQ(dba->vertices_count(lab2), 17);
|
EXPECT_EQ(dba->vertices_count(label2), 17);
|
||||||
EXPECT_EQ(dba->vertices_count(), 28);
|
EXPECT_EQ(dba->vertices_count(), 28);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(GraphDbAccessor, VertexByLabelPropertyCount) {
|
TEST_F(GraphDbAccessorIndex, LabelIndexIteration) {
|
||||||
Dbms dbms;
|
// add 10 vertices, check visibility
|
||||||
auto dba = dbms.active();
|
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");
|
// remove 3 vertices, check visibility
|
||||||
auto lab2 = dba->label("lab2");
|
int deleted = 0;
|
||||||
|
for (auto vertex : dba->vertices(false)) {
|
||||||
auto prop1 = dba->property("prop1");
|
dba->remove_vertex(vertex);
|
||||||
auto prop2 = dba->property("prop2");
|
if (++deleted >= 3) break;
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
for (int i = 0; i < 15; ++i) {
|
EXPECT_EQ(Count(dba->vertices(label, false)), 10);
|
||||||
auto vertex = dba->insert_vertex();
|
EXPECT_EQ(Count(dba->vertices(label, true)), 7);
|
||||||
vertex.add_label(lab1);
|
Commit();
|
||||||
vertex.PropsSet(prop2, 2);
|
EXPECT_EQ(Count(dba->vertices(label, false)), 7);
|
||||||
}
|
EXPECT_EQ(Count(dba->vertices(label, true)), 7);
|
||||||
for (int i = 0; i < 16; ++i) {
|
}
|
||||||
auto vertex = dba->insert_vertex();
|
|
||||||
vertex.add_label(lab2);
|
TEST_F(GraphDbAccessorIndex, EdgeTypeCount) {
|
||||||
vertex.PropsSet(prop1, 3);
|
auto edge_type2 = dba->edge_type("edge_type2");
|
||||||
}
|
EXPECT_EQ(dba->edges_count(edge_type), 0);
|
||||||
for (int i = 0; i < 17; ++i) {
|
EXPECT_EQ(dba->edges_count(edge_type2), 0);
|
||||||
auto vertex = dba->insert_vertex();
|
EXPECT_EQ(dba->edges_count(), 0);
|
||||||
vertex.add_label(lab2);
|
|
||||||
vertex.PropsSet(prop2, 4);
|
auto v1 = AddVertex();
|
||||||
}
|
auto v2 = AddVertex();
|
||||||
// even though xxx_count functions in GraphDbAccessor can over-estimate
|
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)
|
// in this situation they should be exact (nothing was ever deleted)
|
||||||
EXPECT_EQ(dba->vertices_count(lab1, prop1), 14);
|
EXPECT_EQ(dba->edges_count(edge_type), 11);
|
||||||
EXPECT_EQ(dba->vertices_count(lab1, prop2), 15);
|
EXPECT_EQ(dba->edges_count(edge_type2), 17);
|
||||||
EXPECT_EQ(dba->vertices_count(lab2, prop1), 16);
|
EXPECT_EQ(dba->edges_count(), 28);
|
||||||
EXPECT_EQ(dba->vertices_count(lab2, prop2), 17);
|
}
|
||||||
EXPECT_EQ(dba->vertices_count(), 14 + 15 + 16 + 17);
|
|
||||||
|
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) \
|
#define EXPECT_WITH_MARGIN(x, center) \
|
||||||
EXPECT_THAT( \
|
EXPECT_THAT( \
|
||||||
x, testing::AllOf(testing::Ge(center - 2), testing::Le(center + 2)));
|
x, testing::AllOf(testing::Ge(center - 2), testing::Le(center + 2)));
|
||||||
|
|
||||||
TEST(GraphDbAccessor, VertexByLabelPropertyValueCount) {
|
TEST_F(GraphDbAccessorIndex, LabelPropertyValueCount) {
|
||||||
Dbms dbms;
|
|
||||||
auto dba = dbms.active();
|
|
||||||
auto label = dba->label("label");
|
|
||||||
auto property = dba->property("property");
|
|
||||||
dba->BuildIndex(label, property);
|
dba->BuildIndex(label, property);
|
||||||
|
|
||||||
// add some vertices without the 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
|
// add vertices with prop values [0, 29), ten vertices for each value
|
||||||
for (int i = 0; i < 300; i++) {
|
for (int i = 0; i < 300; i++) AddVertex(i / 10);
|
||||||
auto vertex = dba->insert_vertex();
|
|
||||||
vertex.add_label(label);
|
|
||||||
vertex.PropsSet(property, i / 10);
|
|
||||||
}
|
|
||||||
// add verties in t he [30, 40) range, 100 vertices for each value
|
// add verties in t he [30, 40) range, 100 vertices for each value
|
||||||
for (int i = 0; i < 1000; i++) {
|
for (int i = 0; i < 1000; i++) AddVertex(30 + i / 100);
|
||||||
auto vertex = dba->insert_vertex();
|
|
||||||
vertex.add_label(label);
|
|
||||||
vertex.PropsSet(property, 30 + i / 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
// test estimates for exact value count
|
// test estimates for exact value count
|
||||||
EXPECT_WITH_MARGIN(dba->vertices_count(label, property, 10), 10);
|
EXPECT_WITH_MARGIN(dba->vertices_count(label, property, 10), 10);
|
||||||
@ -126,127 +169,47 @@ TEST(GraphDbAccessor, VertexByLabelPropertyValueCount) {
|
|||||||
return std::experimental::make_optional(
|
return std::experimental::make_optional(
|
||||||
utils::MakeBoundExclusive(PropertyValue(value)));
|
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);
|
return dba->vertices_count(label, property, lower, upper);
|
||||||
};
|
};
|
||||||
|
|
||||||
using std::experimental::nullopt;
|
using std::experimental::nullopt;
|
||||||
EXPECT_DEATH(Count(nullopt, nullopt), "bound must be provided");
|
::testing::FLAGS_gtest_death_test_style = "threadsafe";
|
||||||
EXPECT_WITH_MARGIN(Count(nullopt, Exclusive(4)), 40);
|
EXPECT_DEATH(vertices_count(nullopt, nullopt), "bound must be provided");
|
||||||
EXPECT_WITH_MARGIN(Count(nullopt, Inclusive(4)), 50);
|
EXPECT_WITH_MARGIN(vertices_count(nullopt, Exclusive(4)), 40);
|
||||||
EXPECT_WITH_MARGIN(Count(Exclusive(13), nullopt), 160 + 1000);
|
EXPECT_WITH_MARGIN(vertices_count(nullopt, Inclusive(4)), 50);
|
||||||
EXPECT_WITH_MARGIN(Count(Inclusive(13), nullopt), 170 + 1000);
|
EXPECT_WITH_MARGIN(vertices_count(Exclusive(13), nullopt), 160 + 1000);
|
||||||
EXPECT_WITH_MARGIN(Count(Inclusive(13), Exclusive(14)), 10);
|
EXPECT_WITH_MARGIN(vertices_count(Inclusive(13), nullopt), 170 + 1000);
|
||||||
EXPECT_WITH_MARGIN(Count(Exclusive(13), Inclusive(14)), 10);
|
EXPECT_WITH_MARGIN(vertices_count(Inclusive(13), Exclusive(14)), 10);
|
||||||
EXPECT_WITH_MARGIN(Count(Exclusive(13), Exclusive(13)), 0);
|
EXPECT_WITH_MARGIN(vertices_count(Exclusive(13), Inclusive(14)), 10);
|
||||||
EXPECT_WITH_MARGIN(Count(Inclusive(20), Exclusive(13)), 0);
|
EXPECT_WITH_MARGIN(vertices_count(Exclusive(13), Exclusive(13)), 0);
|
||||||
|
EXPECT_WITH_MARGIN(vertices_count(Inclusive(20), Exclusive(13)), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef EXPECT_WITH_MARGIN
|
#undef EXPECT_WITH_MARGIN
|
||||||
|
|
||||||
TEST(GraphDbAccessor, EdgeByEdgeTypeCount) {
|
TEST_F(GraphDbAccessorIndex, LabelPropertyValueIteration) {
|
||||||
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");
|
|
||||||
dba->BuildIndex(label, property);
|
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
|
TEST_F(GraphDbAccessorIndex, LabelPropertyValueSorting) {
|
||||||
// vertices with an exact integer.
|
|
||||||
TEST(GraphDbAccessor, FilterLabelPropertySpecificValue) {
|
|
||||||
Dbms dbms;
|
|
||||||
auto dba = dbms.active();
|
|
||||||
auto label = dba->label("lab1");
|
|
||||||
auto property = dba->property("prop1");
|
|
||||||
dba->BuildIndex(label, property);
|
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);
|
std::vector<PropertyValue> expected_property_value(50, 0);
|
||||||
|
|
||||||
// strings
|
// strings
|
||||||
for (int i = 0; i < 10; ++i) {
|
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.add_label(label);
|
||||||
vertex_accessor.PropsSet(property,
|
vertex_accessor.PropsSet(property,
|
||||||
static_cast<std::string>(std::to_string(i)));
|
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.
|
// bools - insert in reverse to check for comparison between values.
|
||||||
for (int i = 9; i >= 0; --i) {
|
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.add_label(label);
|
||||||
vertex_accessor.PropsSet(property, static_cast<bool>(i / 5));
|
vertex_accessor.PropsSet(property, static_cast<bool>(i / 5));
|
||||||
expected_property_value[10 + i] = vertex_accessor.PropsAt(property);
|
expected_property_value[10 + i] = vertex_accessor.PropsAt(property);
|
||||||
@ -262,14 +225,14 @@ TEST(GraphDbAccessor, SortedLabelPropertyEntries) {
|
|||||||
|
|
||||||
// integers
|
// integers
|
||||||
for (int i = 0; i < 10; ++i) {
|
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.add_label(label);
|
||||||
vertex_accessor.PropsSet(property, i);
|
vertex_accessor.PropsSet(property, i);
|
||||||
expected_property_value[20 + 2 * i] = vertex_accessor.PropsAt(property);
|
expected_property_value[20 + 2 * i] = vertex_accessor.PropsAt(property);
|
||||||
}
|
}
|
||||||
// doubles
|
// doubles
|
||||||
for (int i = 0; i < 10; ++i) {
|
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.add_label(label);
|
||||||
vertex_accessor.PropsSet(property, static_cast<double>(i + 0.5));
|
vertex_accessor.PropsSet(property, static_cast<double>(i + 0.5));
|
||||||
expected_property_value[20 + 2 * i + 1] = vertex_accessor.PropsAt(property);
|
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 of ints - insert in reverse to check for comparision between
|
||||||
// lists.
|
// lists.
|
||||||
for (int i = 9; i >= 0; --i) {
|
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.add_label(label);
|
||||||
std::vector<PropertyValue> value;
|
std::vector<PropertyValue> value;
|
||||||
value.push_back(PropertyValue(i));
|
value.push_back(PropertyValue(i));
|
||||||
@ -286,11 +249,11 @@ TEST(GraphDbAccessor, SortedLabelPropertyEntries) {
|
|||||||
expected_property_value[40 + i] = vertex_accessor.PropsAt(property);
|
expected_property_value[40 + i] = vertex_accessor.PropsAt(property);
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECT_EQ(Count(dba2->vertices(label, property, false)), 0);
|
EXPECT_EQ(Count(dba->vertices(label, property, false)), 0);
|
||||||
EXPECT_EQ(Count(dba2->vertices(label, property, true)), 50);
|
EXPECT_EQ(Count(dba->vertices(label, property, true)), 50);
|
||||||
|
|
||||||
int cnt = 0;
|
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);
|
const PropertyValue &property_value = vertex.PropsAt(property);
|
||||||
EXPECT_EQ(property_value.type(), expected_property_value[cnt].type());
|
EXPECT_EQ(property_value.type(), expected_property_value[cnt].type());
|
||||||
switch (property_value.type()) {
|
switch (property_value.type()) {
|
||||||
@ -328,72 +291,92 @@ TEST(GraphDbAccessor, SortedLabelPropertyEntries) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(GraphDbAccessor, VisibilityAfterInsertion) {
|
/**
|
||||||
Dbms dbms;
|
* A test fixture that contains a database, accessor,
|
||||||
auto dba = dbms.active();
|
* (label, property) index and 100 vertices, 10 for
|
||||||
auto v1 = dba->insert_vertex();
|
* each of [0, 10) property values.
|
||||||
auto v2 = dba->insert_vertex();
|
*/
|
||||||
auto lab1 = dba->label("lab1");
|
class GraphDbAccesssorIndexRange : public GraphDbAccessorIndex {
|
||||||
auto lab2 = dba->label("lab2");
|
protected:
|
||||||
v1.add_label(lab1);
|
void SetUp() override {
|
||||||
auto type1 = dba->edge_type("type1");
|
dba->BuildIndex(label, property);
|
||||||
auto type2 = dba->edge_type("type2");
|
for (int i = 0; i < 100; i++) AddVertex(i / 10);
|
||||||
dba->insert_edge(v1, v2, type1);
|
|
||||||
|
|
||||||
EXPECT_EQ(Count(dba->vertices(lab1, false)), 0);
|
ASSERT_EQ(Count(dba->vertices(false)), 0);
|
||||||
EXPECT_EQ(Count(dba->vertices(lab1, true)), 1);
|
ASSERT_EQ(Count(dba->vertices(true)), 100);
|
||||||
EXPECT_EQ(Count(dba->vertices(lab2, false)), 0);
|
Commit();
|
||||||
EXPECT_EQ(Count(dba->vertices(lab2, true)), 0);
|
ASSERT_EQ(Count(dba->vertices(false)), 100);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
dba->advance_command();
|
|
||||||
|
|
||||||
EXPECT_EQ(Count(dba->vertices(lab, false)), 5);
|
auto Vertices(std::experimental::optional<utils::Bound<PropertyValue>> lower,
|
||||||
EXPECT_EQ(Count(dba->vertices(lab, true)), 5);
|
std::experimental::optional<utils::Bound<PropertyValue>> upper,
|
||||||
EXPECT_EQ(Count(dba->edges(type, false)), 3);
|
bool current_state = false) {
|
||||||
EXPECT_EQ(Count(dba->edges(type, true)), 3);
|
return dba->vertices(label, property, lower, upper, current_state);
|
||||||
|
}
|
||||||
|
|
||||||
// delete two edges
|
auto Inclusive(PropertyValue value) {
|
||||||
auto edges_it = dba->edges(false).begin();
|
return std::experimental::make_optional(
|
||||||
for (int k = 0; k < 2; ++k) dba->remove_edge(*edges_it++);
|
utils::MakeBoundInclusive(PropertyValue(value)));
|
||||||
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);
|
|
||||||
|
|
||||||
// detach-delete 2 vertices
|
auto Exclusive(int value) {
|
||||||
auto vertices_it = dba->vertices(false).begin();
|
return std::experimental::make_optional(
|
||||||
for (int k = 0; k < 2; ++k) dba->detach_remove_vertex(*vertices_it++);
|
utils::MakeBoundExclusive(PropertyValue(value)));
|
||||||
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);
|
TEST_F(GraphDbAccesssorIndexRange, RangeIteration) {
|
||||||
EXPECT_EQ(Count(dba->vertices(lab, true)), 3);
|
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);
|
||||||
}
|
}
|
||||||
|
@ -840,104 +840,79 @@ TEST(QueryPlan, ScanAllByLabelProperty) {
|
|||||||
// Add 5 vertices with same label, but with different property values.
|
// Add 5 vertices with same label, but with different property values.
|
||||||
auto label = dba->label("label");
|
auto label = dba->label("label");
|
||||||
auto prop = dba->property("prop");
|
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();
|
auto vertex = dba->insert_vertex();
|
||||||
vertex.add_label(label);
|
vertex.add_label(label);
|
||||||
vertex.PropsSet(prop, i);
|
vertex.PropsSet(prop, value);
|
||||||
}
|
}
|
||||||
dba->commit();
|
dba->commit();
|
||||||
dba = dbms.active();
|
dba = dbms.active();
|
||||||
dba->BuildIndex(label, prop);
|
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->commit();
|
||||||
dba = dbms.active();
|
dba = dbms.active();
|
||||||
dba->BuildIndex(label, prop);
|
ASSERT_EQ(14, CountIterable(dba->vertices(false)));
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(QueryPlan, ScanAllByLabelPropertyRangeNull) {
|
auto check = [&dba, label, prop](TypedValue lower, Bound::Type lower_type,
|
||||||
Dbms dbms;
|
TypedValue upper, Bound::Type upper_type,
|
||||||
auto dba = dbms.active();
|
const std::vector<TypedValue> &expected) {
|
||||||
// Add 2 vertices with the same label, but with property values that cannot be
|
AstTreeStorage storage;
|
||||||
// compared. We are comparing to null, so no results should be produced.
|
SymbolTable symbol_table;
|
||||||
auto label = dba->label("label");
|
auto scan_all = MakeScanAllByLabelPropertyRange(
|
||||||
auto prop = dba->property("prop");
|
storage, symbol_table, "n", label, prop,
|
||||||
auto number_vertex = dba->insert_vertex();
|
Bound{LITERAL(lower), lower_type}, Bound{LITERAL(upper), upper_type});
|
||||||
number_vertex.add_label(label);
|
// RETURN n
|
||||||
number_vertex.PropsSet(prop, 42);
|
auto output = NEXPR("n", IDENT("n"));
|
||||||
auto string_vertex = dba->insert_vertex();
|
auto produce = MakeProduce(scan_all.op_, output);
|
||||||
string_vertex.add_label(label);
|
symbol_table[*output->expression_] = scan_all.sym_;
|
||||||
string_vertex.PropsSet(prop, "string");
|
symbol_table[*output] = symbol_table.CreateSymbol("n", true);
|
||||||
dba->commit();
|
auto results = CollectProduce(produce.get(), symbol_table, *dba);
|
||||||
dba = dbms.active();
|
ASSERT_EQ(results.size(), expected.size());
|
||||||
dba->BuildIndex(label, prop);
|
for (int i = 0; i < expected.size(); i++) {
|
||||||
dba = dbms.active();
|
TypedValue equal =
|
||||||
EXPECT_EQ(2, CountIterable(dba->vertices(false)));
|
results[i][0].Value<VertexAccessor>().PropsAt(prop) == expected[i];
|
||||||
// MATCH (n :label) WHERE null < n.prop
|
ASSERT_EQ(equal.type(), TypedValue::Type::Bool);
|
||||||
AstTreeStorage storage;
|
EXPECT_TRUE(equal.Value<bool>());
|
||||||
SymbolTable symbol_table;
|
}
|
||||||
auto scan_all = MakeScanAllByLabelPropertyRange(
|
};
|
||||||
storage, symbol_table, "n", label, prop,
|
|
||||||
Bound{LITERAL(TypedValue::Null), Bound::Type::EXCLUSIVE},
|
// normal ranges that return something
|
||||||
std::experimental::nullopt);
|
check(false, Bound::Type::INCLUSIVE, true, Bound::Type::EXCLUSIVE, {false});
|
||||||
// RETURN n
|
check(false, Bound::Type::EXCLUSIVE, true, Bound::Type::INCLUSIVE, {true});
|
||||||
auto output = NEXPR("n", IDENT("n"));
|
check("a", Bound::Type::EXCLUSIVE, "c", Bound::Type::EXCLUSIVE, {"b"});
|
||||||
auto produce = MakeProduce(scan_all.op_, output);
|
check(0, Bound::Type::EXCLUSIVE, 2, Bound::Type::INCLUSIVE, {0.5, 1, 1.5, 2});
|
||||||
symbol_table[*output->expression_] = scan_all.sym_;
|
check(1.5, Bound::Type::EXCLUSIVE, 2.5, Bound::Type::INCLUSIVE, {2, 2.5});
|
||||||
symbol_table[*output] = symbol_table.CreateSymbol("n", true);
|
check(std::vector<TypedValue>{0.5}, Bound::Type::EXCLUSIVE,
|
||||||
auto results = CollectProduce(produce.get(), symbol_table, *dba);
|
std::vector<TypedValue>{1.5}, Bound::Type::INCLUSIVE,
|
||||||
EXPECT_EQ(results.size(), 0);
|
{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) {
|
TEST(QueryPlan, ScanAllByLabelPropertyEqualityNoError) {
|
||||||
|
Loading…
Reference in New Issue
Block a user