Variable expansion consolidaton

Summary:
- Removed BreadthFirstAtom, using EdgeAtom only with a Type enum.
- Both variable expansions (breadth and depth first) now have mandatory inner node and edge Identifiers.
- Both variable expansions use inline property filtering and support inline lambdas.
- BFS and variable expansion now have the same planning process.
- Planner modified in the following ways:
	- Variable expansions support inline property filtering (two filters added to all_filters, one for inline, one for post-expand).
	- Asserting against existing_edge since we don't support that anymore.
	- Edge and node symbols bound after variable expansion to disallow post-expand filters to get inlined.
	- Some things simplified due to different handling.
- BreadthFirstExpand logical operator merged into ExpandVariable. Two Cursor classes remain and are dynamically chosen from.

As part of planned planner refactor we should ensure that a filter is applied only once. The current implementation is very suboptimal for property filtering in variable expansions.

@buda: we will start refactoring this these days. This current planner logic is too dense and complex. It is becoming technical debt. Most of the time I spent working on this has been spent figuring the planning out, and I still needed Teon's help at times. Implementing the correct and optimal version of query execution (avoiding multiple potentially expensive filterings) was out of reach also due to tech debt.

Reviewers: buda, teon.banek

Reviewed By: teon.banek

Subscribers: pullbot, buda

Differential Revision: https://phabricator.memgraph.io/D852
This commit is contained in:
florijan 2017-10-05 11:25:52 +02:00
parent d249ff11a5
commit 76fe8bfadf
26 changed files with 766 additions and 1103 deletions

View File

@ -403,10 +403,9 @@ The following sections describe some of the other supported features.
#### Breadth First Search
A typical graph use-case is searching for the shortest path between nodes.
The openCypher standard does not define this feature. Neo4j offers the
`shortestPath` function-like invocation for this purpose.
The openCypher standard does not define this feature, so Memgraph provides
a custom implementation, based on the edge expansion syntax.
Memgraph decided to offer the same functionality using the edge expansion syntax.
Finding the shortest path between nodes can be done using breadth-first
expansion:
@ -430,8 +429,8 @@ whose property is lesser then `3`:
The filter is defined as a lambda function over `e` and `n`, which denote the edge
and node being expanded over in the breadth first search.
There are a few benefits of the breadth-first expansion approach, as compared to
the `shortestPath` function of Neo4j. For one, it is possible to inject
There are a few benefits of the breadth-first expansion approach, as opposed to
a specialized `shortestPath` function. For one, it is possible to inject
expressions that filter on nodes and edges along the path itself, not just the final
destination node. Furthermore, it's possible to find multiple paths to multiple destination
nodes regardless of their length. Also, it is possible to simply go through a node's

View File

