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:
parent
c08bb6e00c
commit
04c2ab3ba9
src/query/plan
tests/unit
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user