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

View File

@ -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 =

View File

@ -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.
*/ */

View File

@ -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;
} }
/** /**

View File

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

View File

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

View File

@ -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();

View File

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

View File

@ -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) {