@ -982,7 +982,13 @@ class NodeAtom : public PatternAtom {
class EdgeAtom : public PatternAtom {
friend class AstTreeStorage;
template <typename TPtr>
static TPtr *CloneOpt(TPtr *ptr, AstTreeStorage &storage) {
return ptr ? ptr->Clone(storage) : nullptr;
}
public:
enum class Type { SINGLE, DEPTH_FIRST, BREADTH_FIRST };
enum class Direction { IN, OUT, BOTH };
DEFVISITABLE(TreeVisitor<TypedValue>);
@ -1007,92 +1013,57 @@ class EdgeAtom : public PatternAtom {
EdgeAtom *Clone(AstTreeStorage &storage) const override {
auto *edge_atom = storage.Create<EdgeAtom>(identifier_->Clone(storage));
edge_atom->direction_ = direction_;
edge_atom->type_ = type_;
edge_atom->edge_types_ = edge_types_;
for (auto property : properties_) {
edge_atom->properties_[property.first] = property.second->Clone(storage);
}
edge_atom->has_range_ = has_range_;
edge_atom->lower_bound_ =
lower_bound_ ? lower_bound_->Clone(storage) : nullptr;
edge_atom->upper_bound_ =
upper_bound_ ? upper_bound_->Clone(storage) : nullptr;
edge_atom->lower_bound_ = CloneOpt(lower_bound_, storage);
edge_atom->upper_bound_ = CloneOpt(upper_bound_, storage);
edge_atom->inner_edge_ = CloneOpt(inner_edge_, storage);
edge_atom->inner_node_ = CloneOpt(inner_node_, storage);
edge_atom->filter_expression_ = CloneOpt(filter_expression_, storage);
return edge_atom;
}
bool IsVariable() const {
switch (type_) {
case Type::DEPTH_FIRST:
case Type::BREADTH_FIRST:
return true;
default:
return false;
}
}
Type type_ = Type::SINGLE;
Direction direction_ = Direction::BOTH;
std::vector<GraphDbTypes::EdgeType> edge_types_;
// maps (property_name, property) to an expression
std::unordered_map<std::pair<std::string, GraphDbTypes::Property>,
Expression *>
properties_;
bool has_range_ = false;
// Used in variable length and BFS expansions. Bounds can be nullptr. Inner
// element symbols can only be null in SINGLE expansion. Filter can be
// nullptr.
Expression *lower_bound_ = nullptr;
Expression *upper_bound_ = nullptr;
protected:
using PatternAtom::PatternAtom;
EdgeAtom(int uid, Identifier *identifier, Direction direction)
: PatternAtom(uid, identifier), direction_(direction) {}
EdgeAtom(int uid, Identifier *identifier, Direction direction,
const std::vector<GraphDbTypes::EdgeType> &edge_types)
: PatternAtom(uid, identifier),
direction_(direction),
edge_types_(edge_types) {}
};
class BreadthFirstAtom : public EdgeAtom {
friend class AstTreeStorage;
template <typename TPtr>
static TPtr *CloneOpt(TPtr *ptr, AstTreeStorage &storage) {
return ptr ? ptr->Clone(storage) : nullptr;
}
public:
DEFVISITABLE(TreeVisitor<TypedValue>);
bool Accept(HierarchicalTreeVisitor &visitor) override {
if (visitor.PreVisit(*this)) {
bool cont = identifier_->Accept(visitor);
if (cont && traversed_edge_identifier_)
cont = traversed_edge_identifier_->Accept(visitor);
if (cont && next_node_identifier_)
cont = next_node_identifier_->Accept(visitor);
if (cont && filter_expression_)
cont = filter_expression_->Accept(visitor);
}
return visitor.PostVisit(*this);
}
BreadthFirstAtom *Clone(AstTreeStorage &storage) const override {
auto bfs_atom = storage.Create<BreadthFirstAtom>(
identifier_->Clone(storage), direction_, edge_types_,
CloneOpt(traversed_edge_identifier_, storage),
CloneOpt(next_node_identifier_, storage),
CloneOpt(filter_expression_, storage));
bfs_atom->has_range_ = has_range_;
bfs_atom->lower_bound_ = CloneOpt(lower_bound_, storage);
bfs_atom->upper_bound_ = CloneOpt(upper_bound_, storage);
return bfs_atom;
}
Identifier *traversed_edge_identifier_ = nullptr;
Identifier *next_node_identifier_ = nullptr;
// Expression which evaluates to true in order to continue the BFS.
Identifier *inner_edge_ = nullptr;
Identifier *inner_node_ = nullptr;
Expression *filter_expression_ = nullptr;
protected:
using EdgeAtom::EdgeAtom;
BreadthFirstAtom(int uid, Identifier *identifier, Direction direction,
const std::vector<GraphDbTypes::EdgeType> &edge_types,
Identifier *traversed_edge_identifier,
Identifier *next_node_identifier,
Expression *filter_expression)
: EdgeAtom(uid, identifier, direction, edge_types),
traversed_edge_identifier_(traversed_edge_identifier),
next_node_identifier_(next_node_identifier),
filter_expression_(filter_expression) {
has_range_ = true;
}
using PatternAtom::PatternAtom;
EdgeAtom(int uid, Identifier *identifier, Type type, Direction direction)
: PatternAtom(uid, identifier), type_(type), direction_(direction) {}
// Creates an edge atom for a SINGLE expansion with the given types.
EdgeAtom(int uid, Identifier *identifier, Type type, Direction direction,
const std::vector<GraphDbTypes::EdgeType> &edge_types)
: PatternAtom(uid, identifier),
type_(type),
direction_(direction),
edge_types_(edge_types) {}
};
class Pattern : public Tree {

View File

@ -21,7 +21,6 @@ class With;
class Pattern;
class NodeAtom;
class EdgeAtom;
class BreadthFirstAtom;
class PrimitiveLiteral;
class ListLiteral;
class MapLiteral;
@ -66,9 +65,9 @@ using TreeCompositeVisitor = ::utils::CompositeVisitor<
InListOperator, ListMapIndexingOperator, ListSlicingOperator, IfOperator,
UnaryPlusOperator, UnaryMinusOperator, IsNullOperator, ListLiteral,
MapLiteral, PropertyLookup, LabelsTest, Aggregation, Function, All, Create,
Match, Return, With, Pattern, NodeAtom, EdgeAtom, BreadthFirstAtom, Delete,
Where, SetProperty, SetProperties, SetLabels, RemoveProperty, RemoveLabels,
Merge, Unwind>;
Match, Return, With, Pattern, NodeAtom, EdgeAtom, Delete, Where,
SetProperty, SetProperties, SetLabels, RemoveProperty, RemoveLabels, Merge,
Unwind>;
using TreeLeafVisitor = ::utils::LeafVisitor<Identifier, PrimitiveLiteral,
ParameterLookup, CreateIndex>;
@ -92,8 +91,7 @@ using TreeVisitor = ::utils::Visitor<
UnaryPlusOperator, UnaryMinusOperator, IsNullOperator, ListLiteral,
MapLiteral, PropertyLookup, LabelsTest, Aggregation, Function, All,
ParameterLookup, Create, Match, Return, With, Pattern, NodeAtom, EdgeAtom,
BreadthFirstAtom, Delete, Where, SetProperty, SetProperties, SetLabels,
RemoveProperty, RemoveLabels, Merge, Unwind, Identifier, PrimitiveLiteral,
CreateIndex>;
Delete, Where, SetProperty, SetProperties, SetLabels, RemoveProperty,
RemoveLabels, Merge, Unwind, Identifier, PrimitiveLiteral, CreateIndex>;
} // namespace query

View File

@ -420,21 +420,23 @@ antlrcpp::Any CypherMainVisitor::visitPatternElementChain(
antlrcpp::Any CypherMainVisitor::visitRelationshipPattern(
CypherParser::RelationshipPatternContext *ctx) {
auto *edge = storage_.Create<EdgeAtom>();
auto relationshipDetail = ctx->relationshipDetail();
auto *variableExpansion =
relationshipDetail ? relationshipDetail->variableExpansion() : nullptr;
bool is_bfs = false;
// Range expressions, only used in variable length and BFS.
Expression *lower = nullptr;
Expression *upper = nullptr;
if (variableExpansion)
std::tie(is_bfs, lower, upper) =
std::tie(is_bfs, edge->lower_bound_, edge->upper_bound_) =
variableExpansion->accept(this)
.as<std::tuple<bool, Expression *, Expression *>>();
auto *edge = (variableExpansion && is_bfs)
? storage_.Create<BreadthFirstAtom>()
: storage_.Create<EdgeAtom>();
if (!variableExpansion)
edge->type_ = EdgeAtom::Type::SINGLE;
else if (is_bfs)
edge->type_ = EdgeAtom::Type::BREADTH_FIRST;
else
edge->type_ = EdgeAtom::Type::DEPTH_FIRST;
if (ctx->leftArrowHead() && !ctx->rightArrowHead()) {
edge->direction_ = EdgeAtom::Direction::IN;
@ -468,35 +470,31 @@ antlrcpp::Any CypherMainVisitor::visitRelationshipPattern(
auto relationshipLambdas = relationshipDetail->relationshipLambda();
if (variableExpansion) {
edge->has_range_ = true;
edge->lower_bound_ = lower;
edge->upper_bound_ = upper;
switch (relationshipLambdas.size()) {
case 0:
// In variable length and BFS expansion inner variables are mandatory.
anonymous_identifiers.push_back(&edge->inner_edge_);
anonymous_identifiers.push_back(&edge->inner_node_);
break;
case 1: {
if (!is_bfs)
throw SemanticException("Relationship lambda only supported in BFS");
auto *lambda = relationshipLambdas[0];
auto *edge_bfs = dynamic_cast<BreadthFirstAtom *>(edge);
std::string traversed_edge_variable =
lambda->traversed_edge->accept(this);
edge_bfs->traversed_edge_identifier_ =
edge->inner_edge_ =
storage_.Create<Identifier>(traversed_edge_variable);
std::string traversed_node_variable =
lambda->traversed_node->accept(this);
edge_bfs->next_node_identifier_ =
edge->inner_node_ =
storage_.Create<Identifier>(traversed_node_variable);
edge_bfs->filter_expression_ = lambda->expression()->accept(this);
edge->filter_expression_ = lambda->expression()->accept(this);
break;
};
default:
throw SemanticException("Only one relationship lambda allowed");
}
} else if (!relationshipLambdas.empty()) {
throw SemanticException("Relationship lambda only supported in BFS");
throw SemanticException(
"Relationship lambda only supported in variable length expansion");
}
auto properties = relationshipDetail->properties();
@ -504,10 +502,6 @@ antlrcpp::Any CypherMainVisitor::visitRelationshipPattern(
case 0:
break;
case 1: {
// TODO support property filters in BFS
if (is_bfs)
throw SemanticException(
"BFS expansion and property maps not supported");
edge->properties_ =
properties[0]
->accept(this)

View File

@ -213,8 +213,8 @@ SymbolGenerator::ReturnType SymbolGenerator::Visit(Identifier &ident) {
if (HasSymbol(ident.name_)) {
throw RedeclareVariableError(ident.name_);
}
type = scope_.visiting_edge->has_range_ ? Symbol::Type::EdgeList
: Symbol::Type::Edge;
type = scope_.visiting_edge->IsVariable() ? Symbol::Type::EdgeList
: Symbol::Type::Edge;
}
symbol = GetOrCreateSymbol(ident.name_, ident.user_declared_, type);
} else if (scope_.in_pattern && !scope_.in_pattern_atom_identifier &&
@ -351,7 +351,7 @@ bool SymbolGenerator::PreVisit(EdgeAtom &edge_atom) {
"Bidirectional relationship are not supported "
"when creating an edge");
}
if (edge_atom.has_range_) {
if (edge_atom.IsVariable()) {
throw SemanticException(
"Variable length relationships are not supported when creating an "
"edge.");
@ -360,7 +360,7 @@ bool SymbolGenerator::PreVisit(EdgeAtom &edge_atom) {
for (auto kv : edge_atom.properties_) {
kv.second->Accept(*this);
}
if (edge_atom.has_range_) {
if (edge_atom.IsVariable()) {
scope_.in_edge_range = true;
if (edge_atom.lower_bound_) {
edge_atom.lower_bound_->Accept(*this);
@ -369,6 +369,15 @@ bool SymbolGenerator::PreVisit(EdgeAtom &edge_atom) {
edge_atom.upper_bound_->Accept(*this);
}
scope_.in_edge_range = false;
scope_.in_pattern = false;
if (edge_atom.filter_expression_) {
VisitWithIdentifiers(*edge_atom.filter_expression_,
{edge_atom.inner_edge_, edge_atom.inner_node_});
} else {
for (auto i : {edge_atom.inner_edge_, edge_atom.inner_node_})
symbol_table_[*i] = CreateSymbol(i->name_, true);
}
scope_.in_pattern = true;
}
scope_.in_pattern_atom_identifier = true;
edge_atom.identifier_->Accept(*this);
@ -410,33 +419,6 @@ void SymbolGenerator::VisitWithIdentifiers(
}
}
bool SymbolGenerator::PreVisit(BreadthFirstAtom &bf_atom) {
scope_.visiting_edge = &bf_atom;
if (scope_.in_create || scope_.in_merge) {
throw SemanticException("BFS cannot be used to create edges.");
}
// Visiting BFS filter and upper bound expressions is not a pattern.
scope_.in_pattern = false;
if (bf_atom.upper_bound_) {
bf_atom.upper_bound_->Accept(*this);
}
if (bf_atom.filter_expression_) {
VisitWithIdentifiers(
*bf_atom.filter_expression_,
{bf_atom.traversed_edge_identifier_, bf_atom.next_node_identifier_});
}
scope_.in_pattern = true;
scope_.in_pattern_atom_identifier = true;
bf_atom.identifier_->Accept(*this);
scope_.in_pattern_atom_identifier = false;
return false;
}
bool SymbolGenerator::PostVisit(BreadthFirstAtom &) {
scope_.visiting_edge = nullptr;
return true;
}
bool SymbolGenerator::HasSymbol(const std::string &name) {
return scope_.symbols.find(name) != scope_.symbols.end();
}

View File

@ -56,8 +56,6 @@ class SymbolGenerator : public HierarchicalTreeVisitor {
bool PostVisit(NodeAtom &) override;
bool PreVisit(EdgeAtom &) override;
bool PostVisit(EdgeAtom &) override;
bool PreVisit(BreadthFirstAtom &) override;
bool PostVisit(BreadthFirstAtom &) override;
private:
// Scope stores the state of where we are when visiting the AST and a map of

View File

@ -44,7 +44,6 @@ class ExpressionEvaluator : public TreeVisitor<TypedValue> {
BLOCK_VISIT(Pattern);
BLOCK_VISIT(NodeAtom);
BLOCK_VISIT(EdgeAtom);
BLOCK_VISIT(BreadthFirstAtom);
BLOCK_VISIT(Delete);
BLOCK_VISIT(Where);
BLOCK_VISIT(SetProperty);

View File

@ -44,7 +44,6 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor {
static constexpr double MakeScanAllByLabelPropertyRange{1.1};
static constexpr double kExpand{2.0};
static constexpr double kExpandVariable{3.0};
static constexpr double kExpandBreadthFirst{5.0};
static constexpr double kFilter{1.5};
static constexpr double kExpandUniquenessFilter{1.5};
static constexpr double kUnwind{1.3};
@ -53,7 +52,6 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor {
struct CardParam {
static constexpr double kExpand{3.0};
static constexpr double kExpandVariable{9.0};
static constexpr double kExpandBreadthFirst{8.0};
static constexpr double kFilter{0.25};
static constexpr double kExpandUniquenessFilter{0.95};
};
@ -144,7 +142,6 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor {
POST_VISIT_CARD_FIRST(Expand);
POST_VISIT_CARD_FIRST(ExpandVariable);
POST_VISIT_CARD_FIRST(ExpandBreadthFirst);
#undef POST_VISIT_CARD_FIRST

View File

@ -384,7 +384,7 @@ ExpandCommon::ExpandCommon(
Symbol node_symbol, Symbol edge_symbol, EdgeAtom::Direction direction,
const std::vector<GraphDbTypes::EdgeType> &edge_types,
const std::shared_ptr<LogicalOperator> &input, Symbol input_symbol,
bool existing_node, bool existing_edge, GraphView graph_view)
bool existing_node, GraphView graph_view)
: node_symbol_(node_symbol),
edge_symbol_(edge_symbol),
direction_(direction),
@ -392,7 +392,6 @@ ExpandCommon::ExpandCommon(
input_(input ? input : std::make_shared<Once>()),
input_symbol_(input_symbol),
existing_node_(existing_node),
existing_edge_(existing_edge),
graph_view_(graph_view) {}
bool ExpandCommon::HandleExistingNode(const VertexAccessor &new_node,
@ -419,22 +418,6 @@ Expand::ExpandCursor::ExpandCursor(const Expand &self, GraphDbAccessor &db)
: self_(self), input_cursor_(self.input_->MakeCursor(db)), db_(db) {}
bool Expand::ExpandCursor::Pull(Frame &frame, Context &context) {
// Helper function for handling existing-edge checking. Returns false only if
// existing_edge is true and the given new_edge is not equal to the existing
// one.
auto handle_existing_edge = [this, &frame](const EdgeAccessor &new_edge) {
if (self_.existing_edge_) {
TypedValue &old_edge_value = frame[self_.edge_symbol_];
// old_edge_value may be Null when using optional matching
if (old_edge_value.IsNull()) return false;
ExpectType(self_.edge_symbol_, old_edge_value, TypedValue::Type::Edge);
return old_edge_value.Value<EdgeAccessor>() == new_edge;
} else {
frame[self_.edge_symbol_] = new_edge;
return true;
}
};
// A helper function for expanding a node from an edge.
auto pull_node = [this, &frame](const EdgeAccessor &new_edge,
EdgeAtom::Direction direction) {
@ -456,11 +439,9 @@ bool Expand::ExpandCursor::Pull(Frame &frame, Context &context) {
// attempt to get a value from the incoming edges
if (in_edges_ && *in_edges_it_ != in_edges_->end()) {
EdgeAccessor edge = *(*in_edges_it_)++;
if (handle_existing_edge(edge)) {
pull_node(edge, EdgeAtom::Direction::IN);
return true;
} else
continue;
frame[self_.edge_symbol_] = edge;
pull_node(edge, EdgeAtom::Direction::IN);
return true;
}
// attempt to get a value from the outgoing edges
@ -471,11 +452,9 @@ bool Expand::ExpandCursor::Pull(Frame &frame, Context &context) {
// already done in the block above
if (self_.direction_ == EdgeAtom::Direction::BOTH && edge.is_cycle())
continue;
if (handle_existing_edge(edge)) {
pull_node(edge, EdgeAtom::Direction::OUT);
return true;
} else
continue;
frame[self_.edge_symbol_] = edge;
pull_node(edge, EdgeAtom::Direction::OUT);
return true;
}
// if we are here, either the edges have not been initialized,
@ -566,18 +545,29 @@ bool Expand::ExpandCursor::InitEdges(Frame &frame, Context &context) {
}
ExpandVariable::ExpandVariable(
Symbol node_symbol, Symbol edge_symbol, EdgeAtom::Direction direction,
Symbol node_symbol, Symbol edge_symbol, EdgeAtom::Type type,
EdgeAtom::Direction direction,
const std::vector<GraphDbTypes::EdgeType> &edge_types, bool is_reverse,
Expression *lower_bound, Expression *upper_bound,
const std::shared_ptr<LogicalOperator> &input, Symbol input_symbol,
bool existing_node, bool existing_edge, GraphView graph_view,
Expression *filter)
bool existing_node, Symbol inner_edge_symbol, Symbol inner_node_symbol,
Expression *filter, GraphView graph_view)
: ExpandCommon(node_symbol, edge_symbol, direction, edge_types, input,
input_symbol, existing_node, existing_edge, graph_view),
input_symbol, existing_node, graph_view),
type_(type),
is_reverse_(is_reverse),
lower_bound_(lower_bound),
upper_bound_(upper_bound),
is_reverse_(is_reverse),
filter_(filter) {}
inner_edge_symbol_(inner_edge_symbol),
inner_node_symbol_(inner_node_symbol),
filter_(filter) {
debug_assert(
type_ == EdgeAtom::Type::DEPTH_FIRST ||
type_ == EdgeAtom::Type::BREADTH_FIRST,
"ExpandVariable can only be used with breadth or depth first type");
debug_assert(!(type_ == EdgeAtom::Type::BREADTH_FIRST && is_reverse),
"Breadth first expansion can't be reversed");
}
ACCEPT_WITH_INPUT(ExpandVariable)
@ -658,16 +648,9 @@ class ExpandVariableCursor : public Cursor {
if (PullInput(frame, context)) {
// if lower bound is zero we also yield empty paths
if (lower_bound_ == 0) {
auto &edges_on_frame =
frame[self_.edge_symbol_].Value<std::vector<TypedValue>>();
auto &start_vertex =
frame[self_.input_symbol_].Value<VertexAccessor>();
// take into account existing_edge when yielding empty paths
if ((!self_.existing_edge_ || edges_on_frame.empty()) &&
// Place the start vertex on the frame.
self_.HandleExistingNode(start_vertex, frame)) {
if (self_.filter_ && !EvaluateFilter(evaluator, self_.filter_))
continue;
if (self_.HandleExistingNode(start_vertex, frame)) {
return true;
}
}
@ -704,7 +687,8 @@ class ExpandVariableCursor : public Cursor {
self_.edge_types_))>
edges_;
// an iterator indicating the possition in the corresponding edges_ element
// an iterator indicating the possition in the corresponding edges_
// element
std::vector<decltype(edges_.begin()->begin())> edges_it_;
/**
@ -715,7 +699,8 @@ class ExpandVariableCursor : public Cursor {
* is exhausted.
*/
bool PullInput(Frame &frame, Context &context) {
// Input Vertex could be null if it is created by a failed optional match.
// Input Vertex could be null if it is created by a failed optional
// match.
// In those cases we skip that input pull and continue with the next.
while (true) {
if (!input_cursor_->Pull(frame, context)) return false;
@ -751,82 +736,31 @@ class ExpandVariableCursor : public Cursor {
}
// reset the frame value to an empty edge list
if (!self_.existing_edge_)
frame[self_.edge_symbol_] = std::vector<TypedValue>();
frame[self_.edge_symbol_] = std::vector<TypedValue>();
return true;
}
}
/**
* Enum that indicates what happened during attempted edge
* existence and placement check
* YIELD - indicates that either existence matched fully
* or existence is disabled
* PREFIX - indicates that existence is enabled and the current
* edge-list is a prefix of the existing one, so expansion
* should continue but this particular edge-list should not
* be yielded as a valid result
* MISMATCH - indicates that existence is enabled and the
* current edge-list is not a match, this whole expansion
* branch should be abandoned
*/
enum class EdgePlacementResult { YIELD, PREFIX, MISMATCH };
/**
* Handles the placement of a new edge expansion on the frame
* (into the the list of edges already on the frame). Also handles
* the logic of existing-symbol checking. If we are expanding into
* the existing symbol, then this cursor will not modify the value
* on the frame but will only yield true when it's expansion matches.
* Return value of this function indicates those situations. If we
* are not expanding into an existing symbol, but a new one, this
* function will always append the new_edge to the ones on the
* frame and return EdgePlacementResult::YIELD.
*/
EdgePlacementResult HandleEdgePlacement(
const EdgeAccessor &new_edge, std::vector<TypedValue> &edges_on_frame) {
if (self_.existing_edge_) {
// check if the edge to add corresponds to the existing set's
// corresponding element
if (edges_on_frame.size() < edges_.size())
return EdgePlacementResult::MISMATCH;
int last_edge_index =
self_.is_reverse_ ? 0 : static_cast<int>(edges_.size()) - 1;
EdgeAccessor &edge_on_frame =
edges_on_frame[last_edge_index].Value<EdgeAccessor>();
if (edge_on_frame != new_edge) return EdgePlacementResult::MISMATCH;
// the new_edge matches the corresponding frame element
// check if it's the last one (in which case we can yield)
// or a subset (in which case we continue expansion but don't yield)
if (edges_on_frame.size() == edges_.size())
return EdgePlacementResult::YIELD;
else
return EdgePlacementResult::PREFIX;
// Helper function for appending an edge to the list on the frame.
void AppendEdge(const EdgeAccessor &new_edge,
std::vector<TypedValue> &edges_on_frame) {
// We are placing an edge on the frame. It is possible that there already
// exists an edge on the frame for this level. If so first remove it.
debug_assert(edges_.size() > 0, "Edges are empty");
if (self_.is_reverse_) {
// TODO: This is innefficient, we should look into replacing
// vector with something else for TypedValue::List.
size_t diff = edges_on_frame.size() -
std::min(edges_on_frame.size(), edges_.size() - 1U);
if (diff > 0U)
edges_on_frame.erase(edges_on_frame.begin(),
edges_on_frame.begin() + diff);
edges_on_frame.insert(edges_on_frame.begin(), new_edge);
} else {
// we are placing an edge on the frame
// it is possible that there already exists an edge on the frame for
// this level. if so first remove it
debug_assert(edges_.size() > 0, "Edges are empty");
if (self_.is_reverse_) {
// TODO: This is innefficient, we should look into replacing
// vector with something else for TypedValue::List.
size_t diff = edges_on_frame.size() -
std::min(edges_on_frame.size(), edges_.size() - 1U);
if (diff > 0U)
edges_on_frame.erase(edges_on_frame.begin(),
edges_on_frame.begin() + diff);
edges_on_frame.insert(edges_on_frame.begin(), new_edge);
} else {
edges_on_frame.resize(
std::min(edges_on_frame.size(), edges_.size() - 1U));
edges_on_frame.emplace_back(new_edge);
}
return EdgePlacementResult::YIELD;
edges_on_frame.resize(
std::min(edges_on_frame.size(), edges_.size() - 1U));
edges_on_frame.emplace_back(new_edge);
}
}
@ -843,10 +777,9 @@ class ExpandVariableCursor : public Cursor {
ExpressionEvaluator evaluator(frame, context.parameters_,
context.symbol_table_, db_,
self_.graph_view_);
// some expansions might not be valid due to
// edge uniqueness, existing_edge, existing_node criterions,
// so expand in a loop until either the input vertex is
// exhausted or a valid variable-length expansion is available
// Some expansions might not be valid due to edge uniqueness and
// existing_node criterions, so expand in a loop until either the input
// vertex is exhausted or a valid variable-length expansion is available.
while (true) {
// pop from the stack while there is stuff to pop and the current
// level is exhausted
@ -866,17 +799,15 @@ class ExpandVariableCursor : public Cursor {
// elements as edges_ due to edge-uniqueness (when a whole layer
// gets exhausted but no edges are valid). for that reason only
// pop from edges_on_frame if they contain enough elements
if (!self_.existing_edge_) {
if (self_.is_reverse_) {
auto diff = edges_on_frame.size() -
std::min(edges_on_frame.size(), edges_.size());
if (diff > 0) {
edges_on_frame.erase(edges_on_frame.begin(),
edges_on_frame.begin() + diff);
}
} else {
edges_on_frame.resize(std::min(edges_on_frame.size(), edges_.size()));
if (self_.is_reverse_) {
auto diff = edges_on_frame.size() -
std::min(edges_on_frame.size(), edges_.size());
if (diff > 0) {
edges_on_frame.erase(edges_on_frame.begin(),
edges_on_frame.begin() + diff);
}
} else {
edges_on_frame.resize(std::min(edges_on_frame.size(), edges_.size()));
}
// if we are here, we have a valid stack,
@ -884,26 +815,15 @@ class ExpandVariableCursor : public Cursor {
std::pair<EdgeAccessor, EdgeAtom::Direction> current_edge =
*edges_it_.back()++;
// check edge-uniqueness
// only needs to be done if we're not expanding into existing edge
// otherwise it gives false positives
if (!self_.existing_edge_) {
bool found_existing = std::any_of(
edges_on_frame.begin(), edges_on_frame.end(),
[&current_edge](const TypedValue &edge) {
return current_edge.first == edge.Value<EdgeAccessor>();
});
if (found_existing) continue;
}
auto edge_placement_result =
HandleEdgePlacement(current_edge.first, edges_on_frame);
if (edge_placement_result == EdgePlacementResult::MISMATCH) continue;
// Skip expanding out of filtered expansion. It is assumed that the
// expression does not use the vertex which has yet to be put on frame.
// Therefore, this check is done as soon as the edge is on the frame.
if (self_.filter_ && !EvaluateFilter(evaluator, self_.filter_)) continue;
// Check edge-uniqueness.
bool found_existing =
std::any_of(edges_on_frame.begin(), edges_on_frame.end(),
[&current_edge](const TypedValue &edge) {
return current_edge.first == edge.Value<EdgeAccessor>();
});
if (found_existing) continue;
AppendEdge(current_edge.first, edges_on_frame);
VertexAccessor current_vertex =
current_edge.second == EdgeAtom::Direction::IN
? current_edge.first.from()
@ -911,6 +831,11 @@ class ExpandVariableCursor : public Cursor {
if (!self_.HandleExistingNode(current_vertex, frame)) continue;
// Skip expanding out of filtered expansion.
frame[self_.inner_edge_symbol_] = current_edge.first;
frame[self_.inner_node_symbol_] = current_vertex;
if (self_.filter_ && !EvaluateFilter(evaluator, self_.filter_)) continue;
// we are doing depth-first search, so place the current
// edge's expansions onto the stack, if we should continue to expand
if (upper_bound_ > static_cast<int64_t>(edges_.size())) {
@ -920,11 +845,8 @@ class ExpandVariableCursor : public Cursor {
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 =
static_cast<int64_t>(edges_on_frame.size()) >= lower_bound_;
if (bound_ok && edge_placement_result == EdgePlacementResult::YIELD)
// We only yield true if we satisfy the lower bound.
if (static_cast<int64_t>(edges_on_frame.size()) >= lower_bound_)
return true;
else
continue;
@ -932,165 +854,161 @@ class ExpandVariableCursor : public Cursor {
}
};
std::unique_ptr<Cursor> ExpandVariable::MakeCursor(GraphDbAccessor &db) const {
return std::make_unique<ExpandVariableCursor>(*this, db);
}
class ExpandBreadthFirstCursor : public query::plan::Cursor {
public:
ExpandBreadthFirstCursor(const ExpandVariable &self, GraphDbAccessor &db)
: self_(self), db_(db), input_cursor_(self_.input_->MakeCursor(db)) {}
ExpandBreadthFirst::ExpandBreadthFirst(
Symbol node_symbol, Symbol edge_list_symbol, EdgeAtom::Direction direction,
const std::vector<GraphDbTypes::EdgeType> &edge_types,
Expression *max_depth,
std::experimental::optional<Symbol> inner_node_symbol,
std::experimental::optional<Symbol> inner_edge_symbol, Expression *where,
const std::shared_ptr<LogicalOperator> &input, Symbol input_symbol,
bool existing_node, GraphView graph_view)
: node_symbol_(node_symbol),
edge_list_symbol_(edge_list_symbol),
direction_(direction),
edge_types_(edge_types),
max_depth_(max_depth),
inner_node_symbol_(inner_node_symbol),
inner_edge_symbol_(inner_edge_symbol),
where_(where),
input_(input ? input : std::make_shared<Once>()),
input_symbol_(input_symbol),
existing_node_(existing_node),
graph_view_(graph_view) {}
bool Pull(Frame &frame, Context &context) override {
// evaulator for the filtering condition
ExpressionEvaluator evaluator(frame, context.parameters_,
context.symbol_table_, db_,
self_.graph_view_);
ACCEPT_WITH_INPUT(ExpandBreadthFirst)
// for the given (edge, vertex) pair checks if they satisfy the
// "where" condition. if so, places them in the to_visit_ structure.
auto expand_pair = [this, &evaluator, &frame](EdgeAccessor edge,
VertexAccessor vertex) {
// if we already processed the given vertex it doesn't get expanded
if (processed_.find(vertex) != processed_.end()) return;
std::unique_ptr<Cursor> ExpandBreadthFirst::MakeCursor(
GraphDbAccessor &db) const {
return std::make_unique<ExpandBreadthFirst::Cursor>(*this, db);
}
ExpandBreadthFirst::Cursor::Cursor(const ExpandBreadthFirst &self,
GraphDbAccessor &db)
: self_(self), db_(db), input_cursor_(self_.input_->MakeCursor(db)) {}
bool ExpandBreadthFirst::Cursor::Pull(Frame &frame, Context &context) {
// evaulator for the filtering condition
ExpressionEvaluator evaluator(frame, context.parameters_,
context.symbol_table_, db_, self_.graph_view_);
// for the given (edge, vertex) pair checks if they satisfy the
// "where" condition. if so, places them in the to_visit_ structure.
auto expand_pair = [this, &evaluator, &frame](EdgeAccessor edge,
VertexAccessor vertex) {
// if we already processed the given vertex it doesn't get expanded
if (processed_.find(vertex) != processed_.end()) return;
SwitchAccessor(edge, self_.graph_view_);
SwitchAccessor(vertex, self_.graph_view_);
if (self_.where_) {
// to evaluate the where expression we need the inner
// values on the frame
if (self_.inner_edge_symbol_) frame[*self_.inner_edge_symbol_] = edge;
if (self_.inner_node_symbol_) frame[*self_.inner_node_symbol_] = vertex;
TypedValue result = self_.where_->Accept(evaluator);
switch (result.type()) {
case TypedValue::Type::Null:
return;
case TypedValue::Type::Bool:
if (!result.Value<bool>()) return;
break;
default:
throw QueryRuntimeException(
"Expansion condition must be boolean or null");
}
}
to_visit_next_.emplace_back(edge, vertex);
processed_.emplace(vertex, edge);
};
// populates the to_visit_next_ structure with expansions
// from the given vertex. skips expansions that don't satisfy
// the "where" condition.
auto expand_from_vertex = [this, &expand_pair](VertexAccessor &vertex) {
if (self_.direction_ != EdgeAtom::Direction::IN) {
for (const EdgeAccessor &edge : vertex.out(&self_.edge_types_))
expand_pair(edge, edge.to());
}
if (self_.direction_ != EdgeAtom::Direction::OUT) {
for (const EdgeAccessor &edge : vertex.in(&self_.edge_types_))
expand_pair(edge, edge.from());
}
};
// do it all in a loop because we skip some elements
while (true) {
// if we have nothing to visit on the current depth, switch to next
if (to_visit_current_.empty()) to_visit_current_.swap(to_visit_next_);
// if current is still empty, it means both are empty, so pull from input
if (to_visit_current_.empty()) {
if (!input_cursor_->Pull(frame, context)) return false;
processed_.clear();
auto vertex_value = frame[self_.input_symbol_];
// it is possible that the vertex is Null due to optional matching
if (vertex_value.IsNull()) continue;
auto vertex = vertex_value.Value<VertexAccessor>();
SwitchAccessor(edge, self_.graph_view_);
SwitchAccessor(vertex, self_.graph_view_);
processed_.emplace(vertex, std::experimental::nullopt);
expand_from_vertex(vertex);
max_depth_ = self_.max_depth_
? EvaluateInt(evaluator, self_.max_depth_,
"Max depth in breadth-first expansion")
: std::numeric_limits<int>::max();
if (max_depth_ < 1)
throw QueryRuntimeException(
"Max depth in breadth-first expansion must be greater then zero");
// go back to loop start and see if we expanded anything
continue;
}
frame[self_.inner_edge_symbol_] = edge;
frame[self_.inner_node_symbol_] = vertex;
// take the next expansion from the queue
std::pair<EdgeAccessor, VertexAccessor> expansion =
to_visit_current_.front();
to_visit_current_.pop_front();
if (self_.filter_) {
TypedValue result = self_.filter_->Accept(evaluator);
switch (result.type()) {
case TypedValue::Type::Null:
return;
case TypedValue::Type::Bool:
if (!result.Value<bool>()) return;
break;
default:
throw QueryRuntimeException(
"Expansion condition must be boolean or null");
}
}
to_visit_next_.emplace_back(edge, vertex);
processed_.emplace(vertex, edge);
};
// create the frame value for the edges
std::vector<TypedValue> edge_list{expansion.first};
auto last_vertex = expansion.second;
// populates the to_visit_next_ structure with expansions
// from the given vertex. skips expansions that don't satisfy
// the "where" condition.
auto expand_from_vertex = [this, &expand_pair](VertexAccessor &vertex) {
if (self_.direction_ != EdgeAtom::Direction::IN) {
for (const EdgeAccessor &edge : vertex.out(&self_.edge_types_))
expand_pair(edge, edge.to());
}
if (self_.direction_ != EdgeAtom::Direction::OUT) {
for (const EdgeAccessor &edge : vertex.in(&self_.edge_types_))
expand_pair(edge, edge.from());
}
};
// do it all in a loop because we skip some elements
while (true) {
const EdgeAccessor &last_edge = edge_list.back().Value<EdgeAccessor>();
last_vertex =
last_edge.from() == last_vertex ? last_edge.to() : last_edge.from();
// origin_vertex must be in processed
const auto &previous_edge = processed_.find(last_vertex)->second;
if (!previous_edge) break;
// if we have nothing to visit on the current depth, switch to next
if (to_visit_current_.empty()) to_visit_current_.swap(to_visit_next_);
edge_list.push_back(previous_edge.value());
// if current is still empty, it means both are empty, so pull from
// input
if (to_visit_current_.empty()) {
if (!input_cursor_->Pull(frame, context)) return false;
processed_.clear();
auto vertex_value = frame[self_.input_symbol_];
// it is possible that the vertex is Null due to optional matching
if (vertex_value.IsNull()) continue;
auto vertex = vertex_value.Value<VertexAccessor>();
SwitchAccessor(vertex, self_.graph_view_);
processed_.emplace(vertex, std::experimental::nullopt);
expand_from_vertex(vertex);
upper_bound_ = self_.upper_bound_
? EvaluateInt(evaluator, self_.upper_bound_,
"Max depth in breadth-first expansion")
: std::numeric_limits<int>::max();
if (upper_bound_ < 1)
throw QueryRuntimeException(
"Max depth in breadth-first expansion must be greater then "
"zero");
// go back to loop start and see if we expanded anything
continue;
}
// take the next expansion from the queue
std::pair<EdgeAccessor, VertexAccessor> expansion =
to_visit_current_.front();
to_visit_current_.pop_front();
// create the frame value for the edges
std::vector<TypedValue> edge_list{expansion.first};
auto last_vertex = expansion.second;
while (true) {
const EdgeAccessor &last_edge = edge_list.back().Value<EdgeAccessor>();
last_vertex =
last_edge.from() == last_vertex ? last_edge.to() : last_edge.from();
// origin_vertex must be in processed
const auto &previous_edge = processed_.find(last_vertex)->second;
if (!previous_edge) break;
edge_list.push_back(previous_edge.value());
}
// expand only if what we've just expanded is less then max depth
if (static_cast<int>(edge_list.size()) < upper_bound_)
expand_from_vertex(expansion.second);
// place destination node on the frame, handle existence flag
if (self_.existing_node_) {
TypedValue &node = frame[self_.node_symbol_];
// due to optional matching the existing node could be null
if (node.IsNull() || (node != expansion.second).Value<bool>()) continue;
} else
frame[self_.node_symbol_] = expansion.second;
// place edges on the frame in the correct order
std::reverse(edge_list.begin(), edge_list.end());
frame[self_.edge_symbol_] = std::move(edge_list);
return true;
}
// expand only if what we've just expanded is less then max depth
if (static_cast<int>(edge_list.size()) < max_depth_)
expand_from_vertex(expansion.second);
// place destination node on the frame, handle existence flag
if (self_.existing_node_) {
TypedValue &node = frame[self_.node_symbol_];
// due to optional matching the existing node could be null
if (node.IsNull() || (node != expansion.second).Value<bool>()) continue;
} else
frame[self_.node_symbol_] = expansion.second;
// place edges on the frame in the correct order
std::reverse(edge_list.begin(), edge_list.end());
frame[self_.edge_list_symbol_] = std::move(edge_list);
return true;
}
}
void Reset() override {
input_cursor_->Reset();
processed_.clear();
to_visit_next_.clear();
to_visit_current_.clear();
}
void ExpandBreadthFirst::Cursor::Reset() {
input_cursor_->Reset();
processed_.clear();
to_visit_next_.clear();
to_visit_current_.clear();
private:
const ExpandVariable &self_;
GraphDbAccessor &db_;
const std::unique_ptr<query::plan::Cursor> input_cursor_;
// maximum depth of the expansion. calculated on each pull
// from the input, the initial value is irrelevant.
int upper_bound_{-1};
// maps vertices to the edge they got expanded from. it is an optional
// edge because the root does not get expanded from anything.
// contains visited vertices as well as those scheduled to be visited.
std::unordered_map<VertexAccessor, std::experimental::optional<EdgeAccessor>>
processed_;
// edge/vertex pairs we have yet to visit, for current and next depth
std::deque<std::pair<EdgeAccessor, VertexAccessor>> to_visit_current_;
std::deque<std::pair<EdgeAccessor, VertexAccessor>> to_visit_next_;
};
std::unique_ptr<Cursor> ExpandVariable::MakeCursor(GraphDbAccessor &db) const {
if (type_ == EdgeAtom::Type::BREADTH_FIRST)
return std::make_unique<ExpandBreadthFirstCursor>(*this, db);
else
return std::make_unique<ExpandVariableCursor>(*this, db);
}
class ConstructNamedPathCursor : public Cursor {
@ -1118,7 +1036,8 @@ class ConstructNamedPathCursor : public Cursor {
query::Path path(start_vertex.ValueVertex());
// If the last path element symbol was for an edge list, then
// the next symbol is a vertex and it should not append to the path because
// the next symbol is a vertex and it should not append to the path
// because
// expansion already did it.
bool last_was_edge_list = false;
@ -1139,7 +1058,8 @@ class ConstructNamedPathCursor : public Cursor {
break;
case TypedValue::Type::List: {
last_was_edge_list = true;
// We need to expand all edges in the list and intermediary vertices.
// We need to expand all edges in the list and intermediary
// vertices.
const std::vector<TypedValue> &edges = expansion.ValueList();
for (const auto &edge_value : edges) {
const EdgeAccessor &edge = edge_value.ValueEdge();
@ -1260,7 +1180,8 @@ Delete::DeleteCursor::DeleteCursor(const Delete &self, GraphDbAccessor &db)
bool Delete::DeleteCursor::Pull(Frame &frame, Context &context) {
if (!input_cursor_->Pull(frame, context)) return false;
// Delete should get the latest information, this way it is also possible to
// Delete should get the latest information, this way it is also possible
// to
// delete newly added nodes and edges.
ExpressionEvaluator evaluator(frame, context.parameters_,
context.symbol_table_, db_, GraphView::NEW);
@ -1294,7 +1215,8 @@ bool Delete::DeleteCursor::Pull(Frame &frame, Context &context) {
break;
}
// skip Edges (already deleted) and Nulls (can occur in optional match)
// skip Edges (already deleted) and Nulls (can occur in optional
// match)
case TypedValue::Type::Edge:
case TypedValue::Type::Null:
break;
@ -1342,7 +1264,8 @@ bool SetProperty::SetPropertyCursor::Pull(Frame &frame, Context &context) {
// Skip setting properties on Null (can occur in optional match).
break;
case TypedValue::Type::Map:
// Semantically modifying a map makes sense, but it's not supported due to
// Semantically modifying a map makes sense, but it's not supported due
// to
// all the copying we do (when PropertyValue -> TypedValue and in
// ExpressionEvaluator). So even though we set a map property here, that
// is never visible to the user and it's not stored.
@ -1427,7 +1350,8 @@ void SetProperties::SetPropertiesCursor::Set(TRecordAccessor &record,
}
}
// instantiate the SetProperties function with concrete TRecordAccessor types
// instantiate the SetProperties function with concrete TRecordAccessor
// types
template void SetProperties::SetPropertiesCursor::Set(
RecordAccessor<Vertex> &record, const TypedValue &rhs) const;
template void SetProperties::SetPropertiesCursor::Set(
@ -1597,9 +1521,9 @@ bool ExpandUniquenessFilter<TAccessor>::ExpandUniquenessFilterCursor::Pull(
TypedValue &expand_value = frame[self_.expand_symbol_];
for (const auto &previous_symbol : self_.previous_symbols_) {
TypedValue &previous_value = frame[previous_symbol];
// This shouldn't raise a TypedValueException, because the planner makes
// sure these are all of the expected type. In case they are not, an error
// should be raised long before this code is executed.
// This shouldn't raise a TypedValueException, because the planner
// makes sure these are all of the expected type. In case they are not, an
// error should be raised long before this code is executed.
if (ContainsSame<TAccessor>(previous_value, expand_value)) return false;
}
return true;
@ -1755,8 +1679,8 @@ bool Aggregate::AggregateCursor::Pull(Frame &frame, Context &context) {
pulled_all_input_ = true;
aggregation_it_ = aggregation_.begin();
// in case there is no input and no group_bys we need to return true just
// this once
// in case there is no input and no group_bys we need to return true
// just this once
if (aggregation_.empty() && self_.group_by_.empty()) {
// place default aggregation values on the frame
for (const auto &elem : self_.aggregations_)
@ -1832,12 +1756,12 @@ void Aggregate::AggregateCursor::EnsureInitialized(
void Aggregate::AggregateCursor::Update(
Frame &, const SymbolTable &, ExpressionEvaluator &evaluator,
Aggregate::AggregateCursor::AggregationValue &agg_value) {
debug_assert(
self_.aggregations_.size() == agg_value.values_.size(),
"Expected as much AggregationValue.values_ as there are aggregations.");
debug_assert(
self_.aggregations_.size() == agg_value.counts_.size(),
"Expected as much AggregationValue.counts_ as there are aggregations.");
debug_assert(self_.aggregations_.size() == agg_value.values_.size(),
"Expected as much AggregationValue.values_ as there are "
"aggregations.");
debug_assert(self_.aggregations_.size() == agg_value.counts_.size(),
"Expected as much AggregationValue.counts_ as there are "
"aggregations.");
// we iterate over counts, values and aggregation info at the same time
auto count_it = agg_value.counts_.begin();
@ -2054,8 +1978,8 @@ Limit::LimitCursor::LimitCursor(const Limit &self, GraphDbAccessor &db)
bool Limit::LimitCursor::Pull(Frame &frame, Context &context) {
// we need to evaluate the limit expression before the first input Pull
// because it might be 0 and thereby we shouldn't Pull from input at all
// we can do this before Pulling from the input because the limit expression
// is not allowed to contain any identifiers
// we can do this before Pulling from the input because the limit
// expression is not allowed to contain any identifiers
if (limit_ == -1) {
ExpressionEvaluator evaluator(frame, context.parameters_,
context.symbol_table_, db_);
@ -2280,7 +2204,8 @@ bool Merge::MergeCursor::Pull(Frame &frame, Context &context) {
debug_assert(merge_create_pull_result, "MergeCreate must never fail");
return true;
}
// we have exhausted merge_match_cursor_ after 1 or more successful Pulls
// we have exhausted merge_match_cursor_ after 1 or more successful
// Pulls
// attempt next input_cursor_ pull
pull_input_ = true;
return Pull(frame, context);

View File

@ -68,7 +68,6 @@ class ScanAllByLabelPropertyRange;
class ScanAllByLabelPropertyValue;
class Expand;
class ExpandVariable;
class ExpandBreadthFirst;
class ConstructNamedPath;
class Filter;
class Produce;
@ -95,8 +94,8 @@ class CreateIndex;
using LogicalOperatorCompositeVisitor = ::utils::CompositeVisitor<
Once, CreateNode, CreateExpand, ScanAll, ScanAllByLabel,
ScanAllByLabelPropertyRange, ScanAllByLabelPropertyValue, Expand,
ExpandVariable, ExpandBreadthFirst, ConstructNamedPath, Filter, Produce,
Delete, SetProperty, SetProperties, SetLabels, RemoveProperty, RemoveLabels,
ExpandVariable, ConstructNamedPath, Filter, Produce, Delete, SetProperty,
SetProperties, SetLabels, RemoveProperty, RemoveLabels,
ExpandUniquenessFilter<VertexAccessor>,
ExpandUniquenessFilter<EdgeAccessor>, Accumulate, AdvanceCommand, Aggregate,
Skip, Limit, OrderBy, Merge, Optional, Unwind, Distinct>;
@ -484,13 +483,12 @@ class ExpandCommon {
* that expansion should emanate from.
* @param existing_node If or not the node to be expanded is already present
* in the Frame and should just be checked for equality.
* @param existing_edge Same like `existing_node`, but for edges.
*/
ExpandCommon(Symbol node_symbol, Symbol edge_symbol,
EdgeAtom::Direction direction,
const std::vector<GraphDbTypes::EdgeType> &edge_types,
const std::shared_ptr<LogicalOperator> &input,
Symbol input_symbol, bool existing_node, bool existing_edge,
Symbol input_symbol, bool existing_node,
GraphView graph_view = GraphView::AS_IS);
const auto &input_symbol() const { return input_symbol_; }
@ -511,11 +509,9 @@ class ExpandCommon {
const std::shared_ptr<LogicalOperator> input_;
const Symbol input_symbol_;
// if the given node and edge atom refer to symbols
// (query identifiers) that have already been expanded
// and should be just validated in the frame
// If the given node atom refer to a symbol that has already been expanded and
// should be just validated in the frame.
const bool existing_node_;
const bool existing_edge_;
// from which state the input node should get expanded
const GraphView graph_view_;
@ -598,10 +594,11 @@ class Expand : public LogicalOperator, public ExpandCommon {
* pulled.
*/
class ExpandVariable : public LogicalOperator, public ExpandCommon {
// the ExpandVariableCursor is not declared in the header because
// the Cursors are not declared in the header because
// it's edges_ and edges_it_ are decltyped using a helper function
// that should be inaccessible (private class function won't compile)
friend class ExpandVariableCursor;
friend class ExpandBreadthFirstCursor;
public:
/**
@ -611,6 +608,8 @@ class ExpandVariable : public LogicalOperator, public ExpandCommon {
* Expansion length bounds are both inclusive (as in Neo's Cypher
* implementation).
*
* @param type - Either Type::DEPTH_FIRST (default variable-length expansion),
* or Type::BREADTH_FIRST.
* @param is_reverse Set to `true` if the edges written on frame should expand
* from `node_symbol` to `input_symbol`. Opposed to the usual expanding
* from `input_symbol` to `node_symbol`.
@ -618,124 +617,44 @@ class ExpandVariable : public LogicalOperator, public ExpandCommon {
* that get expanded (inclusive).
* @param upper_bound An optional indicator of the maximum number of edges
* that get expanded (inclusive).
* @param inner_edge_symbol Like `inner_node_symbol`
* @param inner_node_symbol For each expansion the node expanded into is
* assigned to this symbol so it can be evaulated by the 'where'
* expression.
* @param filter_ The filter that must be satisfied for an expansion to
* succeed. Can use inner(node|edge) symbols. If nullptr, it is ignored.
*/
ExpandVariable(Symbol node_symbol, Symbol edge_symbol,
ExpandVariable(Symbol node_symbol, Symbol edge_symbol, EdgeAtom::Type type,
EdgeAtom::Direction direction,
const std::vector<GraphDbTypes::EdgeType> &edge_types,
bool is_reverse, 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,
Expression *filter = nullptr);
Symbol input_symbol, bool existing_node,
Symbol inner_edge_symbol, Symbol inner_node_symbol,
Expression *filter = nullptr,
GraphView graph_view = GraphView::AS_IS);
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
std::unique_ptr<Cursor> MakeCursor(GraphDbAccessor &db) const override;
auto type() const { return type_; }
private:
const EdgeAtom::Type type_;
// True if the path should be written as expanding from node_symbol to
// input_symbol.
const bool is_reverse_;
// lower and upper bounds of the variable length expansion
// both are optional, defaults are (1, inf)
Expression *lower_bound_;
Expression *upper_bound_;
// True if the path should be written as expanding from node_symbol to
// input_symbol.
bool is_reverse_;
Expression *filter_;
};
/**
* Variable length breadth-first expansion operator. For a node existing in the
* frame it expands a variable number of edges and places them (in a list-type
* TypedValue), as well as the final destination node, on the frame.
*
* This class does not handle node/edge filtering based on properties, labels
* and edge types. However, it does handle filtering on existing node. Edge and
* vertex uniqueness are inherent to breadth-first expansion, due to filtering
* of already visited nodes.
*
* Most params are equivalent to @c ExpandCommon, and are documented there.
* Additional param documentation follows:
*
* @param max_depth Expression that controls the maximum allowed length of the
* expansion. Inclusive.
* @param inner_node_symbol For each expansion the node expanded into is
* assigned to this symbol so it can be evaulated by the 'where' expression.
* @param inner_edge_symbol Like `inner_node_symbol`
* @Param where An expression that controls whether an expansion is accepted or
* not. It can use the `inner` node and edge symbols. The expansion succeeds
* only if the expression evaluates to `true` for the expanded-into edge and
* node. `false` and `Null` prevent expansion, while any other value results in
* an exception.
*/
class ExpandBreadthFirst : public LogicalOperator {
public:
ExpandBreadthFirst(Symbol node_symbol, Symbol edge_list_symbol,
EdgeAtom::Direction direction,
const std::vector<GraphDbTypes::EdgeType> &edge_types,
Expression *max_depth,
std::experimental::optional<Symbol> inner_node_symbol,
std::experimental::optional<Symbol> inner_edge_symbol,
Expression *where,
const std::shared_ptr<LogicalOperator> &input,
Symbol input_symbol, bool existing_node,
GraphView graph_view = GraphView::AS_IS);
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
std::unique_ptr<Cursor> MakeCursor(GraphDbAccessor &db) const override;
private:
class Cursor : public query::plan::Cursor {
public:
Cursor(const ExpandBreadthFirst &self, GraphDbAccessor &db);
bool Pull(Frame &, Context &) override;
void Reset() override;
private:
const ExpandBreadthFirst &self_;
GraphDbAccessor &db_;
const std::unique_ptr<query::plan::Cursor> input_cursor_;
// maximum depth of the expansion. calculated on each pull
// from the input, the initial value is irrelevant.
int max_depth_{-1};
// maps vertices to the edge they got expanded from. it is an optional
// edge because the root does not get expanded from anything.
// contains visited vertices as well as those scheduled to be visited.
std::unordered_map<VertexAccessor,
std::experimental::optional<EdgeAccessor>>
processed_;
// edge/vertex pairs we have yet to visit, for current and next depth
std::deque<std::pair<EdgeAccessor, VertexAccessor>> to_visit_current_;
std::deque<std::pair<EdgeAccessor, VertexAccessor>> to_visit_next_;
};
// info on what's getting expanded
const Symbol node_symbol_;
const Symbol edge_list_symbol_;
const EdgeAtom::Direction direction_;
const std::vector<GraphDbTypes::EdgeType> edge_types_;
Expression *max_depth_;
// symbols for a single node and edge that are currently getting expanded
const std::experimental::optional<Symbol> inner_node_symbol_;
const std::experimental::optional<Symbol> inner_edge_symbol_;
const Symbol inner_edge_symbol_;
const Symbol inner_node_symbol_;
// a filtering expression for skipping expansions during expansion
// can refer to inner node and edges
Expression *where_;
// the input op and the symbol under which the op's result
// can be found in the frame
const std::shared_ptr<LogicalOperator> input_;
const Symbol input_symbol_;
// if the node symbol already exists on the frame and this expansion
// should just match it
bool existing_node_;
// from which state the input node should get expanded
const GraphView graph_view_;
Expression *filter_;
};
/**

View File

@ -522,24 +522,13 @@ std::vector<Expansion> NormalizePatterns(
auto collect_expansion = [&](auto *prev_node, auto *edge,
auto *current_node) {
UsedSymbolsCollector collector(symbol_table);
if (edge->lower_bound_) {
edge->lower_bound_->Accept(collector);
}
if (edge->upper_bound_) {
edge->upper_bound_->Accept(collector);
}
if (auto *bf_atom = dynamic_cast<BreadthFirstAtom *>(edge)) {
// Get used symbols inside bfs filter expression and max depth.
if (bf_atom->filter_expression_)
bf_atom->filter_expression_->Accept(collector);
if (bf_atom->upper_bound_) bf_atom->upper_bound_->Accept(collector);
// Remove symbols which are bound by the bfs itself.
if (bf_atom->traversed_edge_identifier_) {
collector.symbols_.erase(
symbol_table.at(*bf_atom->traversed_edge_identifier_));
collector.symbols_.erase(
symbol_table.at(*bf_atom->next_node_identifier_));
}
// Remove symbols which are bound by variable expansions.
if (edge->IsVariable()) {
if (edge->lower_bound_) edge->lower_bound_->Accept(collector);
if (edge->upper_bound_) edge->upper_bound_->Accept(collector);
collector.symbols_.erase(symbol_table.at(*edge->inner_edge_));
collector.symbols_.erase(symbol_table.at(*edge->inner_node_));
if (edge->filter_expression_) edge->filter_expression_->Accept(collector);
}
expansions.emplace_back(Expansion{prev_node, edge, edge->direction_, false,
collector.symbols_, current_node});
@ -610,31 +599,6 @@ void AddMatching(const Match &match, SymbolTable &symbol_table,
matching);
}
// Iterates over `all_filters` joining them in one expression via
// `AndOperator`. Filters which use unbound symbols are skipped, as well
// as those that fail the `predicate` function. The function takes a single
// argument, `FilterInfo`. All the joined filters are removed from
// `all_filters`.
template <class TPredicate>
Expression *ExtractFilters(const std::unordered_set<Symbol> &bound_symbols,
std::vector<Filters::FilterInfo> &all_filters,
AstTreeStorage &storage,
const TPredicate &predicate) {
Expression *filter_expr = nullptr;
for (auto filters_it = all_filters.begin();
filters_it != all_filters.end();) {
if (HasBoundFilterSymbols(bound_symbols, *filters_it) &&
predicate(*filters_it)) {
filter_expr = impl::BoolJoin<AndOperator>(storage, filter_expr,
filters_it->expression);
filters_it = all_filters.erase(filters_it);
} else {
filters_it++;
}
}
return filter_expr;
}
auto SplitExpressionOnAnd(Expression *expression) {
std::vector<Expression *> expressions;
std::stack<Expression *> pending_expressions;
@ -664,24 +628,28 @@ bool BindSymbol(std::unordered_set<Symbol> &bound_symbols,
return insertion.second;
}
Expression *ExtractMultiExpandFilter(
const std::unordered_set<Symbol> &bound_symbols,
const Symbol &expands_to_node,
std::vector<Filters::FilterInfo> &all_filters, AstTreeStorage &storage) {
return ExtractFilters(bound_symbols, all_filters, storage,
[&](const auto &filter) {
return filter.is_for_multi_expand &&
filter.used_symbols.find(expands_to_node) ==
filter.used_symbols.end();
});
Expression *ExtractFilters(const std::unordered_set<Symbol> &bound_symbols,
std::vector<Filters::FilterInfo> &all_filters,
AstTreeStorage &storage) {
Expression *filter_expr = nullptr;
for (auto filters_it = all_filters.begin();
filters_it != all_filters.end();) {
if (HasBoundFilterSymbols(bound_symbols, *filters_it)) {
filter_expr = impl::BoolJoin<AndOperator>(storage, filter_expr,
filters_it->expression);
filters_it = all_filters.erase(filters_it);
} else {
filters_it++;
}
}
return filter_expr;
}
LogicalOperator *GenFilters(LogicalOperator *last_op,
const std::unordered_set<Symbol> &bound_symbols,
std::vector<Filters::FilterInfo> &all_filters,
AstTreeStorage &storage) {
auto *filter_expr = ExtractFilters(bound_symbols, all_filters, storage,
[](const auto &) { return true; });
auto *filter_expr = ExtractFilters(bound_symbols, all_filters, storage);
if (filter_expr) {
last_op =
new Filter(std::shared_ptr<LogicalOperator>(last_op), filter_expr);
@ -927,40 +895,63 @@ void Filters::AnalyzeFilter(Expression *expr, const SymbolTable &symbol_table) {
void Filters::CollectPatternFilters(Pattern &pattern, SymbolTable &symbol_table,
AstTreeStorage &storage) {
UsedSymbolsCollector collector(symbol_table);
auto add_properties_filter = [&](auto *atom, bool is_variable_path = false) {
debug_assert(
dynamic_cast<BreadthFirstAtom *>(atom) && atom->properties_.empty() ||
!dynamic_cast<BreadthFirstAtom *>(atom),
"Property filters are not supported in BFS");
auto add_properties_variable = [&](EdgeAtom *atom) {
const auto &symbol = symbol_table.at(*atom->identifier_);
for (auto &prop_pair : atom->properties_) {
// We need to store two property-lookup filters in all_filters. One is
// used for inlining property filters into variable expansion, and
// utilizes the inner_edge symbol. The other is used for post-expansion
// filtering and does not use the inner_edge symbol, but the edge symbol
// (a list of edges).
{
collector.symbols_.clear();
prop_pair.second->Accept(collector);
collector.symbols_.emplace(symbol_table.at(*atom->inner_node_));
collector.symbols_.emplace(symbol_table.at(*atom->inner_edge_));
// First handle the inline property filter.
auto *property_lookup =
storage.Create<PropertyLookup>(atom->inner_edge_, prop_pair.first);
auto *prop_equal =
storage.Create<EqualOperator>(property_lookup, prop_pair.second);
all_filters_.emplace_back(FilterInfo{prop_equal, collector.symbols_});
}
{
collector.symbols_.clear();
prop_pair.second->Accept(collector);
collector.symbols_.insert(symbol); // PropertyLookup uses the symbol.
// Now handle the post-expansion filter.
// Create a new identifier and a symbol which will be filled in All.
auto *identifier = atom->identifier_->Clone(storage);
symbol_table[*identifier] =
symbol_table.CreateSymbol(identifier->name_, false);
// Create an equality expression and store it in all_filters_.
auto *property_lookup =
storage.Create<PropertyLookup>(identifier, prop_pair.first);
auto *prop_equal =
storage.Create<EqualOperator>(property_lookup, prop_pair.second);
all_filters_.emplace_back(
FilterInfo{storage.Create<All>(identifier, atom->identifier_,
storage.Create<Where>(prop_equal)),
collector.symbols_});
}
}
};
auto add_properties = [&](auto *atom) {
const auto &symbol = symbol_table.at(*atom->identifier_);
for (auto &prop_pair : atom->properties_) {
collector.symbols_.clear();
prop_pair.second->Accept(collector);
auto *identifier = atom->identifier_;
if (is_variable_path) {
// Create a new identifier and a symbol which will be filled in All.
identifier = identifier->Clone(storage);
symbol_table[*identifier] =
symbol_table.CreateSymbol(identifier->name_, false);
} else {
// Store a PropertyFilter on the value of the property.
property_filters_[symbol][prop_pair.first.second].emplace_back(
PropertyFilter{collector.symbols_, prop_pair.second});
}
// Store a PropertyFilter on the value of the property.
property_filters_[symbol][prop_pair.first.second].emplace_back(
PropertyFilter{collector.symbols_, prop_pair.second});
// Create an equality expression and store it in all_filters_.
auto *property_lookup =
storage.Create<PropertyLookup>(identifier, prop_pair.first);
storage.Create<PropertyLookup>(atom->identifier_, prop_pair.first);
auto *prop_equal =
storage.Create<EqualOperator>(property_lookup, prop_pair.second);
collector.symbols_.insert(symbol); // PropertyLookup uses the symbol.
if (is_variable_path) {
all_filters_.emplace_back(
FilterInfo{storage.Create<All>(identifier, atom->identifier_,
storage.Create<Where>(prop_equal)),
collector.symbols_, true});
} else {
all_filters_.emplace_back(FilterInfo{prop_equal, collector.symbols_});
}
all_filters_.emplace_back(FilterInfo{prop_equal, collector.symbols_});
}
};
auto add_node_filter = [&](NodeAtom *node) {
@ -974,10 +965,13 @@ void Filters::CollectPatternFilters(Pattern &pattern, SymbolTable &symbol_table,
storage.Create<LabelsTest>(node->identifier_, node->labels_),
std::unordered_set<Symbol>{node_symbol}});
}
add_properties_filter(node);
add_properties(node);
};
auto add_expand_filter = [&](NodeAtom *, EdgeAtom *edge, NodeAtom *node) {
add_properties_filter(edge, edge->has_range_);
if (edge->IsVariable())
add_properties_variable(edge);
else
add_properties(edge);
add_node_filter(node);
};
ForEachPattern(pattern, add_node_filter, add_expand_filter);

View File

@ -52,10 +52,6 @@ class Filters {
Expression *expression;
/// Set of used symbols by the filter @c expression.
std::unordered_set<Symbol> used_symbols;
/// True if the filter is to be applied on multiple expanding edges.
/// This is used to inline filtering in an @c ExpandVariable and
/// @c ExpandBreadthFirst operators.
bool is_for_multi_expand = false;
};
/// List of FilterInfo objects corresponding to all filter expressions that
@ -220,15 +216,13 @@ namespace impl {
bool BindSymbol(std::unordered_set<Symbol> &bound_symbols,
const Symbol &symbol);
// Looks for filter expressions, which can be inlined in an ExpandVariable
// operator. Such expressions are merged into one (via `and`) and removed from
// `all_filters`. If the expression uses `expands_to_node`, it is skipped. In
// such a case, we cannot cut variable expand short, since filtering may be
// satisfied by a node deeper in the path.
Expression *ExtractMultiExpandFilter(
const std::unordered_set<Symbol> &bound_symbols,
const Symbol &expands_to_node,
std::vector<Filters::FilterInfo> &all_filters, AstTreeStorage &storage);
// Iterates over `all_filters` joining them in one expression via
// `AndOperator`. Filters which use unbound symbols are skipped
// The function takes a single argument, `FilterInfo`. All the joined filters
// are removed from `all_filters`.
Expression *ExtractFilters(const std::unordered_set<Symbol> &bound_symbols,
std::vector<Filters::FilterInfo> &all_filters,
AstTreeStorage &storage);
LogicalOperator *GenFilters(LogicalOperator *last_op,
const std::unordered_set<Symbol> &bound_symbols,
@ -517,53 +511,52 @@ class RuleBasedPlanner {
}
// We have an edge, so generate Expand.
if (expansion.edge) {
auto *edge = expansion.edge;
// If the expand symbols were already bound, then we need to indicate
// that they exist. The Expand will then check whether the pattern holds
// instead of writing the expansion to symbols.
const auto &node_symbol =
symbol_table.at(*expansion.node2->identifier_);
auto existing_node = false;
if (!impl::BindSymbol(bound_symbols, node_symbol)) {
existing_node = true;
} else {
match_context.new_symbols.emplace_back(node_symbol);
}
const auto &edge_symbol = symbol_table.at(*expansion.edge->identifier_);
auto existing_edge = false;
if (!impl::BindSymbol(bound_symbols, edge_symbol)) {
existing_edge = true;
} else {
match_context.new_symbols.emplace_back(edge_symbol);
}
if (auto *bf_atom = dynamic_cast<BreadthFirstAtom *>(expansion.edge)) {
std::experimental::optional<Symbol> traversed_edge_symbol;
if (bf_atom->traversed_edge_identifier_)
traversed_edge_symbol =
symbol_table.at(*bf_atom->traversed_edge_identifier_);
std::experimental::optional<Symbol> next_node_symbol;
if (bf_atom->next_node_identifier_)
next_node_symbol = symbol_table.at(*bf_atom->next_node_identifier_);
// Inline BFS edge filtering together with its filter expression.
auto existing_node = utils::Contains(bound_symbols, node_symbol);
const auto &edge_symbol = symbol_table.at(*edge->identifier_);
debug_assert(!utils::Contains(bound_symbols, edge_symbol),
"Existing edges are not supported");
if (edge->IsVariable()) {
Symbol inner_edge_symbol = symbol_table.at(*edge->inner_edge_);
Symbol inner_node_symbol = symbol_table.at(*edge->inner_node_);
{
// Bind the inner edge and node symbols so they're available for
// inline filtering in ExpandVariable.
bool inner_edge_bound =
impl::BindSymbol(bound_symbols, inner_edge_symbol);
bool inner_node_bound =
impl::BindSymbol(bound_symbols, inner_node_symbol);
debug_assert(inner_edge_bound && inner_node_bound,
"An inner edge and node can't be bound from before");
}
auto *filter_expr = impl::BoolJoin<AndOperator>(
storage, impl::ExtractMultiExpandFilter(
bound_symbols, node_symbol, all_filters, storage),
bf_atom->filter_expression_);
last_op = new ExpandBreadthFirst(
node_symbol, edge_symbol, expansion.direction,
expansion.edge->edge_types_, bf_atom->upper_bound_,
next_node_symbol, traversed_edge_symbol, filter_expr,
std::shared_ptr<LogicalOperator>(last_op), node1_symbol,
existing_node, match_context.graph_view);
} else if (expansion.edge->has_range_) {
auto *filter_expr = impl::ExtractMultiExpandFilter(
bound_symbols, node_symbol, all_filters, storage);
storage,
impl::ExtractFilters(bound_symbols, all_filters, storage),
edge->filter_expression_);
// At this point it's possible we have leftover filters for inline
// filtering (they use the inner symbols. If they were not collected,
// we have to remove them manually because no other filter-extraction
// will ever bind them again.
all_filters.erase(
std::remove_if(all_filters.begin(), all_filters.end(),
[ e = inner_edge_symbol, n = inner_node_symbol ](
Filters::FilterInfo & fi) {
return utils::Contains(fi.used_symbols, e) ||
utils::Contains(fi.used_symbols, n);
}),
all_filters.end());
last_op = new ExpandVariable(
node_symbol, edge_symbol, expansion.direction,
expansion.edge->edge_types_, expansion.is_flipped,
expansion.edge->lower_bound_, expansion.edge->upper_bound_,
std::shared_ptr<LogicalOperator>(last_op), node1_symbol,
existing_node, existing_edge, match_context.graph_view,
filter_expr);
node_symbol, edge_symbol, edge->type_, expansion.direction,
edge->edge_types_, expansion.is_flipped, edge->lower_bound_,
edge->upper_bound_, std::shared_ptr<LogicalOperator>(last_op),
node1_symbol, existing_node, inner_edge_symbol, inner_node_symbol,
filter_expr, match_context.graph_view);
} else {
if (!existing_node) {
// Try to get better behaviour by creating an indexed scan and then
@ -581,32 +574,37 @@ class RuleBasedPlanner {
existing_node = true;
}
}
last_op = new Expand(node_symbol, edge_symbol, expansion.direction,
expansion.edge->edge_types_,
std::shared_ptr<LogicalOperator>(last_op),
node1_symbol, existing_node, existing_edge,
match_context.graph_view);
last_op = new Expand(
node_symbol, edge_symbol, expansion.direction, edge->edge_types_,
std::shared_ptr<LogicalOperator>(last_op), node1_symbol,
existing_node, match_context.graph_view);
}
if (!existing_edge) {
// Ensure Cyphermorphism (different edge symbols always map to
// different edges).
for (const auto &edge_symbols : matching.edge_symbols) {
if (edge_symbols.find(edge_symbol) == edge_symbols.end()) {
// Bind the expanded edge and node.
impl::BindSymbol(bound_symbols, edge_symbol);
match_context.new_symbols.emplace_back(edge_symbol);
if (impl::BindSymbol(bound_symbols, node_symbol)) {
match_context.new_symbols.emplace_back(node_symbol);
}
// Ensure Cyphermorphism (different edge symbols always map to
// different edges).
for (const auto &edge_symbols : matching.edge_symbols) {
if (edge_symbols.find(edge_symbol) == edge_symbols.end()) {
continue;
}
std::vector<Symbol> other_symbols;
for (const auto &symbol : edge_symbols) {
if (symbol == edge_symbol ||
bound_symbols.find(symbol) == bound_symbols.end()) {
continue;
}
std::vector<Symbol> other_symbols;
for (const auto &symbol : edge_symbols) {
if (symbol == edge_symbol ||
bound_symbols.find(symbol) == bound_symbols.end()) {
continue;
}
other_symbols.push_back(symbol);
}
if (!other_symbols.empty()) {
last_op = new ExpandUniquenessFilter<EdgeAccessor>(
std::shared_ptr<LogicalOperator>(last_op), edge_symbol,
other_symbols);
}
other_symbols.push_back(symbol);
}
if (!other_symbols.empty()) {
last_op = new ExpandUniquenessFilter<EdgeAccessor>(
std::shared_ptr<LogicalOperator>(last_op), edge_symbol,
other_symbols);
}
}
last_op =

View File

@ -93,7 +93,7 @@ void AddNextExpansions(
expansion.node2 &&
symbol_table.at(*expansion.node2->identifier_) == node_symbol,
"Expected node_symbol to be bound in node2");
if (!dynamic_cast<BreadthFirstAtom *>(expansion.edge)) {
if (expansion.edge->type_ != EdgeAtom::Type::BREADTH_FIRST) {
// BFS must *not* be flipped. Doing that changes the BFS results.
std::swap(expansion.node1, expansion.node2);
expansion.is_flipped = true;

View File

@ -20,6 +20,7 @@ static void AddChainedMatches(int num_matches, query::AstTreeStorage &storage) {
storage.Create<query::Identifier>(node1_name)));
pattern->atoms_.emplace_back(storage.Create<query::EdgeAtom>(
storage.Create<query::Identifier>("edge" + std::to_string(i)),
query::EdgeAtom::Type::SINGLE,
query::EdgeAtom::Direction::BOTH));
pattern->atoms_.emplace_back(storage.Create<query::NodeAtom>(
storage.Create<query::Identifier>("node" + std::to_string(i))));

View File

@ -428,7 +428,6 @@ class PlanPrinter : public query::plan::HierarchicalLogicalOperatorVisitor {
return true;
}
PRE_VISIT(ExpandBreadthFirst);
PRE_VISIT(ConstructNamedPath);
PRE_VISIT(Filter);
PRE_VISIT(SetProperty);

View File

@ -73,3 +73,18 @@ Feature: Bfs
Then the result should be:
| s | r0 | r1 |
| 2 | 0 | 1 |
Scenario: Test match BFS property filters
Given an empty graph
And having executed:
"""
CREATE ()-[:r0 {id: 0}]->()-[:r1 {id: 1}]->()-[:r2 {id: 2}]->()-[:r3 {id: 3}]->()
"""
When executing query:
"""
MATCH ()-[r *bfs..10 {id: 1}]->(m)
RETURN size(r) AS s, (r[0]).id AS r0
"""
Then the result should be:
| s | r0 |
| 1 | 1 |

View File

@ -900,7 +900,7 @@ TYPED_TEST(CypherMainVisitorTest, RelationshipPatternUnbounded) {
EdgeAtom *edge = nullptr;
AssertMatchSingleEdgeAtom(match, edge);
EXPECT_EQ(edge->direction_, EdgeAtom::Direction::OUT);
EXPECT_TRUE(edge->has_range_);
EXPECT_EQ(edge->type_, EdgeAtom::Type::DEPTH_FIRST);
EXPECT_EQ(edge->lower_bound_, nullptr);
EXPECT_EQ(edge->upper_bound_, nullptr);
}
@ -912,7 +912,7 @@ TYPED_TEST(CypherMainVisitorTest, RelationshipPatternLowerBounded) {
EdgeAtom *edge = nullptr;
AssertMatchSingleEdgeAtom(match, edge);
EXPECT_EQ(edge->direction_, EdgeAtom::Direction::OUT);
EXPECT_TRUE(edge->has_range_);
EXPECT_EQ(edge->type_, EdgeAtom::Type::DEPTH_FIRST);
CheckLiteral(ast_generator.context_, edge->lower_bound_, 42);
EXPECT_EQ(edge->upper_bound_, nullptr);
}
@ -924,7 +924,7 @@ TYPED_TEST(CypherMainVisitorTest, RelationshipPatternUpperBounded) {
EdgeAtom *edge = nullptr;
AssertMatchSingleEdgeAtom(match, edge);
EXPECT_EQ(edge->direction_, EdgeAtom::Direction::OUT);
EXPECT_TRUE(edge->has_range_);
EXPECT_EQ(edge->type_, EdgeAtom::Type::DEPTH_FIRST);
EXPECT_EQ(edge->lower_bound_, nullptr);
CheckLiteral(ast_generator.context_, edge->upper_bound_, 42);
}
@ -936,7 +936,7 @@ TYPED_TEST(CypherMainVisitorTest, RelationshipPatternLowerUpperBounded) {
EdgeAtom *edge = nullptr;
AssertMatchSingleEdgeAtom(match, edge);
EXPECT_EQ(edge->direction_, EdgeAtom::Direction::OUT);
EXPECT_TRUE(edge->has_range_);
EXPECT_EQ(edge->type_, EdgeAtom::Type::DEPTH_FIRST);
CheckLiteral(ast_generator.context_, edge->lower_bound_, 24);
CheckLiteral(ast_generator.context_, edge->upper_bound_, 42);
}
@ -948,7 +948,7 @@ TYPED_TEST(CypherMainVisitorTest, RelationshipPatternFixedRange) {
EdgeAtom *edge = nullptr;
AssertMatchSingleEdgeAtom(match, edge);
EXPECT_EQ(edge->direction_, EdgeAtom::Direction::OUT);
EXPECT_TRUE(edge->has_range_);
EXPECT_EQ(edge->type_, EdgeAtom::Type::DEPTH_FIRST);
CheckLiteral(ast_generator.context_, edge->lower_bound_, 42);
CheckLiteral(ast_generator.context_, edge->upper_bound_, 42);
}
@ -961,7 +961,7 @@ TYPED_TEST(CypherMainVisitorTest, RelationshipPatternFloatingUpperBound) {
EdgeAtom *edge = nullptr;
AssertMatchSingleEdgeAtom(match, edge);
EXPECT_EQ(edge->direction_, EdgeAtom::Direction::OUT);
EXPECT_TRUE(edge->has_range_);
EXPECT_EQ(edge->type_, EdgeAtom::Type::DEPTH_FIRST);
CheckLiteral(ast_generator.context_, edge->lower_bound_, 1);
CheckLiteral(ast_generator.context_, edge->upper_bound_, 0.2);
}
@ -973,7 +973,7 @@ TYPED_TEST(CypherMainVisitorTest, RelationshipPatternUnboundedWithProperty) {
EdgeAtom *edge = nullptr;
AssertMatchSingleEdgeAtom(match, edge);
EXPECT_EQ(edge->direction_, EdgeAtom::Direction::OUT);
EXPECT_TRUE(edge->has_range_);
EXPECT_EQ(edge->type_, EdgeAtom::Type::DEPTH_FIRST);
EXPECT_EQ(edge->lower_bound_, nullptr);
EXPECT_EQ(edge->upper_bound_, nullptr);
CheckLiteral(ast_generator.context_,
@ -988,7 +988,7 @@ TYPED_TEST(CypherMainVisitorTest,
EdgeAtom *edge = nullptr;
AssertMatchSingleEdgeAtom(match, edge);
EXPECT_EQ(edge->direction_, EdgeAtom::Direction::OUT);
EXPECT_TRUE(edge->has_range_);
EXPECT_EQ(edge->type_, EdgeAtom::Type::DEPTH_FIRST);
EXPECT_EQ(edge->lower_bound_, nullptr);
EXPECT_EQ(edge->upper_bound_, nullptr);
CheckLiteral(ast_generator.context_,
@ -1005,13 +1005,14 @@ TYPED_TEST(CypherMainVisitorTest, RelationshipPatternUpperBoundedWithProperty) {
EdgeAtom *edge = nullptr;
AssertMatchSingleEdgeAtom(match, edge);
EXPECT_EQ(edge->direction_, EdgeAtom::Direction::OUT);
EXPECT_TRUE(edge->has_range_);
EXPECT_EQ(edge->type_, EdgeAtom::Type::DEPTH_FIRST);
EXPECT_EQ(edge->lower_bound_, nullptr);
CheckLiteral(ast_generator.context_, edge->upper_bound_, 2);
CheckLiteral(ast_generator.context_,
edge->properties_[ast_generator.PropPair("prop")], 42);
}
// TODO maybe uncomment
// // PatternPart with variable.
// TYPED_TEST(CypherMainVisitorTest, PatternPartVariable) {
// ParserTables parser("CREATE var=()--()");
@ -1385,17 +1386,17 @@ TYPED_TEST(CypherMainVisitorTest, MatchBfsReturn) {
ASSERT_TRUE(match);
ASSERT_EQ(match->patterns_.size(), 1U);
ASSERT_EQ(match->patterns_[0]->atoms_.size(), 3U);
auto *bfs = dynamic_cast<BreadthFirstAtom *>(match->patterns_[0]->atoms_[1]);
auto *bfs = dynamic_cast<EdgeAtom *>(match->patterns_[0]->atoms_[1]);
ASSERT_TRUE(bfs);
EXPECT_TRUE(bfs->has_range_);
EXPECT_TRUE(bfs->IsVariable());
EXPECT_EQ(bfs->direction_, EdgeAtom::Direction::OUT);
EXPECT_THAT(
bfs->edge_types_,
UnorderedElementsAre(ast_generator.db_accessor_->EdgeType("type1"),
ast_generator.db_accessor_->EdgeType("type2")));
EXPECT_EQ(bfs->identifier_->name_, "r");
EXPECT_EQ(bfs->traversed_edge_identifier_->name_, "e");
EXPECT_EQ(bfs->next_node_identifier_->name_, "n");
EXPECT_EQ(bfs->inner_edge_->name_, "e");
EXPECT_EQ(bfs->inner_node_->name_, "n");
CheckLiteral(ast_generator.context_, bfs->upper_bound_, 10);
auto *eq = dynamic_cast<EqualOperator *>(bfs->filter_expression_);
ASSERT_TRUE(eq);

View File

@ -139,24 +139,43 @@ auto GetPropertyLookup(
}
///
/// Create an EdgeAtom with given name, edge_type and direction.
/// Create an EdgeAtom with given name, direction and edge_type.
///
/// Name is used to create the Identifier which is assigned to the edge.
///
auto GetEdge(AstTreeStorage &storage, const std::string &name,
GraphDbTypes::EdgeType edge_type = nullptr,
EdgeAtom::Direction dir = EdgeAtom::Direction::BOTH) {
auto edge = storage.Create<EdgeAtom>(storage.Create<Identifier>(name), dir);
if (edge_type) edge->edge_types_.emplace_back(edge_type);
return edge;
EdgeAtom::Direction dir = EdgeAtom::Direction::BOTH,
const std::vector<GraphDbTypes::EdgeType> &edge_types = {}) {
return storage.Create<EdgeAtom>(storage.Create<Identifier>(name),
EdgeAtom::Type::SINGLE, dir, edge_types);
}
/// Generates a randomly chosen (uniformly) string from a population of 10 ** 20
std::string random_string() {
std::string str = "rand_str_";
for (int i = 0; i < 20; i++) str += std::to_string(rand() % 10);
return str;
}
///
/// Create an EdgeAtom with given name and direction.
/// Create a variable length expansion EdgeAtom with given name, direction and
/// edge_type.
///
auto GetEdge(AstTreeStorage &storage, const std::string &name,
EdgeAtom::Direction dir) {
return GetEdge(storage, name, nullptr, dir);
/// Name is used to create the Identifier which is assigned to the edge.
///
auto GetEdgeVariable(AstTreeStorage &storage, const std::string &name,
EdgeAtom::Direction dir = EdgeAtom::Direction::BOTH,
const std::vector<GraphDbTypes::EdgeType> &edge_types = {},
Identifier *inner_edge = nullptr,
Identifier *inner_node = nullptr) {
auto r_val =
storage.Create<EdgeAtom>(storage.Create<Identifier>(name),
EdgeAtom::Type::DEPTH_FIRST, dir, edge_types);
r_val->inner_edge_ =
inner_edge ? inner_edge : storage.Create<Identifier>(random_string());
r_val->inner_node_ =
inner_node ? inner_node : storage.Create<Identifier>(random_string());
return r_val;
}
///
@ -171,13 +190,6 @@ auto GetNode(AstTreeStorage &storage, const std::string &name,
return node;
}
/// Generates a randomly chosen (uniformly) string from a population of 10 ** 20
std::string random_string() {
std::string str = "rand_str_";
for (int i = 0; i < 20; i++) str += std::to_string(rand() % 10);
return str;
}
///
/// Create a Pattern with given atoms.
///
@ -460,6 +472,8 @@ auto GetMerge(AstTreeStorage &storage, Pattern *pattern, OnMatch on_match,
///
#define NODE(...) query::test_common::GetNode(storage, __VA_ARGS__)
#define EDGE(...) query::test_common::GetEdge(storage, __VA_ARGS__)
#define EDGE_VARIABLE(...) \
query::test_common::GetEdgeVariable(storage, __VA_ARGS__)
#define PATTERN(...) query::test_common::GetPattern(storage, {__VA_ARGS__})
#define NAMED_PATTERN(name, ...) \
query::test_common::GetPattern(storage, name, {__VA_ARGS__})

View File

@ -165,25 +165,19 @@ TEST_F(QueryCostEstimator, ScanAllByLabelPropertyRangeConstExpr) {
TEST_F(QueryCostEstimator, Expand) {
MakeOp<Expand>(NextSymbol(), NextSymbol(), EdgeAtom::Direction::IN,
std::vector<GraphDbTypes::EdgeType>{}, last_op_, NextSymbol(),
false, false);
false);
EXPECT_COST(CardParam::kExpand * CostParam::kExpand);
}
TEST_F(QueryCostEstimator, ExpandVariable) {
MakeOp<ExpandVariable>(NextSymbol(), NextSymbol(), EdgeAtom::Direction::IN,
MakeOp<ExpandVariable>(NextSymbol(), NextSymbol(),
EdgeAtom::Type::DEPTH_FIRST, EdgeAtom::Direction::IN,
std::vector<GraphDbTypes::EdgeType>{}, false, nullptr,
nullptr, last_op_, NextSymbol(), false, false);
nullptr, last_op_, NextSymbol(), false, NextSymbol(),
NextSymbol(), nullptr);
EXPECT_COST(CardParam::kExpandVariable * CostParam::kExpandVariable);
}
TEST_F(QueryCostEstimator, ExpandBreadthFirst) {
MakeOp<ExpandBreadthFirst>(
NextSymbol(), NextSymbol(), EdgeAtom::Direction::IN,
std::vector<GraphDbTypes::EdgeType>{}, Literal(3), NextSymbol(),
NextSymbol(), Literal(true), last_op_, NextSymbol(), false);
EXPECT_COST(CardParam::kExpandBreadthFirst * CostParam::kExpandBreadthFirst);
}
// Helper for testing an operations cost and cardinality.
// Only for operations that first increment cost, then modify cardinality.
// Intentially a macro (instead of function) for better test feedback.

View File

@ -49,7 +49,7 @@ TEST(QueryPlan, Accumulate) {
auto n = MakeScanAll(storage, symbol_table, "n");
auto r_m = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r",
EdgeAtom::Direction::BOTH, {}, false, "m", false);
EdgeAtom::Direction::BOTH, {}, "m", false);
auto one = LITERAL(1);
auto n_p = PROPERTY_LOOKUP("n", prop);

View File

@ -179,8 +179,7 @@ ExpandTuple MakeExpand(AstTreeStorage &storage, SymbolTable &symbol_table,
Symbol input_symbol, const std::string &edge_identifier,
EdgeAtom::Direction direction,
const std::vector<GraphDbTypes::EdgeType> &edge_types,
bool existing_edge, const std::string &node_identifier,
bool existing_node,
const std::string &node_identifier, bool existing_node,
GraphView graph_view = GraphView::AS_IS) {
auto edge = EDGE(edge_identifier, direction);
auto edge_sym = symbol_table.CreateSymbol(edge_identifier, true);
@ -190,9 +189,9 @@ ExpandTuple MakeExpand(AstTreeStorage &storage, SymbolTable &symbol_table,
auto node_sym = symbol_table.CreateSymbol(node_identifier, true);
symbol_table[*node->identifier_] = node_sym;
auto op = std::make_shared<Expand>(node_sym, edge_sym, direction, edge_types,
input, input_symbol, existing_node,
existing_edge, graph_view);
auto op =
std::make_shared<Expand>(node_sym, edge_sym, direction, edge_types, input,
input_symbol, existing_node, graph_view);
return ExpandTuple{edge, edge_sym, node, node_sym, op};
}

View File

@ -302,7 +302,7 @@ TEST(QueryPlan, Delete) {
{
auto n = MakeScanAll(storage, symbol_table, "n");
auto r_m = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r",
EdgeAtom::Direction::OUT, {}, false, "m", false);
EdgeAtom::Direction::OUT, {}, "m", false);
auto r_get = storage.Create<Identifier>("r");
symbol_table[*r_get] = r_m.edge_sym_;
auto delete_op = std::make_shared<plan::Delete>(
@ -355,7 +355,7 @@ TEST(QueryPlan, DeleteTwiceDeleteBlockingEdge) {
auto n = MakeScanAll(storage, symbol_table, "n");
auto r_m = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r",
EdgeAtom::Direction::BOTH, {}, false, "m", false);
EdgeAtom::Direction::BOTH, {}, "m", false);
// getter expressions for deletion
auto n_get = storage.Create<Identifier>("n");
@ -478,7 +478,7 @@ TEST(QueryPlan, SetProperty) {
// scan (n)-[r]->(m)
auto n = MakeScanAll(storage, symbol_table, "n");
auto r_m = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r",
EdgeAtom::Direction::OUT, {}, false, "m", false);
EdgeAtom::Direction::OUT, {}, "m", false);
// set prop1 to 42 on n and r
auto prop1 = dba->Property("prop1");
@ -529,7 +529,7 @@ TEST(QueryPlan, SetProperties) {
// scan (n)-[r]->(m)
auto n = MakeScanAll(storage, symbol_table, "n");
auto r_m = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r",
EdgeAtom::Direction::OUT, {}, false, "m", false);
EdgeAtom::Direction::OUT, {}, "m", false);
auto op = update ? plan::SetProperties::Op::UPDATE
: plan::SetProperties::Op::REPLACE;
@ -632,7 +632,7 @@ TEST(QueryPlan, RemoveProperty) {
// scan (n)-[r]->(m)
auto n = MakeScanAll(storage, symbol_table, "n");
auto r_m = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r",
EdgeAtom::Direction::OUT, {}, false, "m", false);
EdgeAtom::Direction::OUT, {}, "m", false);
auto n_p = PROPERTY_LOOKUP("n", prop1);
symbol_table[*n_p->expression_] = n.sym_;
@ -708,9 +708,8 @@ TEST(QueryPlan, NodeFilterSet) {
// MATCH (n {prop: 42}) -[r]- (m)
auto scan_all = MakeScanAll(storage, symbol_table, "n");
scan_all.node_->properties_[prop] = LITERAL(42);
auto expand =
MakeExpand(storage, symbol_table, scan_all.op_, scan_all.sym_, "r",
EdgeAtom::Direction::BOTH, {}, false, "m", false);
auto expand = MakeExpand(storage, symbol_table, scan_all.op_, scan_all.sym_,
"r", EdgeAtom::Direction::BOTH, {}, "m", false);
auto *filter_expr =
EQ(storage.Create<PropertyLookup>(scan_all.node_->identifier_, prop),
LITERAL(42));
@ -748,9 +747,8 @@ TEST(QueryPlan, FilterRemove) {
// MATCH (n) -[r]- (m) WHERE n.prop < 43
auto scan_all = MakeScanAll(storage, symbol_table, "n");
scan_all.node_->properties_[prop] = LITERAL(42);
auto expand =
MakeExpand(storage, symbol_table, scan_all.op_, scan_all.sym_, "r",
EdgeAtom::Direction::BOTH, {}, false, "m", false);
auto expand = MakeExpand(storage, symbol_table, scan_all.op_, scan_all.sym_,
"r", EdgeAtom::Direction::BOTH, {}, "m", false);
auto filter_prop = PROPERTY_LOOKUP("n", prop);
symbol_table[*filter_prop->expression_] = scan_all.sym_;
auto filter =
@ -813,7 +811,7 @@ TEST(QueryPlan, Merge) {
// merge_match branch
auto r_m = MakeExpand(storage, symbol_table, std::make_shared<Once>(), n.sym_,
"r", EdgeAtom::Direction::BOTH, {}, false, "m", false);
"r", EdgeAtom::Direction::BOTH, {}, "m", false);
auto m_p = PROPERTY_LOOKUP("m", prop);
symbol_table[*m_p->expression_] = r_m.node_sym_;
auto m_set = std::make_shared<plan::SetProperty>(r_m.op_, m_p, LITERAL(1));

View File

@ -278,7 +278,7 @@ TEST_F(ExpandFixture, Expand) {
auto test_expand = [&](EdgeAtom::Direction direction, GraphView graph_view) {
auto n = MakeScanAll(storage, symbol_table, "n");
auto r_m = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r", direction,
{}, false, "m", false, graph_view);
{}, "m", false, graph_view);
// make a named expression and a produce
auto output = NEXPR("m", IDENT("m"));
@ -315,7 +315,7 @@ TEST_F(ExpandFixture, Expand) {
TEST_F(ExpandFixture, ExpandPath) {
auto n = MakeScanAll(storage, symbol_table, "n");
auto r_m = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r",
EdgeAtom::Direction::OUT, {}, false, "m", false);
EdgeAtom::Direction::OUT, {}, "m", false);
Symbol path_sym = symbol_table.CreateSymbol("path", true);
auto path = std::make_shared<ConstructNamedPath>(
r_m.op_, path_sym,
@ -412,8 +412,8 @@ class QueryPlanExpandVariable : public testing::Test {
const std::vector<GraphDbTypes::EdgeType> &edge_types,
std::experimental::optional<size_t> lower,
std::experimental::optional<size_t> upper, Symbol edge_sym,
bool existing_edge, const std::string &node_to,
GraphView graph_view = GraphView::AS_IS, bool is_reverse = false) {
const std::string &node_to, GraphView graph_view = GraphView::AS_IS,
bool is_reverse = false) {
auto n_from = MakeScanAll(storage, symbol_table, node_from, input_op);
auto filter_op = std::make_shared<Filter>(
n_from.op_, storage.Create<query::LabelsTest>(
@ -430,13 +430,14 @@ class QueryPlanExpandVariable : public testing::Test {
return bound ? LITERAL(static_cast<int64_t>(bound.value())) : nullptr;
};
return std::make_shared<ExpandVariable>(
n_to_sym, edge_sym, direction, edge_types, is_reverse, convert(lower),
convert(upper), filter_op, n_from.sym_, false, existing_edge,
graph_view);
n_to_sym, edge_sym, EdgeAtom::Type::DEPTH_FIRST, direction,
edge_types, is_reverse, convert(lower), convert(upper), filter_op,
n_from.sym_, false, symbol_table.CreateSymbol("inner_edge", false),
symbol_table.CreateSymbol("inner_node", false), nullptr, graph_view);
} else
return std::make_shared<Expand>(n_to_sym, edge_sym, direction, edge_types,
filter_op, n_from.sym_, false,
existing_edge, graph_view);
graph_view);
}
/* Creates an edge (in the frame and symbol table). Returns the symbol. */
@ -495,8 +496,7 @@ TEST_F(QueryPlanExpandVariable, OneVariableExpansion) {
auto e = Edge("r", direction);
return GetEdgeListSizes(
AddMatch<ExpandVariable>(nullptr, "n", layer, direction, {}, lower,
upper, e, false, "m", GraphView::AS_IS,
reverse),
upper, e, "m", GraphView::AS_IS, reverse),
e);
};
@ -559,19 +559,18 @@ TEST_F(QueryPlanExpandVariable, EdgeUniquenessSingleAndVariableExpansion) {
if (single_expansion_before) {
symbols.push_back(Edge("r0", direction));
last_op = AddMatch<Expand>(last_op, "n0", layer, direction, {}, lower,
upper, symbols.back(), false, "m0");
upper, symbols.back(), "m0");
}
auto var_length_sym = Edge("r1", direction);
symbols.push_back(var_length_sym);
last_op =
AddMatch<ExpandVariable>(last_op, "n1", layer, direction, {}, lower,
upper, var_length_sym, false, "m1");
last_op = AddMatch<ExpandVariable>(last_op, "n1", layer, direction, {},
lower, upper, var_length_sym, "m1");
if (!single_expansion_before) {
symbols.push_back(Edge("r2", direction));
last_op = AddMatch<Expand>(last_op, "n2", layer, direction, {}, lower,
upper, symbols.back(), false, "m2");
upper, symbols.back(), "m2");
}
if (add_uniqueness_check) {
@ -601,10 +600,10 @@ TEST_F(QueryPlanExpandVariable, EdgeUniquenessTwoVariableExpansions) {
bool add_uniqueness_check) {
auto e1 = Edge("r1", direction);
auto first = AddMatch<ExpandVariable>(nullptr, "n1", layer, direction, {},
lower, upper, e1, false, "m1");
lower, upper, e1, "m1");
auto e2 = Edge("r2", direction);
auto last_op = AddMatch<ExpandVariable>(first, "n2", layer, direction, {},
lower, upper, e2, false, "m2");
lower, upper, e2, "m2");
if (add_uniqueness_check) {
last_op = std::make_shared<ExpandUniquenessFilter<EdgeAccessor>>(
last_op, e2, std::vector<Symbol>{e1});
@ -619,42 +618,6 @@ TEST_F(QueryPlanExpandVariable, EdgeUniquenessTwoVariableExpansions) {
(map_int{{2, 5 * 8}}));
}
TEST_F(QueryPlanExpandVariable, ExistingEdges) {
auto test_expand = [&](int layer, EdgeAtom::Direction direction,
std::experimental::optional<size_t> lower,
std::experimental::optional<size_t> upper,
bool same_edge_symbol) {
auto e1 = Edge("r1", direction);
auto first = AddMatch<ExpandVariable>(nullptr, "n1", layer, direction, {},
lower, upper, e1, false, "m1");
auto e2 = same_edge_symbol ? e1 : Edge("r2", direction);
auto second =
AddMatch<ExpandVariable>(first, "n2", layer, direction, {}, lower,
upper, e2, same_edge_symbol, "m2");
return GetEdgeListSizes(second, e2);
};
EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 1, 1, false),
(map_int{{1, 4 * 4}}));
EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 1, 1, true),
(map_int{{1, 4}}));
EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 0, 1, false),
(map_int{{0, 2 * 6}, {1, 4 * 6}}));
EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 0, 1, true),
(map_int{{0, 4}, {1, 4}}));
EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 2, 3, false),
(map_int{{2, 8 * 8}}));
EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 2, 3, true),
(map_int{{2, 8}}));
EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 1, 2, false),
(map_int{{1, 4 * 16}, {2, 12 * 16}}));
EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 1, 2, true),
(map_int{{1, 4}, {2, 12}}));
}
TEST_F(QueryPlanExpandVariable, GraphState) {
auto test_expand = [&](
GraphView graph_view,
@ -662,7 +625,7 @@ TEST_F(QueryPlanExpandVariable, GraphState) {
auto e = Edge("r", EdgeAtom::Direction::OUT);
return GetEdgeListSizes(
AddMatch<ExpandVariable>(nullptr, "n", 0, EdgeAtom::Direction::OUT,
edge_types, 2, 2, e, false, "m", graph_view),
edge_types, 2, 2, e, "m", graph_view),
e);
};
@ -692,7 +655,7 @@ TEST_F(QueryPlanExpandVariable, GraphState) {
TEST_F(QueryPlanExpandVariable, NamedPath) {
auto e = Edge("r", EdgeAtom::Direction::OUT);
auto expand = AddMatch<ExpandVariable>(
nullptr, "n", 0, EdgeAtom::Direction::OUT, {}, 2, 2, e, false, "m");
nullptr, "n", 0, EdgeAtom::Direction::OUT, {}, 2, 2, e, "m");
auto find_symbol = [this](const std::string &name) {
for (const auto &pos_sym : symbol_table.table())
if (pos_sym.second.name() == name) return pos_sym.second;
@ -802,11 +765,11 @@ class QueryPlanExpandBreadthFirst : public testing::Test {
? existing_node_input->sym_
: symbol_table.CreateSymbol("node", true);
auto edge_list_sym = symbol_table.CreateSymbol("edgelist_", true);
last_op = std::make_shared<ExpandBreadthFirst>(
node_sym, edge_list_sym, direction,
std::vector<GraphDbTypes::EdgeType>{}, LITERAL(max_depth), inner_node,
inner_edge, where, last_op, n.sym_, existing_node_input != nullptr,
graph_view);
last_op = std::make_shared<ExpandVariable>(
node_sym, edge_list_sym, EdgeAtom::Type::BREADTH_FIRST, direction,
std::vector<GraphDbTypes::EdgeType>{}, false, nullptr,
LITERAL(max_depth), last_op, n.sym_, existing_node_input != nullptr,
inner_edge, inner_node, where, graph_view);
Frame frame(symbol_table.max_position());
auto cursor = last_op->MakeCursor(*dba);
@ -975,7 +938,7 @@ TEST(QueryPlan, ExpandOptional) {
// MATCH (n) OPTIONAL MATCH (n)-[r]->(m)
auto n = MakeScanAll(storage, symbol_table, "n");
auto r_m = MakeExpand(storage, symbol_table, nullptr, n.sym_, "r",
EdgeAtom::Direction::OUT, {}, false, "m", false);
EdgeAtom::Direction::OUT, {}, "m", false);
auto optional = std::make_shared<plan::Optional>(
n.op_, r_m.op_, std::vector<Symbol>{r_m.edge_sym_, r_m.node_sym_});
@ -1050,7 +1013,7 @@ TEST(QueryPlan, OptionalMatchEmptyDBExpandFromNode) {
auto with = MakeProduce(optional, n_ne);
// MATCH (n) -[r]-> (m)
auto r_m = MakeExpand(storage, symbol_table, with, with_n_sym, "r",
EdgeAtom::Direction::OUT, {}, false, "m", false);
EdgeAtom::Direction::OUT, {}, "m", false);
// RETURN m
auto m_ne = NEXPR("m", IDENT("m"));
symbol_table[*m_ne->expression_] = r_m.node_sym_;
@ -1099,7 +1062,7 @@ TEST(QueryPlan, OptionalMatchThenExpandToMissingNode) {
symbol_table[*node->identifier_] = with_n_sym;
auto expand = std::make_shared<plan::Expand>(
with_n_sym, edge_sym, edge_direction,
std::vector<GraphDbTypes::EdgeType>{}, m.op_, m.sym_, true, false);
std::vector<GraphDbTypes::EdgeType>{}, m.op_, m.sym_, true);
// RETURN m
auto m_ne = NEXPR("m", IDENT("m"));
symbol_table[*m_ne->expression_] = m.sym_;
@ -1109,57 +1072,6 @@ TEST(QueryPlan, OptionalMatchThenExpandToMissingNode) {
EXPECT_EQ(0, results.size());
}
TEST(QueryPlan, OptionalMatchThenExpandToMissingEdge) {
Dbms dbms;
auto dba = dbms.active();
// Make a graph with 2 connected, unlabeled nodes.
auto v1 = dba->InsertVertex();
auto v2 = dba->InsertVertex();
auto edge_type = dba->EdgeType("edge_type");
dba->InsertEdge(v1, v2, edge_type);
dba->AdvanceCommand();
EXPECT_EQ(2, CountIterable(dba->Vertices(false)));
EXPECT_EQ(1, CountIterable(dba->Edges(false)));
AstTreeStorage storage;
SymbolTable symbol_table;
// OPTIONAL MATCH (n :missing) -[r]- (m)
auto n = MakeScanAll(storage, symbol_table, "n");
auto label_missing = dba->Label("missing");
n.node_->labels_.emplace_back(label_missing);
auto *filter_expr =
storage.Create<LabelsTest>(n.node_->identifier_, n.node_->labels_);
auto node_filter = std::make_shared<Filter>(n.op_, filter_expr);
auto r_m = MakeExpand(storage, symbol_table, node_filter, n.sym_, "r",
EdgeAtom::Direction::BOTH, {}, false, "m", false);
auto optional = std::make_shared<plan::Optional>(
nullptr, r_m.op_,
std::vector<Symbol>{n.sym_, r_m.edge_sym_, r_m.node_sym_});
// WITH r
auto r_ne = NEXPR("r", IDENT("r"));
symbol_table[*r_ne->expression_] = r_m.edge_sym_;
auto with_r_sym = symbol_table.CreateSymbol("r", true);
symbol_table[*r_ne] = with_r_sym;
auto with = MakeProduce(optional, r_ne);
// MATCH (a) -[r]- (b)
auto a = MakeScanAll(storage, symbol_table, "a", with);
auto edge_direction = EdgeAtom::Direction::BOTH;
auto edge = EDGE("r", edge_direction);
symbol_table[*edge->identifier_] = with_r_sym;
auto node = NODE("n");
auto node_sym = symbol_table.CreateSymbol("b", true);
symbol_table[*node->identifier_] = node_sym;
auto expand = std::make_shared<plan::Expand>(
node_sym, with_r_sym, edge_direction,
std::vector<GraphDbTypes::EdgeType>{}, a.op_, a.sym_, false, true);
// RETURN a
auto a_ne = NEXPR("a", IDENT("a"));
symbol_table[*a_ne->expression_] = a.sym_;
symbol_table[*a_ne] = symbol_table.CreateSymbol("a", true);
auto produce = MakeProduce(expand, a_ne);
auto results = CollectProduce(produce.get(), symbol_table, *dba);
EXPECT_EQ(0, results.size());
}
TEST(QueryPlan, ExpandExistingNode) {
Dbms dbms;
auto dba = dbms.active();
@ -1178,14 +1090,13 @@ TEST(QueryPlan, ExpandExistingNode) {
auto test_existing = [&](bool with_existing, int expected_result_count) {
auto n = MakeScanAll(storage, symbol_table, "n");
auto r_n =
MakeExpand(storage, symbol_table, n.op_, n.sym_, "r",
EdgeAtom::Direction::OUT, {}, false, "n", with_existing);
auto r_n = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r",
EdgeAtom::Direction::OUT, {}, "n", with_existing);
if (with_existing)
r_n.op_ =
std::make_shared<Expand>(n.sym_, r_n.edge_sym_, r_n.edge_->direction_,
std::vector<GraphDbTypes::EdgeType>{}, n.op_,
n.sym_, with_existing, false);
n.sym_, with_existing);
// make a named expression and a produce
auto output = NEXPR("n", IDENT("n"));
@ -1202,54 +1113,6 @@ TEST(QueryPlan, ExpandExistingNode) {
test_existing(false, 2);
}
TEST(QueryPlan, ExpandExistingEdge) {
Dbms dbms;
auto dba = dbms.active();
// make a V-graph (v3)<-[r2]-(v1)-[r1]->(v2)
auto v1 = dba->InsertVertex();
v1.add_label((GraphDbTypes::Label)1);
auto v2 = dba->InsertVertex();
v2.add_label((GraphDbTypes::Label)2);
auto v3 = dba->InsertVertex();
v3.add_label((GraphDbTypes::Label)3);
auto edge_type = dba->EdgeType("Edge");
dba->InsertEdge(v1, v2, edge_type);
dba->InsertEdge(v1, v3, edge_type);
dba->AdvanceCommand();
AstTreeStorage storage;
SymbolTable symbol_table;
auto test_existing = [&](bool with_existing, int expected_result_count) {
auto i = MakeScanAll(storage, symbol_table, "i");
auto r_j = MakeExpand(storage, symbol_table, i.op_, i.sym_, "r",
EdgeAtom::Direction::BOTH, {}, false, "j", false);
auto r_k =
MakeExpand(storage, symbol_table, r_j.op_, r_j.node_sym_, "r",
EdgeAtom::Direction::BOTH, {}, with_existing, "k", false);
if (with_existing)
r_k.op_ = std::make_shared<Expand>(
r_k.node_sym_, r_j.edge_sym_, r_k.edge_->direction_,
std::vector<GraphDbTypes::EdgeType>{}, r_j.op_, r_j.node_sym_, false,
with_existing);
// make a named expression and a produce
auto output = NEXPR("r", IDENT("r"));
symbol_table[*output->expression_] = r_j.edge_sym_;
symbol_table[*output] =
symbol_table.CreateSymbol("named_expression_1", true);
auto produce = MakeProduce(r_k.op_, output);
auto results = CollectProduce(produce.get(), symbol_table, *dba);
EXPECT_EQ(results.size(), expected_result_count);
};
test_existing(true, 4);
test_existing(false, 6);
}
TEST(QueryPlan, ExpandBothCycleEdgeCase) {
// we're testing that expanding on BOTH
// does only one expansion for a cycle
@ -1265,7 +1128,7 @@ TEST(QueryPlan, ExpandBothCycleEdgeCase) {
auto n = MakeScanAll(storage, symbol_table, "n");
auto r_ = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r",
EdgeAtom::Direction::BOTH, {}, false, "_", false);
EdgeAtom::Direction::BOTH, {}, "_", false);
EXPECT_EQ(1, PullAll(r_.op_, *dba, symbol_table));
}
@ -1311,9 +1174,8 @@ TEST(QueryPlan, EdgeFilter) {
auto n = MakeScanAll(storage, symbol_table, "n");
const auto &edge_type = edge_types[0];
auto r_m =
MakeExpand(storage, symbol_table, n.op_, n.sym_, "r",
EdgeAtom::Direction::OUT, {edge_type}, false, "m", false);
auto r_m = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r",
EdgeAtom::Direction::OUT, {edge_type}, "m", false);
r_m.edge_->edge_types_.push_back(edge_type);
r_m.edge_->properties_[prop] = LITERAL(42);
auto *filter_expr =
@ -1357,9 +1219,8 @@ TEST(QueryPlan, EdgeFilterMultipleTypes) {
// make a scan all
auto n = MakeScanAll(storage, symbol_table, "n");
auto r_m =
MakeExpand(storage, symbol_table, n.op_, n.sym_, "r",
EdgeAtom::Direction::OUT, {type_1, type_2}, false, "m", false);
auto r_m = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r",
EdgeAtom::Direction::OUT, {type_1, type_2}, "m", false);
// make a named expression and a produce
auto output = NEXPR("m", IDENT("m"));
@ -1421,14 +1282,13 @@ TEST(QueryPlan, ExpandUniquenessFilter) {
auto n1 = MakeScanAll(storage, symbol_table, "n1");
auto r1_n2 = MakeExpand(storage, symbol_table, n1.op_, n1.sym_, "r1",
EdgeAtom::Direction::OUT, {}, false, "n2", false);
EdgeAtom::Direction::OUT, {}, "n2", false);
std::shared_ptr<LogicalOperator> last_op = r1_n2.op_;
if (vertex_uniqueness)
last_op = std::make_shared<ExpandUniquenessFilter<VertexAccessor>>(
last_op, r1_n2.node_sym_, std::vector<Symbol>{n1.sym_});
auto r2_n3 =
MakeExpand(storage, symbol_table, last_op, r1_n2.node_sym_, "r2",
EdgeAtom::Direction::OUT, {}, false, "n3", false);
auto r2_n3 = MakeExpand(storage, symbol_table, last_op, r1_n2.node_sym_,
"r2", EdgeAtom::Direction::OUT, {}, "n3", false);
last_op = r2_n3.op_;
if (edge_uniqueness)
last_op = std::make_shared<ExpandUniquenessFilter<EdgeAccessor>>(

View File

@ -55,7 +55,6 @@ class PlanChecker : public HierarchicalLogicalOperatorVisitor {
PRE_VISIT(ScanAllByLabelPropertyRange);
PRE_VISIT(Expand);
PRE_VISIT(ExpandVariable);
PRE_VISIT(ExpandBreadthFirst);
PRE_VISIT(Filter);
PRE_VISIT(ConstructNamedPath);
PRE_VISIT(Produce);
@ -125,8 +124,6 @@ using ExpectDelete = OpChecker<Delete>;
using ExpectScanAll = OpChecker<ScanAll>;
using ExpectScanAllByLabel = OpChecker<ScanAllByLabel>;
using ExpectExpand = OpChecker<Expand>;
using ExpectExpandVariable = OpChecker<ExpandVariable>;
using ExpectExpandBreadthFirst = OpChecker<ExpandBreadthFirst>;
using ExpectFilter = OpChecker<Filter>;
using ExpectConstructNamedPath = OpChecker<ConstructNamedPath>;
using ExpectProduce = OpChecker<Produce>;
@ -144,6 +141,20 @@ using ExpectOrderBy = OpChecker<OrderBy>;
using ExpectUnwind = OpChecker<Unwind>;
using ExpectDistinct = OpChecker<Distinct>;
class ExpectExpandVariable : public OpChecker<ExpandVariable> {
public:
void ExpectOp(ExpandVariable &op, const SymbolTable &) override {
EXPECT_EQ(op.type(), query::EdgeAtom::Type::DEPTH_FIRST);
}
};
class ExpectExpandBreadthFirst : public OpChecker<ExpandVariable> {
public:
void ExpectOp(ExpandVariable &op, const SymbolTable &) override {
EXPECT_EQ(op.type(), query::EdgeAtom::Type::BREADTH_FIRST);
}
};
class ExpectAccumulate : public OpChecker<Accumulate> {
public:
ExpectAccumulate(const std::unordered_set<Symbol> &symbols)
@ -341,8 +352,8 @@ TEST(TestLogicalPlanner, CreateExpand) {
Dbms dbms;
auto dba = dbms.active();
auto relationship = dba->EdgeType("relationship");
QUERY(CREATE(
PATTERN(NODE("n"), EDGE("r", relationship, Direction::OUT), NODE("m"))));
QUERY(CREATE(PATTERN(NODE("n"), EDGE("r", Direction::OUT, {relationship}),
NODE("m"))));
CheckPlan(storage, ExpectCreateNode(), ExpectCreateExpand());
}
@ -360,7 +371,7 @@ TEST(TestLogicalPlanner, CreateNodeExpandNode) {
auto dba = dbms.active();
auto relationship = dba->EdgeType("rel");
QUERY(CREATE(
PATTERN(NODE("n"), EDGE("r", relationship, Direction::OUT), NODE("m")),
PATTERN(NODE("n"), EDGE("r", Direction::OUT, {relationship}), NODE("m")),
PATTERN(NODE("l"))));
CheckPlan(storage, ExpectCreateNode(), ExpectCreateExpand(),
ExpectCreateNode());
@ -373,7 +384,7 @@ TEST(TestLogicalPlanner, CreateNamedPattern) {
auto dba = dbms.active();
auto relationship = dba->EdgeType("rel");
QUERY(CREATE(NAMED_PATTERN(
"p", NODE("n"), EDGE("r", relationship, Direction::OUT), NODE("m"))));
"p", NODE("n"), EDGE("r", Direction::OUT, {relationship}), NODE("m"))));
CheckPlan(storage, ExpectCreateNode(), ExpectCreateExpand(),
ExpectConstructNamedPath());
}
@ -385,7 +396,7 @@ TEST(TestLogicalPlanner, MatchCreateExpand) {
auto dba = dbms.active();
auto relationship = dba->EdgeType("relationship");
QUERY(MATCH(PATTERN(NODE("n"))),
CREATE(PATTERN(NODE("n"), EDGE("r", relationship, Direction::OUT),
CREATE(PATTERN(NODE("n"), EDGE("r", Direction::OUT, {relationship}),
NODE("m"))));
CheckPlan(storage, ExpectScanAll(), ExpectCreateExpand());
}
@ -406,10 +417,10 @@ TEST(TestLogicalPlanner, MatchPathReturn) {
Dbms dbms;
auto dba = dbms.active();
auto relationship = dba->EdgeType("relationship");
QUERY(MATCH(PATTERN(NODE("n"), EDGE("r", relationship), NODE("m"))),
QUERY(MATCH(PATTERN(NODE("n"), EDGE("r", Direction::BOTH, {relationship}),
NODE("m"))),
RETURN("n"));
CheckPlan(storage, ExpectScanAll(), ExpectExpand(),
ExpectProduce());
CheckPlan(storage, ExpectScanAll(), ExpectExpand(), ExpectProduce());
}
TEST(TestLogicalPlanner, MatchNamedPatternReturn) {
@ -418,9 +429,10 @@ TEST(TestLogicalPlanner, MatchNamedPatternReturn) {
Dbms dbms;
auto dba = dbms.active();
auto relationship = dba->EdgeType("relationship");
QUERY(
MATCH(NAMED_PATTERN("p", NODE("n"), EDGE("r", relationship), NODE("m"))),
RETURN("n"));
QUERY(MATCH(NAMED_PATTERN("p", NODE("n"),
EDGE("r", Direction::BOTH, {relationship}),
NODE("m"))),
RETURN("n"));
CheckPlan(storage, ExpectScanAll(), ExpectExpand(),
ExpectConstructNamedPath(), ExpectProduce());
}
@ -431,9 +443,10 @@ TEST(TestLogicalPlanner, MatchNamedPatternWithPredicateReturn) {
Dbms dbms;
auto dba = dbms.active();
auto relationship = dba->EdgeType("relationship");
QUERY(
MATCH(NAMED_PATTERN("p", NODE("n"), EDGE("r", relationship), NODE("m"))),
WHERE(EQ(LITERAL(2), IDENT("p"))), RETURN("n"));
QUERY(MATCH(NAMED_PATTERN("p", NODE("n"),
EDGE("r", Direction::BOTH, {relationship}),
NODE("m"))),
WHERE(EQ(LITERAL(2), IDENT("p"))), RETURN("n"));
CheckPlan(storage, ExpectScanAll(), ExpectExpand(),
ExpectConstructNamedPath(), ExpectFilter(), ExpectProduce());
}
@ -569,8 +582,8 @@ TEST(TestLogicalPlanner, CreateMultiExpand) {
auto r = dba->EdgeType("r");
auto p = dba->EdgeType("p");
AstTreeStorage storage;
QUERY(CREATE(PATTERN(NODE("n"), EDGE("r", r, Direction::OUT), NODE("m")),
PATTERN(NODE("n"), EDGE("p", p, Direction::OUT), NODE("l"))));
QUERY(CREATE(PATTERN(NODE("n"), EDGE("r", Direction::OUT, {r}), NODE("m")),
PATTERN(NODE("n"), EDGE("p", Direction::OUT, {p}), NODE("l"))));
CheckPlan(storage, ExpectCreateNode(), ExpectCreateExpand(),
ExpectCreateExpand());
}
@ -631,9 +644,9 @@ TEST(TestLogicalPlanner, MatchWithCreate) {
auto dba = dbms.active();
auto r_type = dba->EdgeType("r");
AstTreeStorage storage;
QUERY(
MATCH(PATTERN(NODE("n"))), WITH("n", AS("a")),
CREATE(PATTERN(NODE("a"), EDGE("r", r_type, Direction::OUT), NODE("b"))));
QUERY(MATCH(PATTERN(NODE("n"))), WITH("n", AS("a")),
CREATE(PATTERN(NODE("a"), EDGE("r", Direction::OUT, {r_type}),
NODE("b"))));
CheckPlan(storage, ExpectScanAll(), ExpectProduce(), ExpectCreateExpand());
}
@ -708,10 +721,11 @@ TEST(TestLogicalPlanner, CreateWithOrderByWhere) {
auto new_prop = PROPERTY_LOOKUP("new", prop);
auto r_prop = PROPERTY_LOOKUP("r", prop);
auto m_prop = PROPERTY_LOOKUP("m", prop);
auto query = QUERY(
CREATE(PATTERN(NODE("n"), EDGE("r", r_type, Direction::OUT), NODE("m"))),
WITH(ident_n, AS("new"), ORDER_BY(new_prop, r_prop)),
WHERE(LESS(m_prop, LITERAL(42))));
auto query =
QUERY(CREATE(PATTERN(NODE("n"), EDGE("r", Direction::OUT, {r_type}),
NODE("m"))),
WITH(ident_n, AS("new"), ORDER_BY(new_prop, r_prop)),
WHERE(LESS(m_prop, LITERAL(42))));
auto symbol_table = MakeSymbolTable(*query);
// Since this is a write query, we expect to accumulate to old used symbols.
auto acc = ExpectAccumulate({
@ -744,12 +758,12 @@ TEST(TestLogicalPlanner, MatchMerge) {
auto prop = dba->Property("prop");
AstTreeStorage storage;
auto ident_n = IDENT("n");
auto query =
QUERY(MATCH(PATTERN(NODE("n"))),
MERGE(PATTERN(NODE("n"), EDGE("r", r_type), NODE("m")),
ON_MATCH(SET(PROPERTY_LOOKUP("n", prop), LITERAL(42))),
ON_CREATE(SET("m", IDENT("n")))),
RETURN(ident_n, AS("n")));
auto query = QUERY(
MATCH(PATTERN(NODE("n"))),
MERGE(PATTERN(NODE("n"), EDGE("r", Direction::BOTH, {r_type}), NODE("m")),
ON_MATCH(SET(PROPERTY_LOOKUP("n", prop), LITERAL(42))),
ON_CREATE(SET("m", IDENT("n")))),
RETURN(ident_n, AS("n")));
std::list<BaseOpChecker *> on_match{new ExpectExpand(),
new ExpectSetProperty()};
std::list<BaseOpChecker *> on_create{new ExpectCreateExpand(),
@ -1254,8 +1268,7 @@ TEST(TestLogicalPlanner, ReturnSumGroupByAll) {
TEST(TestLogicalPlanner, MatchExpandVariable) {
// Test MATCH (n) -[r *..3]-> (m) RETURN r
AstTreeStorage storage;
auto edge = EDGE("r");
edge->has_range_ = true;
auto edge = EDGE_VARIABLE("r");
edge->upper_bound_ = LITERAL(3);
QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("r"));
CheckPlan(storage, ExpectScanAll(), ExpectExpandVariable(), ExpectProduce());
@ -1264,8 +1277,7 @@ TEST(TestLogicalPlanner, MatchExpandVariable) {
TEST(TestLogicalPlanner, MatchExpandVariableNoBounds) {
// Test MATCH (n) -[r *]-> (m) RETURN r
AstTreeStorage storage;
auto edge = EDGE("r");
edge->has_range_ = true;
auto edge = EDGE_VARIABLE("r");
QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("r"));
CheckPlan(storage, ExpectScanAll(), ExpectExpandVariable(), ExpectProduce());
}
@ -1277,13 +1289,12 @@ TEST(TestLogicalPlanner, MatchExpandVariableInlinedFilter) {
auto type = dba->EdgeType("type");
auto prop = PROPERTY_PAIR("prop");
AstTreeStorage storage;
auto edge = EDGE("r", type);
edge->has_range_ = true;
auto edge = EDGE_VARIABLE("r", Direction::BOTH, {type});
edge->properties_[prop] = LITERAL(42);
QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("r"));
CheckPlan(storage, ExpectScanAll(),
ExpectExpandVariable(), // Filter is inlined in expand
ExpectProduce());
ExpectExpandVariable(), // Filter is both inlined and post-expand
ExpectFilter(), ExpectProduce());
}
TEST(TestLogicalPlanner, MatchExpandVariableNotInlinedFilter) {
@ -1293,8 +1304,7 @@ TEST(TestLogicalPlanner, MatchExpandVariableNotInlinedFilter) {
auto type = dba->EdgeType("type");
auto prop = PROPERTY_PAIR("prop");
AstTreeStorage storage;
auto edge = EDGE("r", type);
edge->has_range_ = true;
auto edge = EDGE_VARIABLE("r", Direction::BOTH, {type});
edge->properties_[prop] = EQ(PROPERTY_LOOKUP("m", prop), LITERAL(42));
QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("r"));
CheckPlan(storage, ExpectScanAll(), ExpectExpandVariable(), ExpectFilter(),
@ -1304,8 +1314,7 @@ TEST(TestLogicalPlanner, MatchExpandVariableNotInlinedFilter) {
TEST(TestLogicalPlanner, UnwindMatchVariable) {
// Test UNWIND [1,2,3] AS depth MATCH (n) -[r*d]-> (m) RETURN r
AstTreeStorage storage;
auto edge = EDGE("r", Direction::OUT);
edge->has_range_ = true;
auto edge = EDGE_VARIABLE("r", Direction::OUT);
edge->lower_bound_ = IDENT("d");
edge->upper_bound_ = IDENT("d");
QUERY(UNWIND(LIST(LITERAL(1), LITERAL(2), LITERAL(3)), AS("d")),
@ -1315,15 +1324,17 @@ TEST(TestLogicalPlanner, UnwindMatchVariable) {
}
TEST(TestLogicalPlanner, MatchBreadthFirst) {
// Test MATCH (n) -bfs[r:type](r, n|n, 10)-> (m) RETURN r
// Test MATCH (n) -[r:type *..10 (r, n|n)]-> (m) RETURN r
Dbms dbms;
auto dba = dbms.active();
auto edge_type = dba->EdgeType("type");
AstTreeStorage storage;
auto *bfs = storage.Create<query::BreadthFirstAtom>(
IDENT("r"), Direction::OUT,
std::vector<GraphDbTypes::EdgeType>{edge_type}, IDENT("r"), IDENT("n"),
IDENT("n"));
auto *bfs = storage.Create<query::EdgeAtom>(
IDENT("r"), query::EdgeAtom::Type::BREADTH_FIRST, Direction::OUT,
std::vector<GraphDbTypes::EdgeType>{edge_type});
bfs->inner_edge_ = IDENT("r");
bfs->inner_node_ = IDENT("n");
bfs->filter_expression_ = IDENT("n");
bfs->upper_bound_ = LITERAL(10);
QUERY(MATCH(PATTERN(NODE("n"), bfs, NODE("m"))), RETURN("r"));
CheckPlan(storage, ExpectScanAll(), ExpectExpandBreadthFirst(),

View File

@ -154,10 +154,11 @@ TEST(TestSymbolGenerator, MatchCreateRedeclareEdge) {
Dbms dbms;
auto dba = dbms.active();
auto relationship = dba->EdgeType("relationship");
auto query = QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m"))),
CREATE(PATTERN(NODE("n"), EDGE("r", relationship,
EdgeAtom::Direction::OUT),
NODE("l"))));
auto query =
QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m"))),
CREATE(PATTERN(NODE("n"),
EDGE("r", EdgeAtom::Direction::OUT, {relationship}),
NODE("l"))));
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), RedeclareVariableError);
}
@ -193,7 +194,7 @@ TEST(TestSymbolGenerator, CreateMultipleEdgeType) {
auto dba = dbms.active();
auto rel1 = dba->EdgeType("rel1");
auto rel2 = dba->EdgeType("rel2");
auto edge = EDGE("r", rel1, EdgeAtom::Direction::OUT);
auto edge = EDGE("r", EdgeAtom::Direction::OUT, {rel1});
edge->edge_types_.emplace_back(rel2);
auto query = QUERY(CREATE(PATTERN(NODE("n"), edge, NODE("m"))));
SymbolTable symbol_table;
@ -208,7 +209,8 @@ TEST(TestSymbolGenerator, CreateBidirectionalEdge) {
Dbms dbms;
auto dba = dbms.active();
auto rel1 = dba->EdgeType("rel1");
auto query = QUERY(CREATE(PATTERN(NODE("n"), EDGE("r", rel1), NODE("m"))));
auto query = QUERY(CREATE(PATTERN(
NODE("n"), EDGE("r", EdgeAtom::Direction::BOTH, {rel1}), NODE("m"))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), SemanticException);
@ -334,10 +336,10 @@ TEST(TestSymbolGenerator, CreateMultiExpand) {
auto p_type = dba->EdgeType("p");
AstTreeStorage storage;
auto node_n1 = NODE("n");
auto edge_r = EDGE("r", r_type, EdgeAtom::Direction::OUT);
auto edge_r = EDGE("r", EdgeAtom::Direction::OUT, {r_type});
auto node_m = NODE("m");
auto node_n2 = NODE("n");
auto edge_p = EDGE("p", p_type, EdgeAtom::Direction::OUT);
auto edge_p = EDGE("p", EdgeAtom::Direction::OUT, {p_type});
auto node_l = NODE("l");
auto query = QUERY(CREATE(PATTERN(node_n1, edge_r, node_m),
PATTERN(node_n2, edge_p, node_l)));
@ -373,7 +375,7 @@ TEST(TestSymbolGenerator, MatchCreateExpandLabel) {
AstTreeStorage storage;
auto query = QUERY(
MATCH(PATTERN(NODE("n"))),
CREATE(PATTERN(NODE("m"), EDGE("r", r_type, EdgeAtom::Direction::OUT),
CREATE(PATTERN(NODE("m"), EDGE("r", EdgeAtom::Direction::OUT, {r_type}),
NODE("n", label))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
@ -388,8 +390,8 @@ TEST(TestSymbolGenerator, CreateExpandProperty) {
AstTreeStorage storage;
auto n_prop = NODE("n");
n_prop->properties_[PROPERTY_PAIR("prop")] = LITERAL(42);
auto query = QUERY(CREATE(
PATTERN(NODE("n"), EDGE("r", r_type, EdgeAtom::Direction::OUT), n_prop)));
auto query = QUERY(CREATE(PATTERN(
NODE("n"), EDGE("r", EdgeAtom::Direction::OUT, {r_type}), n_prop)));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), SemanticException);
@ -477,7 +479,7 @@ TEST(TestSymbolGenerator, CreateNodeEdge) {
AstTreeStorage storage;
auto node_1 = NODE("n");
auto node_2 = NODE("n");
auto edge = EDGE("r", r_type, EdgeAtom::Direction::OUT);
auto edge = EDGE("r", EdgeAtom::Direction::OUT, {r_type});
auto node_3 = NODE("n");
auto query = QUERY(CREATE(PATTERN(node_1), PATTERN(node_2, edge, node_3)));
SymbolTable symbol_table;
@ -499,7 +501,7 @@ TEST(TestSymbolGenerator, MatchWithCreate) {
AstTreeStorage storage;
auto node_1 = NODE("n");
auto node_2 = NODE("m");
auto edge = EDGE("r", r_type, EdgeAtom::Direction::OUT);
auto edge = EDGE("r", EdgeAtom::Direction::OUT, {r_type});
auto node_3 = NODE("m");
auto query = QUERY(MATCH(PATTERN(node_1)), WITH("n", AS("m")),
CREATE(PATTERN(node_2, edge, node_3)));
@ -645,8 +647,10 @@ TEST(TestSymbolGenerator, MergeVariableError) {
auto dba = dbms.active();
auto rel = dba->EdgeType("rel");
AstTreeStorage storage;
auto query = QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m"))),
MERGE(PATTERN(NODE("a"), EDGE("r", rel), NODE("b"))));
auto query = QUERY(
MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m"))),
MERGE(PATTERN(NODE("a"), EDGE("r", EdgeAtom::Direction::BOTH, {rel}),
NODE("b"))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), RedeclareVariableError);
@ -673,7 +677,7 @@ TEST(TestSymbolGenerator, MergeOnMatchOnCreate) {
AstTreeStorage storage;
auto match_n = NODE("n");
auto merge_n = NODE("n");
auto edge_r = EDGE("r", rel);
auto edge_r = EDGE("r", EdgeAtom::Direction::BOTH, {rel});
auto node_m = NODE("m");
auto n_prop = PROPERTY_LOOKUP("n", prop);
auto m_prop = PROPERTY_LOOKUP("m", prop);
@ -827,7 +831,7 @@ TEST(TestSymbolGenerator, MatchMergeExpandLabel) {
AstTreeStorage storage;
auto query = QUERY(
MATCH(PATTERN(NODE("n"))),
MERGE(PATTERN(NODE("m"), EDGE("r", r_type, EdgeAtom::Direction::OUT),
MERGE(PATTERN(NODE("m"), EDGE("r", EdgeAtom::Direction::OUT, {r_type}),
NODE("n", label))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
@ -860,8 +864,7 @@ TEST(TestSymbolGenerator, MatchVariablePathUsingIdentifier) {
auto dba = dbms.active();
auto prop = dba->Property("prop");
AstTreeStorage storage;
auto edge = EDGE("r");
edge->has_range_ = true;
auto edge = EDGE_VARIABLE("r");
auto l_prop = PROPERTY_LOOKUP("l", prop);
edge->upper_bound_ = l_prop;
auto node_l = NODE("l");
@ -870,8 +873,9 @@ TEST(TestSymbolGenerator, MatchVariablePathUsingIdentifier) {
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
query->Accept(symbol_generator);
// Symbols for pattern * 2, `n`, `r`, `m`, `l` and implicit in RETURN `r AS r`
EXPECT_EQ(symbol_table.max_position(), 7);
// Symbols for pattern * 2, `n`, `r`, inner_node, inner_edge, `m`, `l` and
// implicit in RETURN `r AS r`
EXPECT_EQ(symbol_table.max_position(), 9);
auto l = symbol_table.at(*node_l->identifier_);
EXPECT_EQ(l, symbol_table.at(*l_prop->expression_));
auto r = symbol_table.at(*edge->identifier_);
@ -884,8 +888,7 @@ TEST(TestSymbolGenerator, MatchVariablePathUsingUnboundIdentifier) {
auto dba = dbms.active();
auto prop = dba->Property("prop");
AstTreeStorage storage;
auto edge = EDGE("r");
edge->has_range_ = true;
auto edge = EDGE_VARIABLE("r");
auto l_prop = PROPERTY_LOOKUP("l", prop);
edge->upper_bound_ = l_prop;
auto node_l = NODE("l");
@ -900,8 +903,7 @@ TEST(TestSymbolGenerator, CreateVariablePath) {
// Test CREATE (n) -[r *]-> (m) raises a SemanticException, since variable
// paths cannot be created.
AstTreeStorage storage;
auto edge = EDGE("r", EdgeAtom::Direction::OUT);
edge->has_range_ = true;
auto edge = EDGE_VARIABLE("r", EdgeAtom::Direction::OUT);
auto query = QUERY(CREATE(PATTERN(NODE("n"), edge, NODE("m"))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
@ -912,8 +914,7 @@ TEST(TestSymbolGenerator, MergeVariablePath) {
// Test MERGE (n) -[r *]-> (m) raises a SemanticException, since variable
// paths cannot be created.
AstTreeStorage storage;
auto edge = EDGE("r", EdgeAtom::Direction::OUT);
edge->has_range_ = true;
auto edge = EDGE_VARIABLE("r", EdgeAtom::Direction::OUT);
auto query = QUERY(MERGE(PATTERN(NODE("n"), edge, NODE("m"))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
@ -926,8 +927,7 @@ TEST(TestSymbolGenerator, RedeclareVariablePath) {
// variable paths with already declared symbols. In the future, this test
// should be changed to check for type errors.
AstTreeStorage storage;
auto edge = EDGE("n", EdgeAtom::Direction::OUT);
edge->has_range_ = true;
auto edge = EDGE_VARIABLE("n", EdgeAtom::Direction::OUT);
auto query = QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("n"));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
@ -942,8 +942,7 @@ TEST(TestSymbolGenerator, VariablePathSameIdentifier) {
auto dba = dbms.active();
auto prop = dba->Property("prop");
AstTreeStorage storage;
auto edge = EDGE("r", EdgeAtom::Direction::OUT);
edge->has_range_ = true;
auto edge = EDGE_VARIABLE("r", EdgeAtom::Direction::OUT);
edge->lower_bound_ = PROPERTY_LOOKUP("r", prop);
auto query = QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("r"));
SymbolTable symbol_table;
@ -997,7 +996,7 @@ TEST(TestSymbolGenerator, WithReturnAll) {
}
TEST(TestSymbolGenerator, MatchBfsReturn) {
// Test MATCH (n) -bfs[r](r, n | r.prop, n.prop)-> (m) RETURN r AS r
// Test MATCH (n) -[r *bfs..n.prop] (r, n | r.prop)]-> (m) RETURN r AS r
Dbms dbms;
auto dba = dbms.active();
auto prop = dba->Property("prop");
@ -1005,9 +1004,12 @@ TEST(TestSymbolGenerator, MatchBfsReturn) {
auto *node_n = NODE("n");
auto *r_prop = PROPERTY_LOOKUP("r", prop);
auto *n_prop = PROPERTY_LOOKUP("n", prop);
auto *bfs = storage.Create<BreadthFirstAtom>(
IDENT("r"), EdgeAtom::Direction::OUT,
std::vector<GraphDbTypes::EdgeType>{}, IDENT("r"), IDENT("n"), r_prop);
auto *bfs = storage.Create<EdgeAtom>(
IDENT("r"), EdgeAtom::Type::BREADTH_FIRST, EdgeAtom::Direction::OUT,
std::vector<GraphDbTypes::EdgeType>{});
bfs->inner_edge_ = IDENT("r");
bfs->inner_node_ = IDENT("n");
bfs->filter_expression_ = r_prop;
bfs->upper_bound_ = n_prop;
auto *ret_r = IDENT("r");
auto *query =
@ -1018,23 +1020,23 @@ TEST(TestSymbolGenerator, MatchBfsReturn) {
// Symbols for pattern, `n`, `[r]`, `r|`, `n|`, `m` and `AS r`.
EXPECT_EQ(symbol_table.max_position(), 7);
EXPECT_EQ(symbol_table.at(*ret_r), symbol_table.at(*bfs->identifier_));
EXPECT_NE(symbol_table.at(*ret_r),
symbol_table.at(*bfs->traversed_edge_identifier_));
EXPECT_EQ(symbol_table.at(*bfs->traversed_edge_identifier_),
EXPECT_NE(symbol_table.at(*ret_r), symbol_table.at(*bfs->inner_edge_));
EXPECT_EQ(symbol_table.at(*bfs->inner_edge_),
symbol_table.at(*r_prop->expression_));
EXPECT_NE(symbol_table.at(*node_n->identifier_),
symbol_table.at(*bfs->next_node_identifier_));
symbol_table.at(*bfs->inner_node_));
EXPECT_EQ(symbol_table.at(*node_n->identifier_),
symbol_table.at(*n_prop->expression_));
}
TEST(TestSymbolGenerator, MatchBfsUsesEdgeSymbolError) {
// Test MATCH (n) -bfs[r](e, n | r, 10)-> (m) RETURN r
// Test MATCH (n) -[r *bfs..10 (e, n | r)]-> (m) RETURN r
AstTreeStorage storage;
auto *bfs =
storage.Create<BreadthFirstAtom>(IDENT("r"), EdgeAtom::Direction::OUT,
std::vector<GraphDbTypes::EdgeType>{},
IDENT("e"), IDENT("n"), IDENT("r"));
auto *bfs = storage.Create<EdgeAtom>(
IDENT("r"), EdgeAtom::Type::BREADTH_FIRST, EdgeAtom::Direction::OUT);
bfs->inner_edge_ = IDENT("e");
bfs->inner_node_ = IDENT("n");
bfs->filter_expression_ = IDENT("r");
bfs->upper_bound_ = LITERAL(10);
auto *query = QUERY(MATCH(PATTERN(NODE("n"), bfs, NODE("m"))), RETURN("r"));
SymbolTable symbol_table;
@ -1043,13 +1045,14 @@ TEST(TestSymbolGenerator, MatchBfsUsesEdgeSymbolError) {
}
TEST(TestSymbolGenerator, MatchBfsUsesPreviousOuterSymbol) {
// Test MATCH (a) -bfs[r](e, n | a, 10)-> (m) RETURN r
// Test MATCH (a) -[r *bfs..10 (e, n | a)]-> (m) RETURN r
AstTreeStorage storage;
auto *node_a = NODE("a");
auto *bfs =
storage.Create<BreadthFirstAtom>(IDENT("r"), EdgeAtom::Direction::OUT,
std::vector<GraphDbTypes::EdgeType>{},
IDENT("e"), IDENT("n"), IDENT("a"));
auto *bfs = storage.Create<EdgeAtom>(
IDENT("r"), EdgeAtom::Type::BREADTH_FIRST, EdgeAtom::Direction::OUT);
bfs->inner_edge_ = IDENT("e");
bfs->inner_node_ = IDENT("n");
bfs->filter_expression_ = IDENT("a");
bfs->upper_bound_ = LITERAL(10);
auto *query = QUERY(MATCH(PATTERN(node_a, bfs, NODE("m"))), RETURN("r"));
SymbolTable symbol_table;
@ -1060,12 +1063,13 @@ TEST(TestSymbolGenerator, MatchBfsUsesPreviousOuterSymbol) {
}
TEST(TestSymbolGenerator, MatchBfsUsesLaterSymbolError) {
// Test MATCH (n) -bfs[r](e, n | m, 10)-> (m) RETURN r
// Test MATCH (n) -[r *bfs..10 (e, n | m)]-> (m) RETURN r
AstTreeStorage storage;
auto *bfs =
storage.Create<BreadthFirstAtom>(IDENT("r"), EdgeAtom::Direction::OUT,
std::vector<GraphDbTypes::EdgeType>{},
IDENT("e"), IDENT("n"), IDENT("m"));
auto *bfs = storage.Create<EdgeAtom>(
IDENT("r"), EdgeAtom::Type::BREADTH_FIRST, EdgeAtom::Direction::OUT);
bfs->inner_edge_ = IDENT("e");
bfs->inner_node_ = IDENT("n");
bfs->filter_expression_ = IDENT("m");
bfs->upper_bound_ = LITERAL(10);
auto *query = QUERY(MATCH(PATTERN(NODE("n"), bfs, NODE("m"))), RETURN("r"));
SymbolTable symbol_table;

View File

@ -88,9 +88,8 @@ TEST(TestVariableStartPlanner, MatchReturn) {
dba->AdvanceCommand();
// Test MATCH (n) -[r]-> (m) RETURN n
AstTreeStorage storage;
QUERY(
MATCH(PATTERN(NODE("n"), EDGE("r", nullptr, Direction::OUT), NODE("m"))),
RETURN("n"));
QUERY(MATCH(PATTERN(NODE("n"), EDGE("r", Direction::OUT), NODE("m"))),
RETURN("n"));
// We have 2 nodes `n` and `m` from which we could start, so expect 2 plans.
CheckPlansProduce(2, storage, *dba, [&](const auto &results) {
// We expect to produce only a single (v1) node.
@ -111,10 +110,9 @@ TEST(TestVariableStartPlanner, MatchTripletPatternReturn) {
{
// Test `MATCH (n) -[r]-> (m) -[e]-> (l) RETURN n`
AstTreeStorage storage;
QUERY(
MATCH(PATTERN(NODE("n"), EDGE("r", nullptr, Direction::OUT), NODE("m"),
EDGE("e", nullptr, Direction::OUT), NODE("l"))),
RETURN("n"));
QUERY(MATCH(PATTERN(NODE("n"), EDGE("r", Direction::OUT), NODE("m"),
EDGE("e", Direction::OUT), NODE("l"))),
RETURN("n"));
// We have 3 nodes: `n`, `m` and `l` from which we could start.
CheckPlansProduce(3, storage, *dba, [&](const auto &results) {
// We expect to produce only a single (v1) node.
@ -124,11 +122,9 @@ TEST(TestVariableStartPlanner, MatchTripletPatternReturn) {
{
// Equivalent to `MATCH (n) -[r]-> (m), (m) -[e]-> (l) RETURN n`.
AstTreeStorage storage;
QUERY(
MATCH(
PATTERN(NODE("n"), EDGE("r", nullptr, Direction::OUT), NODE("m")),
PATTERN(NODE("m"), EDGE("e", nullptr, Direction::OUT), NODE("l"))),
RETURN("n"));
QUERY(MATCH(PATTERN(NODE("n"), EDGE("r", Direction::OUT), NODE("m")),
PATTERN(NODE("m"), EDGE("e", Direction::OUT), NODE("l"))),
RETURN("n"));
CheckPlansProduce(3, storage, *dba, [&](const auto &results) {
AssertRows(results, {{v1}});
});
@ -148,9 +144,8 @@ TEST(TestVariableStartPlanner, MatchOptionalMatchReturn) {
// Test MATCH (n) -[r]-> (m) OPTIONAL MATCH (m) -[e]-> (l) RETURN n, l
AstTreeStorage storage;
QUERY(
MATCH(PATTERN(NODE("n"), EDGE("r", nullptr, Direction::OUT), NODE("m"))),
OPTIONAL_MATCH(
PATTERN(NODE("m"), EDGE("e", nullptr, Direction::OUT), NODE("l"))),
MATCH(PATTERN(NODE("n"), EDGE("r", Direction::OUT), NODE("m"))),
OPTIONAL_MATCH(PATTERN(NODE("m"), EDGE("e", Direction::OUT), NODE("l"))),
RETURN("n", "l"));
// We have 2 nodes `n` and `m` from which we could start the MATCH, and 2
// nodes for OPTIONAL MATCH. This should produce 2 * 2 plans.
@ -175,10 +170,9 @@ TEST(TestVariableStartPlanner, MatchOptionalMatchMergeReturn) {
// MERGE (u) -[q:r]-> (v) RETURN n, m, l, u, v
AstTreeStorage storage;
QUERY(
MATCH(PATTERN(NODE("n"), EDGE("r", nullptr, Direction::OUT), NODE("m"))),
OPTIONAL_MATCH(
PATTERN(NODE("m"), EDGE("e", nullptr, Direction::OUT), NODE("l"))),
MERGE(PATTERN(NODE("u"), EDGE("q", r_type, Direction::OUT), NODE("v"))),
MATCH(PATTERN(NODE("n"), EDGE("r", Direction::OUT), NODE("m"))),
OPTIONAL_MATCH(PATTERN(NODE("m"), EDGE("e", Direction::OUT), NODE("l"))),
MERGE(PATTERN(NODE("u"), EDGE("q", Direction::OUT, {r_type}), NODE("v"))),
RETURN("n", "m", "l", "u", "v"));
// Since MATCH, OPTIONAL MATCH and MERGE each have 2 nodes from which we can
// start, we generate 2 * 2 * 2 plans.
@ -198,11 +192,10 @@ TEST(TestVariableStartPlanner, MatchWithMatchReturn) {
dba->AdvanceCommand();
// Test MATCH (n) -[r]-> (m) WITH n MATCH (m) -[r]-> (l) RETURN n, m, l
AstTreeStorage storage;
QUERY(
MATCH(PATTERN(NODE("n"), EDGE("r", nullptr, Direction::OUT), NODE("m"))),
WITH("n"),
MATCH(PATTERN(NODE("m"), EDGE("r", nullptr, Direction::OUT), NODE("l"))),
RETURN("n", "m", "l"));
QUERY(MATCH(PATTERN(NODE("n"), EDGE("r", Direction::OUT), NODE("m"))),
WITH("n"),
MATCH(PATTERN(NODE("m"), EDGE("r", Direction::OUT), NODE("l"))),
RETURN("n", "m", "l"));
// We can start from 2 nodes in each match. Since WITH separates query parts,
// we expect to get 2 plans for each, which totals 2 * 2.
CheckPlansProduce(4, storage, *dba, [&](const auto &results) {
@ -223,8 +216,7 @@ TEST(TestVariableStartPlanner, MatchVariableExpand) {
dba->AdvanceCommand();
// Test MATCH (n) -[r*]-> (m) RETURN r
AstTreeStorage storage;
auto edge = EDGE("r", Direction::OUT);
edge->has_range_ = true;
auto edge = EDGE_VARIABLE("r", Direction::OUT);
QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("r"));
// We expect to get a single column with the following rows:
TypedValue r1_list(std::vector<TypedValue>{r1}); // [r1]
@ -251,8 +243,7 @@ TEST(TestVariableStartPlanner, MatchVariableExpandReferenceNode) {
dba->AdvanceCommand();
// Test MATCH (n) -[r*..n.id]-> (m) RETURN r
AstTreeStorage storage;
auto edge = EDGE("r", Direction::OUT);
edge->has_range_ = true;
auto edge = EDGE_VARIABLE("r", Direction::OUT);
edge->upper_bound_ = PROPERTY_LOOKUP("n", id);
QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("r"));
// We expect to get a single column with the following rows:
@ -277,8 +268,7 @@ TEST(TestVariableStartPlanner, MatchVariableExpandBoth) {
dba->AdvanceCommand();
// Test MATCH (n {id:1}) -[r*]- (m) RETURN r
AstTreeStorage storage;
auto edge = EDGE("r", Direction::BOTH);
edge->has_range_ = true;
auto edge = EDGE_VARIABLE("r", Direction::BOTH);
auto node_n = NODE("n");
node_n->properties_[std::make_pair("id", id)] = LITERAL(1);
QUERY(MATCH(PATTERN(node_n, edge, NODE("m"))), RETURN("r"));
@ -304,11 +294,14 @@ TEST(TestVariableStartPlanner, MatchBfs) {
auto r1 = dba->InsertEdge(v1, v2, dba->EdgeType("r1"));
dba->InsertEdge(v2, v3, dba->EdgeType("r2"));
dba->AdvanceCommand();
// Test MATCH (n) -bfs[r](r, n|n.id <> 3, 10)-> (m) RETURN r
// Test MATCH (n) -[r *bfs..10](r, n | n.id <> 3)]-> (m) RETURN r
AstTreeStorage storage;
auto *bfs = storage.Create<query::BreadthFirstAtom>(
IDENT("r"), Direction::OUT, std::vector<GraphDbTypes::EdgeType>{},
IDENT("r"), IDENT("n"), NEQ(PROPERTY_LOOKUP("n", id), LITERAL(3)));
auto *bfs = storage.Create<query::EdgeAtom>(
IDENT("r"), EdgeAtom::Type::BREADTH_FIRST, Direction::OUT,
std::vector<GraphDbTypes::EdgeType>{});
bfs->inner_edge_ = IDENT("r");
bfs->inner_node_ = IDENT("n");
bfs->filter_expression_ = NEQ(PROPERTY_LOOKUP("n", id), LITERAL(3));
bfs->upper_bound_ = LITERAL(10);
QUERY(MATCH(PATTERN(NODE("n"), bfs, NODE("m"))), RETURN("r"));
// We expect to get a single column with the following rows: