Plan::ExpandVariable - bounds are now expressions

Reviewers: teon.banek, buda

Reviewed By: teon.banek

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D596
This commit is contained in:
florijan 2017-07-28 14:30:30 +02:00
parent c08bb6e00c
commit 04c2ab3ba9
4 changed files with 71 additions and 52 deletions

View File

@ -1,4 +1,5 @@
#include <algorithm>
#include <limits>
#include <type_traits>
#include "query/plan/operator.hpp"
@ -514,20 +515,16 @@ bool Expand::ExpandCursor::PullNode(const EdgeAccessor &new_edge,
}
}
ExpandVariable::ExpandVariable(Symbol node_symbol, Symbol edge_symbol,
EdgeAtom::Direction direction,
std::experimental::optional<size_t> lower_bound,
std::experimental::optional<size_t> upper_bound,
const std::shared_ptr<LogicalOperator> &input,
Symbol input_symbol, bool existing_node,
bool existing_edge, GraphView graph_view)
ExpandVariable::ExpandVariable(
Symbol node_symbol, Symbol edge_symbol, EdgeAtom::Direction direction,
Expression *lower_bound,
Expression *upper_bound,
const std::shared_ptr<LogicalOperator> &input, Symbol input_symbol,
bool existing_node, bool existing_edge, GraphView graph_view)
: ExpandCommon(node_symbol, edge_symbol, direction, input, input_symbol,
existing_node, existing_edge, graph_view),
lower_bound_(lower_bound),
upper_bound_(upper_bound) {
debug_assert(lower_bound || upper_bound,
"At least one variable-length expansion must be specified");
}
upper_bound_(upper_bound) {}
bool Expand::ExpandCursor::HandleExistingEdge(const EdgeAccessor &new_edge,
Frame &frame) const {
@ -583,7 +580,7 @@ auto ExpandFromVertex(const VertexAccessor &vertex,
class ExpandVariableCursor : public Cursor {
public:
ExpandVariableCursor(const ExpandVariable &self, GraphDbAccessor &db)
: self_(self), input_cursor_(self.input_->MakeCursor(db)) {}
: self_(self), db_(db), input_cursor_(self.input_->MakeCursor(db)) {}
bool Pull(Frame &frame, const SymbolTable &symbol_table) override {
while (true) {
@ -591,7 +588,7 @@ class ExpandVariableCursor : public Cursor {
if (PullInput(frame, symbol_table)) {
// if lower bound is zero we also yield empty paths
if (self_.lower_bound_ && self_.lower_bound_.value() == 0) {
if (lower_bound_ == 0) {
auto &edges_on_frame =
frame[self_.edge_symbol_].Value<std::vector<TypedValue>>();
auto &start_vertex =
@ -619,7 +616,14 @@ class ExpandVariableCursor : public Cursor {
private:
const ExpandVariable &self_;
GraphDbAccessor &db_;
const std::unique_ptr<Cursor> input_cursor_;
// bounds. in the cursor they are not optional but set to
// default values if missing in the ExpandVariable operator
// initialize to arbitrary values, they should only be used
// after a successful pull from the input
int64_t upper_bound_{-1};
int64_t lower_bound_{-1};
// a stack of edge iterables corresponding to the level/depth of
// the expansion currently being Pulled
@ -657,7 +661,26 @@ class ExpandVariableCursor : public Cursor {
break;
}
if (!self_.upper_bound_ || self_.upper_bound_.value() > 0) {
// evaluate the upper and lower bounds
ExpressionEvaluator evaluator(frame, symbol_table, db_);
auto calc_bound = [this, &evaluator](auto &bound) {
TypedValue value = bound->Accept(evaluator);
try {
auto return_value = value.Value<int64_t>();
if (return_value < 0)
throw QueryRuntimeException(
"Variable expansion bound must be positive or zero");
return return_value;
} catch (TypedValueException &e) {
throw QueryRuntimeException("Variable expansion bound must be an int");
}
};
lower_bound_ = self_.lower_bound_ ? calc_bound(self_.lower_bound_) : 1;
upper_bound_ = self_.upper_bound_ ? calc_bound(self_.upper_bound_)
: std::numeric_limits<int64_t>::max();
if (upper_bound_ > 0) {
edges_.emplace_back(ExpandFromVertex(vertex, self_.direction_));
edges_it_.emplace_back(edges_.back().begin());
}
@ -794,15 +817,15 @@ class ExpandVariableCursor : public Cursor {
// we are doing depth-first search, so place the current
// edge's expansions onto the stack, if we should continue to expand
if (!self_.upper_bound_ || self_.upper_bound_.value() > edges_.size()) {
if (upper_bound_ > static_cast<int64_t>(edges_.size())) {
edges_.emplace_back(ExpandFromVertex(current_vertex, self_.direction_));
edges_it_.emplace_back(edges_.back().begin());
}
// we only yield true if we satisfy the lower bound, and frame
// edge placement indicated we are good
auto bound_ok = !self_.lower_bound_ ||
edges_on_frame.size() >= self_.lower_bound_.value();
auto bound_ok =
static_cast<int64_t>(edges_on_frame.size()) >= lower_bound_;
if (bound_ok && edge_placement_result == EdgePlacementResult::YIELD)
return true;
else

View File

@ -605,7 +605,7 @@ class ExpandVariable : public LogicalOperator, ExpandCommon {
* @brief Creates a variable-length expansion.
*
* Expansion length bounds are both inclusive (as in Neo's Cypher
* implementation). At least one of them has to be specified.
* implementation).
*
* Edge/Node existence is controlled via booleans. A true value
* simply denotes that this expansion references an already
@ -634,11 +634,9 @@ class ExpandVariable : public LogicalOperator, ExpandCommon {
* in the Frame and should just be checked for equality.
* @param existing_edge Same like `existing_node`, but for edges.
*/
// TODO change API to take expression instead of size_t
ExpandVariable(Symbol node_symbol, Symbol edge_symbol,
EdgeAtom::Direction direction,
std::experimental::optional<size_t> lower_bound,
std::experimental::optional<size_t> upper_bound,
EdgeAtom::Direction direction, Expression *lower_bound,
Expression *upper_bound,
const std::shared_ptr<LogicalOperator> &input,
Symbol input_symbol, bool existing_node, bool existing_edge,
GraphView graph_view = GraphView::AS_IS);
@ -647,10 +645,10 @@ class ExpandVariable : public LogicalOperator, ExpandCommon {
std::unique_ptr<Cursor> MakeCursor(GraphDbAccessor &db) override;
private:
// lower and upper bounds of the variable length expansion are
// optional, but at least one must be set
const std::experimental::optional<size_t> lower_bound_;
const std::experimental::optional<size_t> upper_bound_;
// lower and upper bounds of the variable length expansion
// both are optional, defaults are (1, inf)
Expression *lower_bound_;
Expression *upper_bound_;
};
/**
* @brief Filter whose Pull returns true only when the given expression

View File

@ -838,29 +838,11 @@ LogicalOperator *PlanMatching(const Matching &matching,
context.new_symbols.emplace_back(edge_symbol);
}
if (expansion.edge->has_range_) {
std::experimental::optional<size_t> lower_bound;
std::experimental::optional<size_t> upper_bound;
auto get_bound = [](auto *bound_expr) {
auto *literal = dynamic_cast<PrimitiveLiteral *>(bound_expr);
if (!literal || literal->value_.type() != TypedValue::Type::Int ||
literal->value_.Value<int64_t>() < 0) {
throw SemanticException(
"Length of variable path must be a non-negative integer "
"literal.");
}
return literal->value_.Value<int64_t>();
};
// Default lower bound to 1 if none provided.
lower_bound = expansion.edge->lower_bound_
? get_bound(expansion.edge->lower_bound_)
: 1U;
if (expansion.edge->upper_bound_) {
upper_bound = get_bound(expansion.edge->upper_bound_);
}
last_op = new ExpandVariable(
node_symbol, edge_symbol, expansion.direction, lower_bound,
upper_bound, std::shared_ptr<LogicalOperator>(last_op),
node1_symbol, existing_node, existing_edge, context.graph_view);
node_symbol, edge_symbol, expansion.direction,
expansion.edge->lower_bound_, expansion.edge->upper_bound_,
std::shared_ptr<LogicalOperator>(last_op), node1_symbol,
existing_node, existing_edge, context.graph_view);
} else {
last_op =
new Expand(node_symbol, edge_symbol, expansion.direction,

View File

@ -357,11 +357,15 @@ class QueryPlanExpandVariable : public testing::Test {
auto n_to_sym = symbol_table.CreateSymbol(node_to, true);
symbol_table[*n_to->identifier_] = n_to_sym;
if (std::is_same<TExpansionOperator, ExpandVariable>::value)
if (std::is_same<TExpansionOperator, ExpandVariable>::value) {
// convert optional ints to optional expressions
auto convert = [this](std::experimental::optional<size_t> bound) {
return bound ? LITERAL(static_cast<int64_t>(bound.value())) : nullptr;
};
return std::make_shared<ExpandVariable>(
n_to_sym, edge_sym, direction, lower, upper, filter_op, n_from.sym_,
false, existing_edge, GraphView::OLD);
else
n_to_sym, edge_sym, direction, convert(lower), convert(upper),
filter_op, n_from.sym_, false, existing_edge, GraphView::OLD);
} else
return std::make_shared<Expand>(n_to_sym, edge_sym, direction, filter_op,
n_from.sym_, false, existing_edge,
GraphView::OLD);
@ -425,6 +429,18 @@ TEST_F(QueryPlanExpandVariable, OneVariableExpansion) {
(map_int{{1, 4}, {2, 12}}));
EXPECT_EQ(test_expand(1, EdgeAtom::Direction::BOTH, 4, 4),
(map_int{{4, 24}}));
// default bound values (lower default is 1, upper default is inf)
EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, nullopt, 0), (map_int{}));
EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, nullopt, 1),
(map_int{{1, 4}}));
EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, nullopt, 2),
(map_int{{1, 4}, {2, 8}}));
EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 7, nullopt),
(map_int{{7, 24}, {8, 24}}));
EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 8, nullopt),
(map_int{{8, 24}}));
EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 9, nullopt), (map_int{}));
}
TEST_F(QueryPlanExpandVariable, EdgeUniquenessSingleAndVariableExpansion) {