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:
parent
d249ff11a5
commit
76fe8bfadf
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
||||
|
@ -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(),
|
||||
[¤t_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(),
|
||||
[¤t_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);
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
@ -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 =
|
||||
|
@ -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;
|
||||
|
@ -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))));
|
||||
|
@ -428,7 +428,6 @@ class PlanPrinter : public query::plan::HierarchicalLogicalOperatorVisitor {
|
||||
return true;
|
||||
}
|
||||
|
||||
PRE_VISIT(ExpandBreadthFirst);
|
||||
PRE_VISIT(ConstructNamedPath);
|
||||
PRE_VISIT(Filter);
|
||||
PRE_VISIT(SetProperty);
|
||||
|
@ -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 |
|
||||
|
@ -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);
|
||||
|
@ -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__})
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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};
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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>>(
|
||||
|
@ -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(),
|
||||
|
@ -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;
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user