Add operators for getting nodes by label-property index
Summary: Add ScanAllByLabelPropertyRange operator This operator uses the label + property indexing feature to iterate over the vertices. The property value of each vertex is checked whether it is inside the given range of values. The range is inclusive from both sides. If the value isn't in range, the vertex is filtered out. This manual filtering should be replaced by a database API when it becomes available. Add ScanAllByLabelPropertyValue operator Reviewers: florijan, mislav.bradac, buda Reviewed By: florijan, mislav.bradac Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D503
This commit is contained in:
parent
f5db9d65b0
commit
1841ff9616
@ -194,48 +194,39 @@ class ScanAllCursor : public Cursor {
|
||||
TVerticesFun get_vertices)
|
||||
: output_symbol_(output_symbol),
|
||||
input_cursor_(std::move(input_cursor)),
|
||||
get_vertices_(std::move(get_vertices)),
|
||||
vertices_(get_vertices_()),
|
||||
vertices_it_(vertices_.end()) {}
|
||||
get_vertices_(std::move(get_vertices)) {}
|
||||
|
||||
bool Pull(Frame &frame, const SymbolTable &symbol_table) override {
|
||||
if (vertices_it_ == vertices_.end()) {
|
||||
if (!vertices_ || vertices_it_.value() == vertices_.value().end()) {
|
||||
if (!input_cursor_->Pull(frame, symbol_table)) return false;
|
||||
// We need a getter function, because in case of exhausting a lazy
|
||||
// iterable, we cannot simply reset it by calling begin().
|
||||
// Unfortunately, cppitertools doesn't have move assignment, so we
|
||||
// reconstruct the iterable in-place.
|
||||
ReconstructInPlace(vertices_, get_vertices_());
|
||||
vertices_it_ = vertices_.begin();
|
||||
vertices_.emplace(get_vertices_(frame, symbol_table));
|
||||
vertices_it_.emplace(vertices_.value().begin());
|
||||
}
|
||||
|
||||
// if vertices_ is empty then we are done even though we have just
|
||||
// reinitialized vertices_it_
|
||||
if (vertices_it_ == vertices_.end()) return false;
|
||||
if (vertices_it_.value() == vertices_.value().end()) return false;
|
||||
|
||||
frame[output_symbol_] = *vertices_it_++;
|
||||
frame[output_symbol_] = *vertices_it_.value()++;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Reset() override {
|
||||
input_cursor_->Reset();
|
||||
vertices_it_ = vertices_.end();
|
||||
vertices_ = std::experimental::nullopt;
|
||||
vertices_it_ = std::experimental::nullopt;
|
||||
}
|
||||
|
||||
private:
|
||||
template <class T, typename... Args>
|
||||
void ReconstructInPlace(T &var, Args &&... args) {
|
||||
static_assert(!std::has_virtual_destructor<T>::value,
|
||||
"It is unsafe to in-place reconstruct a derived type.");
|
||||
var.~T();
|
||||
new (&var) T(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
const Symbol output_symbol_;
|
||||
const std::unique_ptr<Cursor> input_cursor_;
|
||||
TVerticesFun get_vertices_;
|
||||
decltype(get_vertices_()) vertices_;
|
||||
decltype(vertices_.begin()) vertices_it_;
|
||||
std::experimental::optional<
|
||||
typename std::result_of<TVerticesFun(Frame &, const SymbolTable &)>::type>
|
||||
vertices_;
|
||||
std::experimental::optional<decltype(vertices_.value().begin())> vertices_it_;
|
||||
};
|
||||
|
||||
ScanAll::ScanAll(const std::shared_ptr<LogicalOperator> &input,
|
||||
@ -250,7 +241,7 @@ ScanAll::ScanAll(const std::shared_ptr<LogicalOperator> &input,
|
||||
ACCEPT_WITH_INPUT(ScanAll)
|
||||
|
||||
std::unique_ptr<Cursor> ScanAll::MakeCursor(GraphDbAccessor &db) {
|
||||
auto vertices = [this, &db]() {
|
||||
auto vertices = [this, &db](Frame &, const SymbolTable &) {
|
||||
return db.vertices(graph_view_ == GraphView::NEW);
|
||||
};
|
||||
return std::make_unique<ScanAllCursor<decltype(vertices)>>(
|
||||
@ -265,13 +256,129 @@ ScanAllByLabel::ScanAllByLabel(const std::shared_ptr<LogicalOperator> &input,
|
||||
ACCEPT_WITH_INPUT(ScanAllByLabel)
|
||||
|
||||
std::unique_ptr<Cursor> ScanAllByLabel::MakeCursor(GraphDbAccessor &db) {
|
||||
auto vertices = [this, &db] {
|
||||
auto vertices = [this, &db](Frame &, const SymbolTable &) {
|
||||
return db.vertices(label_, graph_view_ == GraphView::NEW);
|
||||
};
|
||||
return std::make_unique<ScanAllCursor<decltype(vertices)>>(
|
||||
output_symbol_, input_->MakeCursor(db), std::move(vertices));
|
||||
}
|
||||
|
||||
ScanAllByLabelPropertyRange::ScanAllByLabelPropertyRange(
|
||||
const std::shared_ptr<LogicalOperator> &input, Symbol output_symbol,
|
||||
GraphDbTypes::Label label, GraphDbTypes::Property property,
|
||||
std::experimental::optional<Bound> lower_bound,
|
||||
std::experimental::optional<Bound> upper_bound, GraphView graph_view)
|
||||
: ScanAll(input, output_symbol, graph_view),
|
||||
label_(label),
|
||||
property_(property),
|
||||
lower_bound_(lower_bound),
|
||||
upper_bound_(upper_bound) {
|
||||
debug_assert(lower_bound_ || upper_bound_, "Only one bound can be left out");
|
||||
}
|
||||
|
||||
ACCEPT_WITH_INPUT(ScanAllByLabelPropertyRange)
|
||||
|
||||
std::unique_ptr<Cursor> ScanAllByLabelPropertyRange::MakeCursor(
|
||||
GraphDbAccessor &db) {
|
||||
auto is_less = [](const TypedValue &a, const TypedValue &b,
|
||||
Bound::Type bound_type) {
|
||||
try {
|
||||
auto is_below = bound_type == Bound::Type::INCLUSIVE ? a < b : a <= b;
|
||||
if (is_below.IsNull() || is_below.Value<bool>()) return true;
|
||||
} catch (const TypedValueException &) {
|
||||
throw QueryRuntimeException(
|
||||
"Unable to compare values of type '{}' and '{}'", a.type(), b.type());
|
||||
}
|
||||
return false;
|
||||
};
|
||||
auto vertices = [this, &db, is_less](Frame &frame,
|
||||
const SymbolTable &symbol_table) {
|
||||
ExpressionEvaluator evaluator(frame, symbol_table, db, graph_view_);
|
||||
auto lower_val = lower_bound_ ? lower_bound_->expression->Accept(evaluator)
|
||||
: TypedValue::Null;
|
||||
auto upper_val = upper_bound_ ? upper_bound_->expression->Accept(evaluator)
|
||||
: TypedValue::Null;
|
||||
return iter::filter(
|
||||
[this, lower_val, upper_val, is_less](const VertexAccessor &vertex) {
|
||||
TypedValue value = vertex.PropsAt(property_);
|
||||
debug_assert(!value.IsNull(), "Unexpected property with Null value");
|
||||
if (lower_bound_ && is_less(value, lower_val, lower_bound_->type))
|
||||
return false;
|
||||
if (upper_bound_ && is_less(upper_val, value, upper_bound_->type))
|
||||
return false;
|
||||
return true;
|
||||
},
|
||||
db.vertices(label_, property_, graph_view_ == GraphView::NEW));
|
||||
};
|
||||
return std::make_unique<ScanAllCursor<decltype(vertices)>>(
|
||||
output_symbol_, input_->MakeCursor(db), std::move(vertices));
|
||||
}
|
||||
|
||||
ScanAllByLabelPropertyValue::ScanAllByLabelPropertyValue(
|
||||
const std::shared_ptr<LogicalOperator> &input, Symbol output_symbol,
|
||||
GraphDbTypes::Label label, GraphDbTypes::Property property,
|
||||
Expression *expression, GraphView graph_view)
|
||||
: ScanAll(input, output_symbol, graph_view),
|
||||
label_(label),
|
||||
property_(property),
|
||||
expression_(expression) {
|
||||
debug_assert(expression, "Expression is not optional.");
|
||||
}
|
||||
|
||||
ACCEPT_WITH_INPUT(ScanAllByLabelPropertyValue)
|
||||
|
||||
class ScanAllByLabelPropertyValueCursor : public Cursor {
|
||||
public:
|
||||
ScanAllByLabelPropertyValueCursor(const ScanAllByLabelPropertyValue &self,
|
||||
GraphDbAccessor &db)
|
||||
: self_(self), db_(db), input_cursor_(self_.input()->MakeCursor(db_)) {}
|
||||
|
||||
bool Pull(Frame &frame, const SymbolTable &symbol_table) override {
|
||||
if (!vertices_ || vertices_it_.value() == vertices_.value().end()) {
|
||||
if (!input_cursor_->Pull(frame, symbol_table)) return false;
|
||||
ExpressionEvaluator evaluator(frame, symbol_table, db_,
|
||||
self_.graph_view());
|
||||
TypedValue value = self_.expression()->Accept(evaluator);
|
||||
if (value.IsNull()) return Pull(frame, symbol_table);
|
||||
try {
|
||||
vertices_.emplace(db_.vertices(self_.label(), self_.property(), value,
|
||||
self_.graph_view() == GraphView::NEW));
|
||||
} catch (const TypedValueException &) {
|
||||
throw QueryRuntimeException("'{}' cannot be used as a property value.",
|
||||
value.type());
|
||||
}
|
||||
vertices_it_.emplace(vertices_.value().begin());
|
||||
}
|
||||
|
||||
// if vertices_ is empty then we are done even though we have just
|
||||
// reinitialized vertices_it_
|
||||
if (vertices_it_.value() == vertices_.value().end()) return false;
|
||||
|
||||
frame[self_.output_symbol()] = *vertices_it_.value()++;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Reset() override {
|
||||
input_cursor_->Reset();
|
||||
vertices_ = std::experimental::nullopt;
|
||||
vertices_it_ = std::experimental::nullopt;
|
||||
}
|
||||
|
||||
private:
|
||||
const ScanAllByLabelPropertyValue &self_;
|
||||
GraphDbAccessor &db_;
|
||||
const std::unique_ptr<Cursor> input_cursor_;
|
||||
std::experimental::optional<decltype(
|
||||
db_.vertices(self_.label(), self_.property(), TypedValue::Null, false))>
|
||||
vertices_;
|
||||
std::experimental::optional<decltype(vertices_.value().begin())> vertices_it_;
|
||||
};
|
||||
|
||||
std::unique_ptr<Cursor> ScanAllByLabelPropertyValue::MakeCursor(
|
||||
GraphDbAccessor &db) {
|
||||
return std::make_unique<ScanAllByLabelPropertyValueCursor>(*this, db);
|
||||
}
|
||||
|
||||
Expand::Expand(Symbol node_symbol, Symbol edge_symbol,
|
||||
EdgeAtom::Direction direction,
|
||||
const std::shared_ptr<LogicalOperator> &input,
|
||||
|
@ -3,8 +3,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <experimental/optional>
|
||||
#include <memory>
|
||||
#include <query/exceptions.hpp>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@ -12,6 +12,7 @@
|
||||
#include "database/graph_db_accessor.hpp"
|
||||
#include "database/graph_db_datatypes.hpp"
|
||||
#include "query/common.hpp"
|
||||
#include "query/exceptions.hpp"
|
||||
#include "query/frontend/semantic/symbol_table.hpp"
|
||||
#include "utils/hashing/fnv.hpp"
|
||||
#include "utils/visitor.hpp"
|
||||
@ -54,6 +55,8 @@ class CreateNode;
|
||||
class CreateExpand;
|
||||
class ScanAll;
|
||||
class ScanAllByLabel;
|
||||
class ScanAllByLabelPropertyRange;
|
||||
class ScanAllByLabelPropertyValue;
|
||||
class Expand;
|
||||
class Filter;
|
||||
class Produce;
|
||||
@ -78,7 +81,8 @@ class Distinct;
|
||||
class CreateIndex;
|
||||
|
||||
using LogicalOperatorCompositeVisitor = ::utils::CompositeVisitor<
|
||||
Once, CreateNode, CreateExpand, ScanAll, ScanAllByLabel, Expand, Filter,
|
||||
Once, CreateNode, CreateExpand, ScanAll, ScanAllByLabel,
|
||||
ScanAllByLabelPropertyRange, ScanAllByLabelPropertyValue, Expand, Filter,
|
||||
Produce, Delete, SetProperty, SetProperties, SetLabels, RemoveProperty,
|
||||
RemoveLabels, ExpandUniquenessFilter<VertexAccessor>,
|
||||
ExpandUniquenessFilter<EdgeAccessor>, Accumulate, AdvanceCommand, Aggregate,
|
||||
@ -287,6 +291,10 @@ class CreateExpand : public LogicalOperator {
|
||||
* ScanAll can either iterate over the previous graph state (state before
|
||||
* the current transacton+command) or over current state. This is controlled
|
||||
* with a constructor argument.
|
||||
*
|
||||
* @sa ScanAllByLabel
|
||||
* @sa ScanAllByLabelPropertyRange
|
||||
* @sa ScanAllByLabelPropertyValue
|
||||
*/
|
||||
class ScanAll : public LogicalOperator {
|
||||
public:
|
||||
@ -295,6 +303,10 @@ class ScanAll : public LogicalOperator {
|
||||
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
||||
std::unique_ptr<Cursor> MakeCursor(GraphDbAccessor &db) override;
|
||||
|
||||
auto input() const { return input_; }
|
||||
auto output_symbol() const { return output_symbol_; }
|
||||
auto graph_view() const { return graph_view_; }
|
||||
|
||||
protected:
|
||||
const std::shared_ptr<LogicalOperator> input_;
|
||||
const Symbol output_symbol_;
|
||||
@ -312,6 +324,10 @@ class ScanAll : public LogicalOperator {
|
||||
/**
|
||||
* @brief Behaves like @c ScanAll, but this operator produces only vertices with
|
||||
* given label.
|
||||
*
|
||||
* @sa ScanAll
|
||||
* @sa ScanAllByLabelPropertyRange
|
||||
* @sa ScanAllByLabelPropertyValue
|
||||
*/
|
||||
class ScanAllByLabel : public ScanAll {
|
||||
public:
|
||||
@ -327,6 +343,105 @@ class ScanAllByLabel : public ScanAll {
|
||||
const GraphDbTypes::Label label_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Behaves like @c ScanAll, but produces only vertices with given label and
|
||||
* property value which is inside a range (inclusive or exlusive).
|
||||
*
|
||||
* @sa ScanAll
|
||||
* @sa ScanAllByLabel
|
||||
* @sa ScanAllByLabelPropertyValue
|
||||
*/
|
||||
class ScanAllByLabelPropertyRange : public ScanAll {
|
||||
public:
|
||||
/** Defines a bounding value for a range. */
|
||||
struct Bound {
|
||||
/**
|
||||
* Determines whether the value of bound expression should be included or
|
||||
* excluded.
|
||||
*/
|
||||
enum class Type { INCLUSIVE, EXCLUSIVE };
|
||||
|
||||
/** Expression which when evaluated will produce a value for the bound. */
|
||||
Expression *expression;
|
||||
/** Whether the bound is inclusive or exclusive. */
|
||||
Type type;
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructs the operator for given label and property value in range
|
||||
* (inclusive).
|
||||
*
|
||||
* Range bounds are optional, but only one bound can be left out.
|
||||
*
|
||||
* @param input Preceding operator which will serve as the input.
|
||||
* @param output_symbol Symbol where the vertices will be stored.
|
||||
* @param label Label which the vertex must have.
|
||||
* @param property Property from which the value will be looked up from.
|
||||
* @param lower_bound Optional lower @c Bound.
|
||||
* @param upper_bound Optional upper @c Bound.
|
||||
* @param graph_view GraphView used when obtaining vertices.
|
||||
*/
|
||||
ScanAllByLabelPropertyRange(const std::shared_ptr<LogicalOperator> &input,
|
||||
Symbol output_symbol, GraphDbTypes::Label label,
|
||||
GraphDbTypes::Property property,
|
||||
std::experimental::optional<Bound> lower_bound,
|
||||
std::experimental::optional<Bound> upper_bound,
|
||||
GraphView graph_view = GraphView::OLD);
|
||||
|
||||
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
||||
std::unique_ptr<Cursor> MakeCursor(GraphDbAccessor &db) override;
|
||||
|
||||
auto label() const { return label_; }
|
||||
auto property() const { return property_; }
|
||||
auto lower_bound() const { return lower_bound_; }
|
||||
auto upper_bound() const { return upper_bound_; }
|
||||
|
||||
private:
|
||||
const GraphDbTypes::Label label_;
|
||||
const GraphDbTypes::Property property_;
|
||||
std::experimental::optional<Bound> lower_bound_;
|
||||
std::experimental::optional<Bound> upper_bound_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Behaves like @c ScanAll, but produces only vertices with given label and
|
||||
* property value.
|
||||
*
|
||||
* @sa ScanAll
|
||||
* @sa ScanAllByLabel
|
||||
* @sa ScanAllByLabelPropertyRange
|
||||
*/
|
||||
class ScanAllByLabelPropertyValue : public ScanAll {
|
||||
public:
|
||||
/**
|
||||
* Constructs the operator for given label and property value.
|
||||
*
|
||||
* @param input Preceding operator which will serve as the input.
|
||||
* @param output_symbol Symbol where the vertices will be stored.
|
||||
* @param label Label which the vertex must have.
|
||||
* @param property Property from which the value will be looked up from.
|
||||
* @param expression Expression producing the value of the vertex property.
|
||||
* @param graph_view GraphView used when obtaining vertices.
|
||||
*/
|
||||
ScanAllByLabelPropertyValue(const std::shared_ptr<LogicalOperator> &input,
|
||||
Symbol output_symbol, GraphDbTypes::Label label,
|
||||
GraphDbTypes::Property property,
|
||||
Expression *expression,
|
||||
GraphView graph_view = GraphView::OLD);
|
||||
|
||||
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
||||
std::unique_ptr<Cursor> MakeCursor(GraphDbAccessor &db) override;
|
||||
|
||||
auto label() const { return label_; }
|
||||
auto property() const { return property_; }
|
||||
auto expression() const { return expression_; }
|
||||
|
||||
private:
|
||||
const GraphDbTypes::Label label_;
|
||||
const GraphDbTypes::Property property_;
|
||||
Expression *expression_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Expansion operator. For a node existing in the frame it
|
||||
* expands one edge and one node and places them on the frame.
|
||||
|
@ -20,6 +20,8 @@
|
||||
using namespace query;
|
||||
using namespace query::plan;
|
||||
|
||||
using Bound = ScanAllByLabelPropertyRange::Bound;
|
||||
|
||||
/**
|
||||
* Helper function that collects all the results from the given
|
||||
* Produce into a ResultStreamFaker and returns the results from it.
|
||||
@ -119,6 +121,46 @@ ScanAllTuple MakeScanAllByLabel(
|
||||
return ScanAllTuple{node, logical_op, symbol};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a tuple of stuff for a scan-all starting from the node
|
||||
* with the given name and label whose property values are in range.
|
||||
*
|
||||
* Returns ScanAllTuple(node_atom, scan_all_logical_op, symbol).
|
||||
*/
|
||||
ScanAllTuple MakeScanAllByLabelPropertyRange(
|
||||
AstTreeStorage &storage, SymbolTable &symbol_table, std::string identifier,
|
||||
GraphDbTypes::Label label, GraphDbTypes::Property property,
|
||||
std::experimental::optional<Bound> lower_bound,
|
||||
std::experimental::optional<Bound> upper_bound,
|
||||
std::shared_ptr<LogicalOperator> input = {nullptr},
|
||||
GraphView graph_view = GraphView::OLD) {
|
||||
auto node = NODE(identifier);
|
||||
auto symbol = symbol_table.CreateSymbol(identifier, true);
|
||||
symbol_table[*node->identifier_] = symbol;
|
||||
auto logical_op = std::make_shared<ScanAllByLabelPropertyRange>(
|
||||
input, symbol, label, property, lower_bound, upper_bound, graph_view);
|
||||
return ScanAllTuple{node, logical_op, symbol};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a tuple of stuff for a scan-all starting from the node
|
||||
* with the given name and label whose property value is equal to given value.
|
||||
*
|
||||
* Returns ScanAllTuple(node_atom, scan_all_logical_op, symbol).
|
||||
*/
|
||||
ScanAllTuple MakeScanAllByLabelPropertyValue(
|
||||
AstTreeStorage &storage, SymbolTable &symbol_table, std::string identifier,
|
||||
GraphDbTypes::Label label, GraphDbTypes::Property property,
|
||||
Expression *value, std::shared_ptr<LogicalOperator> input = {nullptr},
|
||||
GraphView graph_view = GraphView::OLD) {
|
||||
auto node = NODE(identifier);
|
||||
auto symbol = symbol_table.CreateSymbol(identifier, true);
|
||||
symbol_table[*node->identifier_] = symbol;
|
||||
auto logical_op = std::make_shared<ScanAllByLabelPropertyValue>(
|
||||
input, symbol, label, property, value, graph_view);
|
||||
return ScanAllTuple{node, logical_op, symbol};
|
||||
}
|
||||
|
||||
struct ExpandTuple {
|
||||
EdgeAtom *edge_;
|
||||
Symbol edge_sym_;
|
||||
|
@ -833,3 +833,179 @@ TEST(QueryPlan, ScanAllByLabel) {
|
||||
ASSERT_EQ(result_row.size(), 1);
|
||||
EXPECT_EQ(result_row[0].Value<VertexAccessor>(), labeled_vertex);
|
||||
}
|
||||
|
||||
TEST(QueryPlan, ScanAllByLabelProperty) {
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
// Add 5 vertices with same label, but with different property values.
|
||||
auto label = dba->label("label");
|
||||
auto prop = dba->property("prop");
|
||||
for (int i = 1; i <= 5; ++i) {
|
||||
auto vertex = dba->insert_vertex();
|
||||
vertex.add_label(label);
|
||||
vertex.PropsSet(prop, i);
|
||||
}
|
||||
dba->commit();
|
||||
dba = dbms.active();
|
||||
dba->BuildIndex(label, prop);
|
||||
dba = dbms.active();
|
||||
EXPECT_EQ(5, CountIterable(dba->vertices(false)));
|
||||
// MATCH (n :label) WHERE 2 <= n.prop < 4
|
||||
AstTreeStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
auto scan_all = MakeScanAllByLabelPropertyRange(
|
||||
storage, symbol_table, "n", label, prop,
|
||||
Bound{LITERAL(2), Bound::Type::INCLUSIVE},
|
||||
Bound{LITERAL(4), Bound::Type::EXCLUSIVE});
|
||||
// RETURN n
|
||||
auto output = NEXPR("n", IDENT("n"));
|
||||
auto produce = MakeProduce(scan_all.op_, output);
|
||||
symbol_table[*output->expression_] = scan_all.sym_;
|
||||
symbol_table[*output] = symbol_table.CreateSymbol("n", true);
|
||||
auto results = CollectProduce(produce.get(), symbol_table, *dba);
|
||||
EXPECT_EQ(results.size(), 2);
|
||||
for (const auto &row : results) {
|
||||
ASSERT_EQ(row.size(), 1);
|
||||
auto vertex = row[0].Value<VertexAccessor>();
|
||||
auto value = vertex.PropsAt(prop);
|
||||
TypedValue::BoolEqual eq;
|
||||
EXPECT_TRUE(eq(value, 2) || eq(value, 3));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(QueryPlan, ScanAllByLabelPropertyCompareError) {
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
// Add 2 vertices with same label, but with property values that cannot be
|
||||
// compared.
|
||||
auto label = dba->label("label");
|
||||
auto prop = dba->property("prop");
|
||||
auto number_vertex = dba->insert_vertex();
|
||||
number_vertex.add_label(label);
|
||||
number_vertex.PropsSet(prop, 42);
|
||||
auto string_vertex = dba->insert_vertex();
|
||||
string_vertex.add_label(label);
|
||||
string_vertex.PropsSet(prop, "string");
|
||||
dba->commit();
|
||||
dba = dbms.active();
|
||||
dba->BuildIndex(label, prop);
|
||||
dba = dbms.active();
|
||||
EXPECT_EQ(2, CountIterable(dba->vertices(false)));
|
||||
// MATCH (n :label) WHERE 1 < n.prop
|
||||
AstTreeStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
auto scan_all = MakeScanAllByLabelPropertyRange(
|
||||
storage, symbol_table, "n", label, prop,
|
||||
Bound{LITERAL(1), Bound::Type::EXCLUSIVE}, std::experimental::nullopt);
|
||||
// RETURN n
|
||||
auto output = NEXPR("n", IDENT("n"));
|
||||
auto produce = MakeProduce(scan_all.op_, output);
|
||||
symbol_table[*output->expression_] = scan_all.sym_;
|
||||
symbol_table[*output] = symbol_table.CreateSymbol("n", true);
|
||||
EXPECT_THROW(CollectProduce(produce.get(), symbol_table, *dba),
|
||||
QueryRuntimeException);
|
||||
}
|
||||
|
||||
TEST(QueryPlan, ScanAllByLabelPropertyRangeNull) {
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
// Add 2 vertices with the same label, but with property values that cannot be
|
||||
// compared. We are comparing to null, so no results should be produced.
|
||||
auto label = dba->label("label");
|
||||
auto prop = dba->property("prop");
|
||||
auto number_vertex = dba->insert_vertex();
|
||||
number_vertex.add_label(label);
|
||||
number_vertex.PropsSet(prop, 42);
|
||||
auto string_vertex = dba->insert_vertex();
|
||||
string_vertex.add_label(label);
|
||||
string_vertex.PropsSet(prop, "string");
|
||||
dba->commit();
|
||||
dba = dbms.active();
|
||||
dba->BuildIndex(label, prop);
|
||||
dba = dbms.active();
|
||||
EXPECT_EQ(2, CountIterable(dba->vertices(false)));
|
||||
// MATCH (n :label) WHERE null < n.prop
|
||||
AstTreeStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
auto scan_all = MakeScanAllByLabelPropertyRange(
|
||||
storage, symbol_table, "n", label, prop,
|
||||
Bound{LITERAL(TypedValue::Null), Bound::Type::EXCLUSIVE},
|
||||
std::experimental::nullopt);
|
||||
// RETURN n
|
||||
auto output = NEXPR("n", IDENT("n"));
|
||||
auto produce = MakeProduce(scan_all.op_, output);
|
||||
symbol_table[*output->expression_] = scan_all.sym_;
|
||||
symbol_table[*output] = symbol_table.CreateSymbol("n", true);
|
||||
auto results = CollectProduce(produce.get(), symbol_table, *dba);
|
||||
EXPECT_EQ(results.size(), 0);
|
||||
}
|
||||
|
||||
TEST(QueryPlan, ScanAllByLabelPropertyEqualityNoError) {
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
// Add 2 vertices with same label, but with property values that cannot be
|
||||
// compared. On the other hand, equality works fine.
|
||||
auto label = dba->label("label");
|
||||
auto prop = dba->property("prop");
|
||||
auto number_vertex = dba->insert_vertex();
|
||||
number_vertex.add_label(label);
|
||||
number_vertex.PropsSet(prop, 42);
|
||||
auto string_vertex = dba->insert_vertex();
|
||||
string_vertex.add_label(label);
|
||||
string_vertex.PropsSet(prop, "string");
|
||||
dba->commit();
|
||||
dba = dbms.active();
|
||||
dba->BuildIndex(label, prop);
|
||||
dba = dbms.active();
|
||||
EXPECT_EQ(2, CountIterable(dba->vertices(false)));
|
||||
// MATCH (n :label {prop: 42})
|
||||
AstTreeStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
auto scan_all = MakeScanAllByLabelPropertyValue(storage, symbol_table, "n",
|
||||
label, prop, LITERAL(42));
|
||||
// RETURN n
|
||||
auto output = NEXPR("n", IDENT("n"));
|
||||
auto produce = MakeProduce(scan_all.op_, output);
|
||||
symbol_table[*output->expression_] = scan_all.sym_;
|
||||
symbol_table[*output] = symbol_table.CreateSymbol("n", true);
|
||||
auto results = CollectProduce(produce.get(), symbol_table, *dba);
|
||||
ASSERT_EQ(results.size(), 1);
|
||||
const auto &row = results[0];
|
||||
ASSERT_EQ(row.size(), 1);
|
||||
auto vertex = row[0].Value<VertexAccessor>();
|
||||
auto value = vertex.PropsAt(prop);
|
||||
TypedValue::BoolEqual eq;
|
||||
EXPECT_TRUE(eq(value, 42));
|
||||
}
|
||||
|
||||
TEST(QueryPlan, ScanAllByLabelPropertyEqualNull) {
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
// Add 2 vertices with the same label, but one has a property value while the
|
||||
// other does not. Checking if the value is equal to null, should yield no
|
||||
// results.
|
||||
auto label = dba->label("label");
|
||||
auto prop = dba->property("prop");
|
||||
auto vertex = dba->insert_vertex();
|
||||
vertex.add_label(label);
|
||||
auto vertex_with_prop = dba->insert_vertex();
|
||||
vertex_with_prop.add_label(label);
|
||||
vertex_with_prop.PropsSet(prop, 42);
|
||||
dba->commit();
|
||||
dba = dbms.active();
|
||||
dba->BuildIndex(label, prop);
|
||||
dba = dbms.active();
|
||||
EXPECT_EQ(2, CountIterable(dba->vertices(false)));
|
||||
// MATCH (n :label {prop: 42})
|
||||
AstTreeStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
auto scan_all = MakeScanAllByLabelPropertyValue(
|
||||
storage, symbol_table, "n", label, prop, LITERAL(TypedValue::Null));
|
||||
// RETURN n
|
||||
auto output = NEXPR("n", IDENT("n"));
|
||||
auto produce = MakeProduce(scan_all.op_, output);
|
||||
symbol_table[*output->expression_] = scan_all.sym_;
|
||||
symbol_table[*output] = symbol_table.CreateSymbol("n", true);
|
||||
auto results = CollectProduce(produce.get(), symbol_table, *dba);
|
||||
EXPECT_EQ(results.size(), 0);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user