BFS syntax changes
Summary: - The new BFS syntax implemented as proposed. - AST BreadthFirstAtom now uses EdgeAtom members: has_range_{true}, upper_bound_, lower_bound_ - Edges data structure now handles all the edge filtering (single or multiple edges), to ease planning. Additional edge filtering (additional Filter op in the plan) is removed. AST EdgeTypeTest is no longer used and is removed. Current state is stable but there are things left to do: - BFS property filtering. - BFS lower_bound_ support. - Support for lambdas in variable length expansion. This includes obligatory (even if not user_defined) inner_node and inner_edge symbols for easier handling. - Code-sharing between BFS and variable length expansions. I'll add asana tasks (and probably start working on them immediately) when/if this lands. Reviewers: buda, teon.banek, mislav.bradac Reviewed By: teon.banek Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D836
This commit is contained in:
parent
3140f175fc
commit
03c78c1277
@ -13,6 +13,7 @@
|
||||
* `counter` and `counterSet` functions added.
|
||||
* `indexInfo` function added.
|
||||
* `collect` aggregation now supports Map collection.
|
||||
* Changed the BFS syntax.
|
||||
|
||||
### Bug Fixes and Other Changes
|
||||
|
||||
@ -20,6 +21,7 @@
|
||||
* Keywords appearing in header (named expressions) keep original case.
|
||||
* Our Bolt protocol implementation is now completely compatible with the protocol version 1 specification. (https://boltprotocol.org/v1/)
|
||||
* Added a log warning when running out of memory and the `memory_warning_threshold` flag
|
||||
* Edges are no longer additionally filtered after expansion.
|
||||
|
||||
## v0.7.0
|
||||
|
||||
|
@ -410,30 +410,25 @@ 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:
|
||||
|
||||
MATCH (a {id: 723})-bfs[r:Type](e, n | true, 10)-(b {id : 882}) RETURN *
|
||||
MATCH (a {id: 723})-[r:Type \*bfs..10]-(b {id : 882}) RETURN *
|
||||
|
||||
The above query will find all paths of length up to 10 between nodes `a` and `b`.
|
||||
Just like in variable-length expansion, a single edge element in the query
|
||||
pattern denotes a possibly longer path.
|
||||
The edge type and maximum path length are used in the same way like in variable
|
||||
length expansion.
|
||||
|
||||
To find only the shortest path, simply append LIMIT 1 to the RETURN clause.
|
||||
|
||||
MATCH (a {id: 723})-bfs[r:Type](e, n | true, 10)-(b {id : 882}) RETURN * LIMIT 1
|
||||
MATCH (a {id: 723})-[r:Type \*bfs..10]-(b {id : 882}) RETURN * LIMIT 1
|
||||
|
||||
Breadth-fist expansion allows an arbitrary expression filter that determines
|
||||
if an expansion is allowed. In the above query that expression is `true`, meaning
|
||||
that all expansions are allowed. Following is an example in which expansion is
|
||||
if an expansion is allowed. Following is an example in which expansion is
|
||||
allowed only over edges whose `x` property is greater then `12` and nodes `y`
|
||||
whose property is lesser then `3`:
|
||||
|
||||
MATCH (a {id: 723})-bfs[](e, n | e.x > 12 and n.y < 3, 10)-() RETURN *
|
||||
MATCH (a {id: 723})-[\*bfs..10 (e, n | e.x > 12 and n.y < 3)]-() RETURN *
|
||||
|
||||
Notice how the filtering expression uses `e` and `n` symbols. These symbols
|
||||
don't have a fixed name, which is exactly why they have to be declared as the first
|
||||
two arguments of the breadth-first expansion.
|
||||
|
||||
The edge symbol and edge types in the square brackets can be omitted, just like
|
||||
in ordinary expansion. All other arguments (in the round brackets) are obligatory.
|
||||
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
|
||||
@ -443,8 +438,9 @@ nodes regardless of their length. Also, it is possible to simply go through a no
|
||||
neighbourhood in breadth-first manner.
|
||||
|
||||
It is fair to say there are a few drawbacks too. Currently, it isn't possible to get
|
||||
all shortest paths to a single node using Memgraph's breadth-first expansion. Also,
|
||||
the syntax is a bit unwieldy.
|
||||
all shortest paths to a single node using Memgraph's breadth-first expansion. Also
|
||||
property maps (in curly brackets) are not supported with a BFS. These features will
|
||||
most likely be included in subsequent Memgraph releases.
|
||||
|
||||
#### UNWIND
|
||||
|
||||
|
@ -16,14 +16,14 @@ namespace query {
|
||||
|
||||
#define CLONE_BINARY_EXPRESSION \
|
||||
auto Clone(AstTreeStorage &storage) const->std::remove_const< \
|
||||
std::remove_pointer<decltype(this)>::type>::type *override { \
|
||||
std::remove_pointer<decltype(this)>::type>::type * override { \
|
||||
return storage.Create< \
|
||||
std::remove_cv<std::remove_reference<decltype(*this)>::type>::type>( \
|
||||
expression1_->Clone(storage), expression2_->Clone(storage)); \
|
||||
}
|
||||
#define CLONE_UNARY_EXPRESSION \
|
||||
auto Clone(AstTreeStorage &storage) const->std::remove_const< \
|
||||
std::remove_pointer<decltype(this)>::type>::type *override { \
|
||||
std::remove_pointer<decltype(this)>::type>::type * override { \
|
||||
return storage.Create< \
|
||||
std::remove_cv<std::remove_reference<decltype(*this)>::type>::type>( \
|
||||
expression_->Clone(storage)); \
|
||||
@ -737,35 +737,6 @@ class LabelsTest : public Expression {
|
||||
: Expression(uid), expression_(expression), labels_(labels) {}
|
||||
};
|
||||
|
||||
class EdgeTypeTest : public Expression {
|
||||
friend class AstTreeStorage;
|
||||
|
||||
public:
|
||||
DEFVISITABLE(TreeVisitor<TypedValue>);
|
||||
bool Accept(HierarchicalTreeVisitor &visitor) override {
|
||||
if (visitor.PreVisit(*this)) {
|
||||
expression_->Accept(visitor);
|
||||
}
|
||||
return visitor.PostVisit(*this);
|
||||
}
|
||||
|
||||
EdgeTypeTest *Clone(AstTreeStorage &storage) const override {
|
||||
return storage.Create<EdgeTypeTest>(expression_->Clone(storage),
|
||||
edge_types_);
|
||||
}
|
||||
|
||||
Expression *expression_ = nullptr;
|
||||
std::vector<GraphDbTypes::EdgeType> edge_types_;
|
||||
|
||||
protected:
|
||||
EdgeTypeTest(int uid, Expression *expression,
|
||||
std::vector<GraphDbTypes::EdgeType> edge_types)
|
||||
: Expression(uid), expression_(expression), edge_types_(edge_types) {
|
||||
debug_assert(edge_types.size(),
|
||||
"EdgeTypeTest must have at least one edge_type");
|
||||
}
|
||||
};
|
||||
|
||||
class Function : public Expression {
|
||||
friend class AstTreeStorage;
|
||||
|
||||
@ -1053,35 +1024,44 @@ class EdgeAtom : public PatternAtom {
|
||||
};
|
||||
|
||||
class BreadthFirstAtom : public EdgeAtom {
|
||||
// TODO: Reconsider inheriting from EdgeAtom, since only `direction_` and
|
||||
// `edge_types_` are used.
|
||||
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)) {
|
||||
identifier_->Accept(visitor) &&
|
||||
traversed_edge_identifier_->Accept(visitor) &&
|
||||
next_node_identifier_->Accept(visitor) &&
|
||||
filter_expression_->Accept(visitor) && max_depth_->Accept(visitor);
|
||||
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 {
|
||||
return storage.Create<BreadthFirstAtom>(
|
||||
auto bfs_atom = storage.Create<BreadthFirstAtom>(
|
||||
identifier_->Clone(storage), direction_, edge_types_,
|
||||
traversed_edge_identifier_->Clone(storage),
|
||||
next_node_identifier_->Clone(storage),
|
||||
filter_expression_->Clone(storage), max_depth_->Clone(storage));
|
||||
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.
|
||||
Expression *filter_expression_ = nullptr;
|
||||
Expression *max_depth_ = nullptr;
|
||||
|
||||
protected:
|
||||
using EdgeAtom::EdgeAtom;
|
||||
@ -1089,12 +1069,13 @@ class BreadthFirstAtom : public EdgeAtom {
|
||||
const std::vector<GraphDbTypes::EdgeType> &edge_types,
|
||||
Identifier *traversed_edge_identifier,
|
||||
Identifier *next_node_identifier,
|
||||
Expression *filter_expression, Expression *max_depth)
|
||||
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),
|
||||
max_depth_(max_depth) {}
|
||||
filter_expression_(filter_expression) {
|
||||
has_range_ = true;
|
||||
}
|
||||
};
|
||||
|
||||
class Pattern : public Tree {
|
||||
|
@ -10,7 +10,6 @@ class NamedExpression;
|
||||
class Identifier;
|
||||
class PropertyLookup;
|
||||
class LabelsTest;
|
||||
class EdgeTypeTest;
|
||||
class Aggregation;
|
||||
class Function;
|
||||
class All;
|
||||
@ -66,10 +65,10 @@ using TreeCompositeVisitor = ::utils::CompositeVisitor<
|
||||
LessOperator, GreaterOperator, LessEqualOperator, GreaterEqualOperator,
|
||||
InListOperator, ListMapIndexingOperator, ListSlicingOperator, IfOperator,
|
||||
UnaryPlusOperator, UnaryMinusOperator, IsNullOperator, ListLiteral,
|
||||
MapLiteral, PropertyLookup, LabelsTest, EdgeTypeTest, Aggregation, Function,
|
||||
All, Create, Match, Return, With, Pattern, NodeAtom, EdgeAtom,
|
||||
BreadthFirstAtom, Delete, Where, SetProperty, SetProperties, SetLabels,
|
||||
RemoveProperty, RemoveLabels, Merge, Unwind>;
|
||||
MapLiteral, PropertyLookup, LabelsTest, Aggregation, Function, All, Create,
|
||||
Match, Return, With, Pattern, NodeAtom, EdgeAtom, BreadthFirstAtom, Delete,
|
||||
Where, SetProperty, SetProperties, SetLabels, RemoveProperty, RemoveLabels,
|
||||
Merge, Unwind>;
|
||||
|
||||
using TreeLeafVisitor = ::utils::LeafVisitor<Identifier, PrimitiveLiteral,
|
||||
ParameterLookup, CreateIndex>;
|
||||
@ -91,10 +90,10 @@ using TreeVisitor = ::utils::Visitor<
|
||||
LessOperator, GreaterOperator, LessEqualOperator, GreaterEqualOperator,
|
||||
InListOperator, ListMapIndexingOperator, ListSlicingOperator, IfOperator,
|
||||
UnaryPlusOperator, UnaryMinusOperator, IsNullOperator, ListLiteral,
|
||||
MapLiteral, PropertyLookup, LabelsTest, EdgeTypeTest, Aggregation, Function,
|
||||
All, ParameterLookup, Create, Match, Return, With, Pattern, NodeAtom,
|
||||
EdgeAtom, BreadthFirstAtom, Delete, Where, SetProperty, SetProperties,
|
||||
SetLabels, RemoveProperty, RemoveLabels, Merge, Unwind, Identifier,
|
||||
PrimitiveLiteral, CreateIndex>;
|
||||
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>;
|
||||
|
||||
} // namespace query
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@ -417,65 +418,21 @@ antlrcpp::Any CypherMainVisitor::visitPatternElementChain(
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitRelationshipPattern(
|
||||
CypherParser::RelationshipPatternContext *ctx) {
|
||||
auto *edge = ctx->bfsDetail() ? storage_.Create<BreadthFirstAtom>()
|
||||
: storage_.Create<EdgeAtom>();
|
||||
if (ctx->bfsDetail()) {
|
||||
if (ctx->bfsDetail()->bfs_variable) {
|
||||
std::string variable = ctx->bfsDetail()->bfs_variable->accept(this);
|
||||
edge->identifier_ = storage_.Create<Identifier>(variable);
|
||||
users_identifiers.insert(variable);
|
||||
}
|
||||
auto *bf_atom = dynamic_cast<BreadthFirstAtom *>(edge);
|
||||
std::string traversed_edge_variable =
|
||||
ctx->bfsDetail()->traversed_edge->accept(this);
|
||||
if (ctx->bfsDetail()->relationshipTypes()) {
|
||||
bf_atom->edge_types_ = ctx->bfsDetail()
|
||||
->relationshipTypes()
|
||||
->accept(this)
|
||||
.as<std::vector<GraphDbTypes::EdgeType>>();
|
||||
}
|
||||
bf_atom->traversed_edge_identifier_ =
|
||||
storage_.Create<Identifier>(traversed_edge_variable);
|
||||
std::string next_node_variable = ctx->bfsDetail()->next_node->accept(this);
|
||||
bf_atom->next_node_identifier_ =
|
||||
storage_.Create<Identifier>(next_node_variable);
|
||||
bf_atom->filter_expression_ =
|
||||
ctx->bfsDetail()->expression()[0]->accept(this);
|
||||
bf_atom->max_depth_ = ctx->bfsDetail()->expression()[1]->accept(this);
|
||||
} else if (ctx->relationshipDetail()) {
|
||||
if (ctx->relationshipDetail()->variable()) {
|
||||
std::string variable =
|
||||
ctx->relationshipDetail()->variable()->accept(this);
|
||||
edge->identifier_ = storage_.Create<Identifier>(variable);
|
||||
users_identifiers.insert(variable);
|
||||
}
|
||||
if (ctx->relationshipDetail()->relationshipTypes()) {
|
||||
edge->edge_types_ = ctx->relationshipDetail()
|
||||
->relationshipTypes()
|
||||
->accept(this)
|
||||
.as<std::vector<GraphDbTypes::EdgeType>>();
|
||||
}
|
||||
if (ctx->relationshipDetail()->properties()) {
|
||||
edge->properties_ =
|
||||
ctx->relationshipDetail()
|
||||
->properties()
|
||||
->accept(this)
|
||||
.as<std::map<std::pair<std::string, GraphDbTypes::Property>,
|
||||
Expression *>>();
|
||||
}
|
||||
if (ctx->relationshipDetail()->rangeLiteral()) {
|
||||
edge->has_range_ = true;
|
||||
auto range = ctx->relationshipDetail()
|
||||
->rangeLiteral()
|
||||
->accept(this)
|
||||
.as<std::pair<Expression *, Expression *>>();
|
||||
edge->lower_bound_ = range.first;
|
||||
edge->upper_bound_ = range.second;
|
||||
}
|
||||
}
|
||||
if (!edge->identifier_) {
|
||||
anonymous_identifiers.push_back(&edge->identifier_);
|
||||
}
|
||||
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) =
|
||||
variableExpansion->accept(this)
|
||||
.as<std::tuple<bool, Expression *, Expression *>>();
|
||||
|
||||
auto *edge = (variableExpansion && is_bfs)
|
||||
? storage_.Create<BreadthFirstAtom>()
|
||||
: storage_.Create<EdgeAtom>();
|
||||
|
||||
if (ctx->leftArrowHead() && !ctx->rightArrowHead()) {
|
||||
edge->direction_ = EdgeAtom::Direction::IN;
|
||||
@ -486,6 +443,80 @@ antlrcpp::Any CypherMainVisitor::visitRelationshipPattern(
|
||||
// grammar.
|
||||
edge->direction_ = EdgeAtom::Direction::BOTH;
|
||||
}
|
||||
|
||||
if (!relationshipDetail) {
|
||||
anonymous_identifiers.push_back(&edge->identifier_);
|
||||
return edge;
|
||||
}
|
||||
|
||||
if (relationshipDetail->variable()) {
|
||||
std::string variable = relationshipDetail->variable()->accept(this);
|
||||
edge->identifier_ = storage_.Create<Identifier>(variable);
|
||||
users_identifiers.insert(variable);
|
||||
} else {
|
||||
anonymous_identifiers.push_back(&edge->identifier_);
|
||||
}
|
||||
|
||||
if (relationshipDetail->relationshipTypes()) {
|
||||
edge->edge_types_ = ctx->relationshipDetail()
|
||||
->relationshipTypes()
|
||||
->accept(this)
|
||||
.as<std::vector<GraphDbTypes::EdgeType>>();
|
||||
}
|
||||
|
||||
auto relationshipLambdas = relationshipDetail->relationshipLambda();
|
||||
if (variableExpansion) {
|
||||
edge->has_range_ = true;
|
||||
edge->lower_bound_ = lower;
|
||||
edge->upper_bound_ = upper;
|
||||
|
||||
switch (relationshipLambdas.size()) {
|
||||
case 0:
|
||||
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_ =
|
||||
storage_.Create<Identifier>(traversed_edge_variable);
|
||||
std::string traversed_node_variable =
|
||||
lambda->traversed_node->accept(this);
|
||||
edge_bfs->next_node_identifier_ =
|
||||
storage_.Create<Identifier>(traversed_node_variable);
|
||||
edge_bfs->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");
|
||||
}
|
||||
|
||||
auto properties = relationshipDetail->properties();
|
||||
switch (properties.size()) {
|
||||
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)
|
||||
.as<std::map<std::pair<std::string, GraphDbTypes::Property>,
|
||||
Expression *>>();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw SemanticException("Only one property map supported in edge");
|
||||
}
|
||||
|
||||
return edge;
|
||||
}
|
||||
|
||||
@ -495,8 +526,8 @@ antlrcpp::Any CypherMainVisitor::visitRelationshipDetail(
|
||||
return 0;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitBfsDetail(
|
||||
CypherParser::BfsDetailContext *) {
|
||||
antlrcpp::Any CypherMainVisitor::visitRelationshipLambda(
|
||||
CypherParser::RelationshipLambdaContext *) {
|
||||
debug_assert(false, "Should never be called. See documentation in hpp.");
|
||||
return 0;
|
||||
}
|
||||
@ -510,34 +541,41 @@ antlrcpp::Any CypherMainVisitor::visitRelationshipTypes(
|
||||
return types;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitRangeLiteral(
|
||||
CypherParser::RangeLiteralContext *ctx) {
|
||||
antlrcpp::Any CypherMainVisitor::visitVariableExpansion(
|
||||
CypherParser::VariableExpansionContext *ctx) {
|
||||
debug_assert(ctx->expression().size() <= 2U,
|
||||
"Expected 0, 1 or 2 bounds in range literal.");
|
||||
|
||||
bool is_bfs = !ctx->getTokens(CypherParser::BFS).empty();
|
||||
Expression *lower = nullptr;
|
||||
Expression *upper = nullptr;
|
||||
|
||||
if (ctx->expression().size() == 0U) {
|
||||
// Case -[*]-
|
||||
return std::pair<Expression *, Expression *>(nullptr, nullptr);
|
||||
} else if (ctx->expression().size() == 1U) {
|
||||
auto dots_tokens = ctx->getTokens(kDotsTokenId);
|
||||
Expression *bound = ctx->expression()[0]->accept(this);
|
||||
if (!dots_tokens.size()) {
|
||||
// Case -[*bound]-
|
||||
return std::pair<Expression *, Expression *>(bound, bound);
|
||||
}
|
||||
if (dots_tokens[0]->getSourceInterval().startsAfter(
|
||||
ctx->expression()[0]->getSourceInterval())) {
|
||||
lower = bound;
|
||||
upper = bound;
|
||||
} else if (dots_tokens[0]->getSourceInterval().startsAfter(
|
||||
ctx->expression()[0]->getSourceInterval())) {
|
||||
// Case -[*bound..]-
|
||||
return std::pair<Expression *, Expression *>(bound, nullptr);
|
||||
lower = bound;
|
||||
} else {
|
||||
// Case -[*..bound]-
|
||||
return std::pair<Expression *, Expression *>(nullptr, bound);
|
||||
upper = bound;
|
||||
}
|
||||
} else {
|
||||
// Case -[*lbound..rbound]-
|
||||
Expression *lbound = ctx->expression()[0]->accept(this);
|
||||
Expression *rbound = ctx->expression()[1]->accept(this);
|
||||
return std::pair<Expression *, Expression *>(lbound, rbound);
|
||||
lower = ctx->expression()[0]->accept(this);
|
||||
upper = ctx->expression()[1]->accept(this);
|
||||
}
|
||||
|
||||
if (is_bfs && lower)
|
||||
throw SemanticException("BFS does not support lower bounds");
|
||||
return std::make_tuple(is_bfs, lower, upper);
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitExpression(
|
||||
|
@ -272,7 +272,8 @@ class CypherMainVisitor : public antlropencypher::CypherBaseVisitor {
|
||||
* This should never be called. Everything is done directly in
|
||||
* visitRelationshipPattern.
|
||||
*/
|
||||
antlrcpp::Any visitBfsDetail(CypherParser::BfsDetailContext *ctx) override;
|
||||
antlrcpp::Any visitRelationshipLambda(
|
||||
CypherParser::RelationshipLambdaContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return vector<GraphDbTypes::EdgeType>
|
||||
@ -281,10 +282,10 @@ class CypherMainVisitor : public antlropencypher::CypherBaseVisitor {
|
||||
CypherParser::RelationshipTypesContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return pair<int64_t, int64_t>.
|
||||
* @return std::tuple<bool, int64_t, int64_t>.
|
||||
*/
|
||||
antlrcpp::Any visitRangeLiteral(
|
||||
CypherParser::RangeLiteralContext *ctx) override;
|
||||
antlrcpp::Any visitVariableExpansion(
|
||||
CypherParser::VariableExpansionContext *ctx) override;
|
||||
|
||||
/**
|
||||
* Top level expression, does nothing.
|
||||
|
@ -8,7 +8,7 @@ using antlropencypher::CypherParser;
|
||||
// grammar change since even changes in ordering of rules will cause antlr to
|
||||
// generate different constants for unnamed tokens.
|
||||
const auto kReturnAllTokenId = CypherParser::T__4; // *
|
||||
const auto kDotsTokenId = CypherParser::T__11; // ..
|
||||
const auto kDotsTokenId = CypherParser::T__10; // ..
|
||||
const auto kEqTokenId = CypherParser::T__2; // =
|
||||
const auto kNeTokenId1 = CypherParser::T__18; // <>
|
||||
const auto kNeTokenId2 = CypherParser::T__19; // !=
|
||||
|
@ -117,17 +117,19 @@ nodePattern : '(' SP? ( variable SP? )? ( nodeLabels SP? )? ( properties SP? )?
|
||||
|
||||
patternElementChain : relationshipPattern SP? nodePattern ;
|
||||
|
||||
relationshipPattern : ( leftArrowHead SP? dash SP? ( bfsDetail | relationshipDetail )? SP? dash SP? rightArrowHead )
|
||||
| ( leftArrowHead SP? dash SP? ( bfsDetail | relationshipDetail )? SP? dash )
|
||||
| ( dash SP? ( bfsDetail | relationshipDetail )? SP? dash SP? rightArrowHead )
|
||||
| ( dash SP? ( bfsDetail | relationshipDetail )? SP? dash )
|
||||
relationshipPattern : ( leftArrowHead SP? dash SP? ( relationshipDetail )? SP? dash SP? rightArrowHead )
|
||||
| ( leftArrowHead SP? dash SP? ( relationshipDetail )? SP? dash )
|
||||
| ( dash SP? ( relationshipDetail )? SP? dash SP? rightArrowHead )
|
||||
| ( dash SP? ( relationshipDetail )? SP? dash )
|
||||
;
|
||||
|
||||
bfsDetail : BFS SP? ( '[' SP? ( bfs_variable=variable SP? )? ( relationshipTypes SP? )? SP? ']' )? SP? '(' SP? traversed_edge=variable SP? ',' SP? next_node=variable SP? '|' SP? expression SP? ',' SP? expression SP? ')' ;
|
||||
relationshipDetail : '[' SP? ( variable SP? )? ( relationshipTypes SP? )? ( variableExpansion SP? )? properties SP? ']'
|
||||
| '[' SP? ( variable SP? )? ( relationshipTypes SP? )? ( variableExpansion SP? )? relationshipLambda SP? ']'
|
||||
| '[' SP? ( variable SP? )? ( relationshipTypes SP? )? ( variableExpansion SP? )? ( (properties SP?) | (relationshipLambda SP?) )* ']';
|
||||
|
||||
relationshipDetail : '[' SP? ( variable SP? )? ( relationshipTypes SP? )? ( rangeLiteral SP? )? properties SP? ']'
|
||||
| '[' SP? ( variable SP? )? ( relationshipTypes SP? )? ( rangeLiteral SP? )? ( properties SP? )? ']'
|
||||
;
|
||||
relationshipLambda: '(' SP? traversed_edge=variable SP? ',' SP? traversed_node=variable SP? '|' SP? expression SP? ')';
|
||||
|
||||
variableExpansion : '*' SP? (BFS)? SP? ( expression SP? )? ( '..' ( SP? expression )? )? ;
|
||||
|
||||
properties : mapLiteral
|
||||
| parameter
|
||||
@ -139,8 +141,6 @@ nodeLabels : nodeLabel ( SP? nodeLabel )* ;
|
||||
|
||||
nodeLabel : ':' SP? labelName ;
|
||||
|
||||
rangeLiteral : '*' SP? ( expression SP? )? ( '..' ( SP? expression )? )? ;
|
||||
|
||||
labelName : symbolicName ;
|
||||
|
||||
relTypeName : symbolicName ;
|
||||
|
@ -196,34 +196,25 @@ SymbolGenerator::ReturnType SymbolGenerator::Visit(Identifier &ident) {
|
||||
symbol = GetOrCreateSymbol(ident.name_, ident.user_declared_,
|
||||
Symbol::Type::Path);
|
||||
} else if (scope_.in_pattern && scope_.in_pattern_atom_identifier) {
|
||||
// Patterns can bind new symbols or reference already bound. But there
|
||||
// are the following special cases:
|
||||
// 1) Patterns used to create nodes and edges cannot redeclare already
|
||||
// established bindings. Declaration only happens in single node
|
||||
// patterns and in edge patterns. OpenCypher example,
|
||||
// `MATCH (n) CREATE (n)` should throw an error that `n` is already
|
||||
// declared. While `MATCH (n) CREATE (n) -[:R]-> (n)` is allowed,
|
||||
// since `n` now references the bound node instead of declaring it.
|
||||
// Additionally, we will support edge referencing in pattern:
|
||||
// `MATCH (n) - [r] -> (n) - [r] -> (n) RETURN r`, which would
|
||||
// usually raise redeclaration of `r`.
|
||||
// Patterns used to create nodes and edges cannot redeclare already
|
||||
// established bindings. Declaration only happens in single node
|
||||
// patterns and in edge patterns. OpenCypher example,
|
||||
// `MATCH (n) CREATE (n)` should throw an error that `n` is already
|
||||
// declared. While `MATCH (n) CREATE (n) -[:R]-> (n)` is allowed,
|
||||
// since `n` now references the bound node instead of declaring it.
|
||||
if ((scope_.in_create_node || scope_.in_create_edge) &&
|
||||
HasSymbol(ident.name_)) {
|
||||
// Case 1)
|
||||
throw RedeclareVariableError(ident.name_);
|
||||
}
|
||||
auto type = Symbol::Type::Vertex;
|
||||
if (scope_.visiting_edge) {
|
||||
if (scope_.visiting_edge->has_range_ && HasSymbol(ident.name_)) {
|
||||
// TOOD: Support using variable paths with already obtained results from
|
||||
// an existing symbol.
|
||||
// Edge referencing is not allowed (like in Neo4j):
|
||||
// `MATCH (n) - [r] -> (n) - [r] -> (n) RETURN r` is not allowed.
|
||||
if (HasSymbol(ident.name_)) {
|
||||
throw RedeclareVariableError(ident.name_);
|
||||
}
|
||||
if (scope_.visiting_edge->has_range_) {
|
||||
type = Symbol::Type::EdgeList;
|
||||
} else {
|
||||
type = Symbol::Type::Edge;
|
||||
}
|
||||
type = scope_.visiting_edge->has_range_ ? Symbol::Type::EdgeList
|
||||
: Symbol::Type::Edge;
|
||||
}
|
||||
symbol = GetOrCreateSymbol(ident.name_, ident.user_declared_, type);
|
||||
} else if (scope_.in_pattern && !scope_.in_pattern_atom_identifier &&
|
||||
@ -424,19 +415,20 @@ bool SymbolGenerator::PreVisit(BreadthFirstAtom &bf_atom) {
|
||||
if (scope_.in_create || scope_.in_merge) {
|
||||
throw SemanticException("BFS cannot be used to create edges.");
|
||||
}
|
||||
// Visiting BFS filter and max_depth expressions is not a pattern.
|
||||
// Visiting BFS filter and upper bound expressions is not a pattern.
|
||||
scope_.in_pattern = false;
|
||||
bf_atom.max_depth_->Accept(*this);
|
||||
VisitWithIdentifiers(
|
||||
*bf_atom.filter_expression_,
|
||||
{bf_atom.traversed_edge_identifier_, bf_atom.next_node_identifier_});
|
||||
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;
|
||||
// XXX: Make BFS symbol be EdgeList.
|
||||
bf_atom.has_range_ = true;
|
||||
scope_.in_pattern_atom_identifier = true;
|
||||
bf_atom.identifier_->Accept(*this);
|
||||
scope_.in_pattern_atom_identifier = false;
|
||||
bf_atom.has_range_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -305,26 +305,6 @@ class ExpressionEvaluator : public TreeVisitor<TypedValue> {
|
||||
}
|
||||
}
|
||||
|
||||
TypedValue Visit(EdgeTypeTest &edge_type_test) override {
|
||||
auto expression_result = edge_type_test.expression_->Accept(*this);
|
||||
switch (expression_result.type()) {
|
||||
case TypedValue::Type::Null:
|
||||
return TypedValue::Null;
|
||||
case TypedValue::Type::Edge: {
|
||||
auto real_edge_type =
|
||||
expression_result.Value<EdgeAccessor>().EdgeType();
|
||||
for (const auto edge_type : edge_type_test.edge_types_) {
|
||||
if (edge_type == real_edge_type) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
throw QueryRuntimeException("Expected Edge in edge type test");
|
||||
}
|
||||
}
|
||||
|
||||
TypedValue Visit(PrimitiveLiteral &literal) override {
|
||||
// TODO: no need to evaluate constants, we can write it to frame in one
|
||||
// of the previous phases.
|
||||
|
@ -536,10 +536,10 @@ bool Expand::ExpandCursor::InitEdges(Frame &frame, Context &context) {
|
||||
ExpectType(self_.node_symbol_, existing_node,
|
||||
TypedValue::Type::Vertex);
|
||||
in_edges_.emplace(
|
||||
vertex.in_with_destination(existing_node.ValueVertex()));
|
||||
vertex.in(existing_node.ValueVertex(), &self_.edge_types()));
|
||||
}
|
||||
} else {
|
||||
in_edges_.emplace(vertex.in_with_types(&self_.edge_types()));
|
||||
in_edges_.emplace(vertex.in(&self_.edge_types()));
|
||||
}
|
||||
in_edges_it_.emplace(in_edges_->begin());
|
||||
}
|
||||
@ -553,10 +553,10 @@ bool Expand::ExpandCursor::InitEdges(Frame &frame, Context &context) {
|
||||
ExpectType(self_.node_symbol_, existing_node,
|
||||
TypedValue::Type::Vertex);
|
||||
out_edges_.emplace(
|
||||
vertex.out_with_destination(existing_node.ValueVertex()));
|
||||
vertex.out(existing_node.ValueVertex(), &self_.edge_types()));
|
||||
}
|
||||
} else {
|
||||
out_edges_.emplace(vertex.out_with_types(&self_.edge_types()));
|
||||
out_edges_.emplace(vertex.out(&self_.edge_types()));
|
||||
}
|
||||
out_edges_it_.emplace(out_edges_->begin());
|
||||
}
|
||||
@ -608,14 +608,14 @@ auto ExpandFromVertex(const VertexAccessor &vertex,
|
||||
std::vector<decltype(wrapper(direction, vertex.in()))> chain_elements;
|
||||
|
||||
if (direction != EdgeAtom::Direction::OUT && vertex.in_degree() > 0) {
|
||||
auto edges = vertex.in_with_types(&edge_types);
|
||||
auto edges = vertex.in(&edge_types);
|
||||
if (edges.begin() != edges.end()) {
|
||||
chain_elements.emplace_back(
|
||||
wrapper(EdgeAtom::Direction::IN, std::move(edges)));
|
||||
}
|
||||
}
|
||||
if (direction != EdgeAtom::Direction::IN && vertex.out_degree() > 0) {
|
||||
auto edges = vertex.out_with_types(&edge_types);
|
||||
auto edges = vertex.out(&edge_types);
|
||||
if (edges.begin() != edges.end()) {
|
||||
chain_elements.emplace_back(
|
||||
wrapper(EdgeAtom::Direction::OUT, std::move(edges)));
|
||||
@ -939,9 +939,11 @@ std::unique_ptr<Cursor> ExpandVariable::MakeCursor(GraphDbAccessor &db) const {
|
||||
ExpandBreadthFirst::ExpandBreadthFirst(
|
||||
Symbol node_symbol, Symbol edge_list_symbol, EdgeAtom::Direction direction,
|
||||
const std::vector<GraphDbTypes::EdgeType> &edge_types,
|
||||
Expression *max_depth, Symbol inner_node_symbol, Symbol inner_edge_symbol,
|
||||
Expression *where, const std::shared_ptr<LogicalOperator> &input,
|
||||
Symbol input_symbol, bool existing_node, GraphView graph_view)
|
||||
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),
|
||||
@ -981,22 +983,23 @@ bool ExpandBreadthFirst::Cursor::Pull(Frame &frame, Context &context) {
|
||||
SwitchAccessor(edge, self_.graph_view_);
|
||||
SwitchAccessor(vertex, self_.graph_view_);
|
||||
|
||||
// to evaluate the where expression we need the inner
|
||||
// values on the frame
|
||||
frame[self_.inner_edge_symbol_] = edge;
|
||||
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");
|
||||
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);
|
||||
};
|
||||
@ -1006,11 +1009,11 @@ bool ExpandBreadthFirst::Cursor::Pull(Frame &frame, Context &context) {
|
||||
// the "where" condition.
|
||||
auto expand_from_vertex = [this, &expand_pair](VertexAccessor &vertex) {
|
||||
if (self_.direction_ != EdgeAtom::Direction::IN) {
|
||||
for (const EdgeAccessor &edge : vertex.out_with_types(&self_.edge_types_))
|
||||
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_with_types(&self_.edge_types_))
|
||||
for (const EdgeAccessor &edge : vertex.in(&self_.edge_types_))
|
||||
expand_pair(edge, edge.from());
|
||||
}
|
||||
};
|
||||
@ -1032,8 +1035,10 @@ bool ExpandBreadthFirst::Cursor::Pull(Frame &frame, Context &context) {
|
||||
SwitchAccessor(vertex, self_.graph_view_);
|
||||
processed_.emplace(vertex, std::experimental::nullopt);
|
||||
expand_from_vertex(vertex);
|
||||
max_depth_ = EvaluateInt(evaluator, self_.max_depth_,
|
||||
"Max depth in breadth-first expansion");
|
||||
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");
|
||||
|
@ -672,8 +672,10 @@ class ExpandBreadthFirst : public LogicalOperator {
|
||||
ExpandBreadthFirst(Symbol node_symbol, Symbol edge_list_symbol,
|
||||
EdgeAtom::Direction direction,
|
||||
const std::vector<GraphDbTypes::EdgeType> &edge_types,
|
||||
Expression *max_depth, Symbol inner_node_symbol,
|
||||
Symbol inner_edge_symbol, Expression *where,
|
||||
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);
|
||||
@ -717,8 +719,8 @@ class ExpandBreadthFirst : public LogicalOperator {
|
||||
Expression *max_depth_;
|
||||
|
||||
// symbols for a single node and edge that are currently getting expanded
|
||||
const Symbol inner_node_symbol_;
|
||||
const Symbol inner_edge_symbol_;
|
||||
const std::experimental::optional<Symbol> inner_node_symbol_;
|
||||
const std::experimental::optional<Symbol> inner_edge_symbol_;
|
||||
// a filtering expression for skipping expansions during expansion
|
||||
// can refer to inner node and edges
|
||||
Expression *where_;
|
||||
|
@ -530,13 +530,16 @@ std::vector<Expansion> NormalizePatterns(
|
||||
}
|
||||
if (auto *bf_atom = dynamic_cast<BreadthFirstAtom *>(edge)) {
|
||||
// Get used symbols inside bfs filter expression and max depth.
|
||||
bf_atom->filter_expression_->Accept(collector);
|
||||
bf_atom->max_depth_->Accept(collector);
|
||||
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.
|
||||
collector.symbols_.erase(
|
||||
symbol_table.at(*bf_atom->traversed_edge_identifier_));
|
||||
collector.symbols_.erase(
|
||||
symbol_table.at(*bf_atom->next_node_identifier_));
|
||||
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_));
|
||||
}
|
||||
}
|
||||
expansions.emplace_back(Expansion{prev_node, edge, edge->direction_, false,
|
||||
collector.symbols_, current_node});
|
||||
@ -974,35 +977,6 @@ void Filters::CollectPatternFilters(Pattern &pattern, SymbolTable &symbol_table,
|
||||
add_properties_filter(node);
|
||||
};
|
||||
auto add_expand_filter = [&](NodeAtom *, EdgeAtom *edge, NodeAtom *node) {
|
||||
const auto &edge_symbol = symbol_table.at(*edge->identifier_);
|
||||
if (!edge->edge_types_.empty()) {
|
||||
edge_type_filters_[edge_symbol].insert(edge->edge_types_.begin(),
|
||||
edge->edge_types_.end());
|
||||
if (edge->has_range_) {
|
||||
// We need a new identifier and symbol for All.
|
||||
auto *ident_in_all = edge->identifier_->Clone(storage);
|
||||
symbol_table[*ident_in_all] =
|
||||
symbol_table.CreateSymbol(ident_in_all->name_, false);
|
||||
auto *edge_type_test =
|
||||
storage.Create<EdgeTypeTest>(ident_in_all, edge->edge_types_);
|
||||
all_filters_.emplace_back(FilterInfo{
|
||||
storage.Create<All>(ident_in_all, edge->identifier_,
|
||||
storage.Create<Where>(edge_type_test)),
|
||||
std::unordered_set<Symbol>{edge_symbol}, true});
|
||||
} else if (auto *bf_atom = dynamic_cast<BreadthFirstAtom *>(edge)) {
|
||||
// BFS filters will be inlined inside the filter expression, so create
|
||||
// EdgeTypeTest which relies on traversed edge identifier. Set of
|
||||
// used symbols treats this as the original edge symbol.
|
||||
all_filters_.emplace_back(FilterInfo{
|
||||
storage.Create<EdgeTypeTest>(bf_atom->traversed_edge_identifier_,
|
||||
bf_atom->edge_types_),
|
||||
std::unordered_set<Symbol>{edge_symbol}, true});
|
||||
} else {
|
||||
all_filters_.emplace_back(FilterInfo{
|
||||
storage.Create<EdgeTypeTest>(edge->identifier_, edge->edge_types_),
|
||||
std::unordered_set<Symbol>{edge_symbol}});
|
||||
}
|
||||
}
|
||||
add_properties_filter(edge, edge->has_range_);
|
||||
add_node_filter(node);
|
||||
};
|
||||
|
@ -1,6 +1,8 @@
|
||||
/// @file
|
||||
#pragma once
|
||||
|
||||
#include <experimental/optional>
|
||||
|
||||
#include "gflags/gflags.h"
|
||||
|
||||
#include "query/frontend/ast/ast.hpp"
|
||||
@ -533,17 +535,14 @@ class RuleBasedPlanner {
|
||||
} else {
|
||||
match_context.new_symbols.emplace_back(edge_symbol);
|
||||
}
|
||||
const auto edge_types_set =
|
||||
utils::FindOr(matching.filters.edge_type_filters(), edge_symbol,
|
||||
std::unordered_set<GraphDbTypes::EdgeType>())
|
||||
.first;
|
||||
const std::vector<GraphDbTypes::EdgeType> edge_types(
|
||||
edge_types_set.begin(), edge_types_set.end());
|
||||
if (auto *bf_atom = dynamic_cast<BreadthFirstAtom *>(expansion.edge)) {
|
||||
const auto &traversed_edge_symbol =
|
||||
symbol_table.at(*bf_atom->traversed_edge_identifier_);
|
||||
const auto &next_node_symbol =
|
||||
symbol_table.at(*bf_atom->next_node_identifier_);
|
||||
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 *filter_expr = impl::BoolJoin<AndOperator>(
|
||||
storage, impl::ExtractMultiExpandFilter(
|
||||
@ -551,8 +550,8 @@ class RuleBasedPlanner {
|
||||
bf_atom->filter_expression_);
|
||||
last_op = new ExpandBreadthFirst(
|
||||
node_symbol, edge_symbol, expansion.direction,
|
||||
std::move(edge_types), bf_atom->max_depth_, next_node_symbol,
|
||||
traversed_edge_symbol, filter_expr,
|
||||
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_) {
|
||||
@ -560,7 +559,7 @@ class RuleBasedPlanner {
|
||||
bound_symbols, node_symbol, all_filters, storage);
|
||||
last_op = new ExpandVariable(
|
||||
node_symbol, edge_symbol, expansion.direction,
|
||||
std::move(edge_types), expansion.is_flipped,
|
||||
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,
|
||||
@ -583,7 +582,7 @@ class RuleBasedPlanner {
|
||||
}
|
||||
}
|
||||
last_op = new Expand(node_symbol, edge_symbol, expansion.direction,
|
||||
std::move(edge_types),
|
||||
expansion.edge->edge_types_,
|
||||
std::shared_ptr<LogicalOperator>(last_op),
|
||||
node1_symbol, existing_node, existing_edge,
|
||||
match_context.graph_view);
|
||||
|
@ -44,25 +44,18 @@ class Edges {
|
||||
*
|
||||
* @param iterator - Iterator in the underlying storage.
|
||||
* @param end - End iterator in the underlying storage.
|
||||
* @param vertex - The destination vertex vlist pointer.
|
||||
*/
|
||||
Iterator(std::vector<Element>::const_iterator position,
|
||||
std::vector<Element>::const_iterator end, vertex_ptr_t vertex)
|
||||
: position_(position), end_(end), vertex_(vertex) {
|
||||
update_position();
|
||||
}
|
||||
|
||||
/** Ctor used for creating the beginning iterator with known edge types.
|
||||
*
|
||||
* @param iterator - Iterator in the underlying storage.
|
||||
* @param end - End iterator in the underlying storage.
|
||||
* @param vertex - The destination vertex vlist pointer. If nullptr the
|
||||
* edges are not filtered on destination.
|
||||
* @param edge_types - The edge types at least one of which must be matched.
|
||||
* If nullptr all edges are valid.
|
||||
* If nullptr edges are not filtered on type.
|
||||
*/
|
||||
Iterator(std::vector<Element>::const_iterator position,
|
||||
std::vector<Element>::const_iterator end,
|
||||
std::vector<Element>::const_iterator end, vertex_ptr_t vertex,
|
||||
const std::vector<GraphDbTypes::EdgeType> *edge_types)
|
||||
: position_(position), end_(end), edge_types_(edge_types) {
|
||||
: position_(position),
|
||||
end_(end),
|
||||
vertex_(vertex),
|
||||
edge_types_(edge_types) {
|
||||
update_position();
|
||||
}
|
||||
|
||||
@ -85,7 +78,8 @@ class Edges {
|
||||
// end_ is used only in update_position() to limit find.
|
||||
std::vector<Element>::const_iterator end_;
|
||||
|
||||
// Optional predicates. If set they define which edges are skipped by the
|
||||
// Optional predicates. If set they define which edges are skipped by
|
||||
// the
|
||||
// iterator. Only one can be not-null in the current implementation.
|
||||
vertex_ptr_t vertex_{nullptr};
|
||||
// For edge types we use a vector pointer because it's optional.
|
||||
@ -94,14 +88,16 @@ class Edges {
|
||||
/** Helper function that skips edges that don't satisfy the predicate
|
||||
* present in this iterator. */
|
||||
void update_position() {
|
||||
if (vertex_)
|
||||
if (vertex_) {
|
||||
position_ = std::find_if(
|
||||
position_, end_,
|
||||
[v = this->vertex_](const Element &e) { return e.vertex == v; });
|
||||
else if (edge_types_)
|
||||
}
|
||||
if (edge_types_) {
|
||||
position_ = std::find_if(position_, end_, [this](const Element &e) {
|
||||
return utils::Contains(*edge_types_, e.edge_type);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -109,8 +105,8 @@ class Edges {
|
||||
/**
|
||||
* Adds an edge to this structure.
|
||||
*
|
||||
* @param vertex - The destination vertex of the edge. That's the one opposite
|
||||
* from the vertex that contains this `Edges` instance.
|
||||
* @param vertex - The destination vertex of the edge. That's the one
|
||||
* opposite from the vertex that contains this `Edges` instance.
|
||||
* @param edge - The edge.
|
||||
* @param edge_type - Type of the edge.
|
||||
*/
|
||||
@ -137,24 +133,18 @@ class Edges {
|
||||
auto end() const { return Iterator(storage_.end()); }
|
||||
|
||||
/**
|
||||
* Creates a beginning iterator that will skip edges whose destination vertex
|
||||
* is not equal to the given vertex.
|
||||
* Creates a beginning iterator that will skip edges whose destination
|
||||
* vertex is not equal to the given vertex.
|
||||
*
|
||||
* @param vertex - The destination vertex vlist pointer. If nullptr the
|
||||
* edges are not filtered on destination.
|
||||
* @param edge_types - The edge types at least one of which must be matched.
|
||||
* If nullptr edges are not filtered on type.
|
||||
*/
|
||||
auto begin(vertex_ptr_t vertex) const {
|
||||
return Iterator(storage_.begin(), storage_.end(), vertex);
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates a beginning iterator that will skip edges whose edge type is not
|
||||
* among the given. If none are given, or the pointer is null, then all edges
|
||||
* are valid. Relies on the fact that edge types are immutable during the
|
||||
* whole edge lifetime.
|
||||
*/
|
||||
auto begin(const std::vector<GraphDbTypes::EdgeType> *edge_types) const {
|
||||
if (edge_types && !edge_types->empty())
|
||||
return Iterator(storage_.begin(), storage_.end(), edge_types);
|
||||
else
|
||||
return Iterator(storage_.begin());
|
||||
auto begin(vertex_ptr_t vertex,
|
||||
const std::vector<GraphDbTypes::EdgeType> *edge_types) const {
|
||||
if (edge_types && edge_types->empty()) edge_types = nullptr;
|
||||
return Iterator(storage_.begin(), storage_.end(), vertex, edge_types);
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -73,23 +73,30 @@ class VertexAccessor : public RecordAccessor<Vertex> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns EdgeAccessors for all incoming edges whose destination is the given
|
||||
* vertex.
|
||||
* Returns EdgeAccessors for all incoming edges.
|
||||
*
|
||||
* @param dest - The destination vertex filter.
|
||||
* @param edge_types - Edge types filter. At least one be matched. If nullptr
|
||||
* or empty, the parameter is ignored.
|
||||
*/
|
||||
auto in_with_destination(const VertexAccessor &dest) const {
|
||||
auto in(
|
||||
const VertexAccessor &dest,
|
||||
const std::vector<GraphDbTypes::EdgeType> *edge_types = nullptr) const {
|
||||
return MakeAccessorIterator<EdgeAccessor>(
|
||||
current().in_.begin(dest.vlist_), current().in_.end(), db_accessor());
|
||||
current().in_.begin(dest.vlist_, edge_types), current().in_.end(),
|
||||
db_accessor());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns EdgeAccessors for all incoming edges whose type is one of the
|
||||
* given. If the given collection of types is nullptr or empty, all edge types
|
||||
* are valid.
|
||||
* Returns EdgeAccessors for all incoming edges.
|
||||
*
|
||||
* @param edge_types - Edge types filter. At least one be matched. If nullptr
|
||||
* or empty, the parameter is ignored.
|
||||
*/
|
||||
auto in_with_types(
|
||||
const std::vector<GraphDbTypes::EdgeType> *edge_types) const {
|
||||
auto in(const std::vector<GraphDbTypes::EdgeType> *edge_types) const {
|
||||
return MakeAccessorIterator<EdgeAccessor>(
|
||||
current().in_.begin(edge_types), current().in_.end(), db_accessor());
|
||||
current().in_.begin(nullptr, edge_types), current().in_.end(),
|
||||
db_accessor());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -103,21 +110,29 @@ class VertexAccessor : public RecordAccessor<Vertex> {
|
||||
/**
|
||||
* Returns EdgeAccessors for all outgoing edges whose destination is the given
|
||||
* vertex.
|
||||
*
|
||||
* @param dest - The destination vertex filter.
|
||||
* @param edge_types - Edge types filter. At least one be matched. If nullptr
|
||||
* or empty, the parameter is ignored.
|
||||
*/
|
||||
auto out_with_destination(const VertexAccessor &dest) const {
|
||||
auto out(
|
||||
const VertexAccessor &dest,
|
||||
const std::vector<GraphDbTypes::EdgeType> *edge_types = nullptr) const {
|
||||
return MakeAccessorIterator<EdgeAccessor>(
|
||||
current().out_.begin(dest.vlist_), current().out_.end(), db_accessor());
|
||||
current().out_.begin(dest.vlist_, edge_types), current().out_.end(),
|
||||
db_accessor());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns EdgeAccessors for all outgoing edges whose type is one of the
|
||||
* given. If the given collection of types is nullptr or empty, all edge types
|
||||
* are valid.
|
||||
* Returns EdgeAccessors for all outgoing edges.
|
||||
*
|
||||
* @param edge_types - Edge types filter. At least one be matched. If nullptr
|
||||
* or empty, the parameter is ignored.
|
||||
*/
|
||||
auto out_with_types(
|
||||
const std::vector<GraphDbTypes::EdgeType> *edge_types) const {
|
||||
auto out(const std::vector<GraphDbTypes::EdgeType> *edge_types) const {
|
||||
return MakeAccessorIterator<EdgeAccessor>(
|
||||
current().out_.begin(edge_types), current().out_.end(), db_accessor());
|
||||
current().out_.begin(nullptr, edge_types), current().out_.end(),
|
||||
db_accessor());
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -8,7 +8,7 @@ Feature: Bfs
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n {a:'0'})-bfs(e, m| true, 1)->(m) RETURN n.a, m.a
|
||||
MATCH (n {a:'0'})-[*bfs..1]->(m) RETURN n.a, m.a
|
||||
"""
|
||||
Then the result should be:
|
||||
| n.a | m.a |
|
||||
@ -23,7 +23,7 @@ Feature: Bfs
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n {a:'0'})-bfs(e, m| m.a = '1.1' OR m.a = '0', 10)->(m) RETURN n.a, m.a
|
||||
MATCH (n {a:'0'})-[*bfs..10 (e, m| m.a = '1.1' OR m.a = '0')]->(m) RETURN n.a, m.a
|
||||
"""
|
||||
Then the result should be:
|
||||
| n.a | m.a |
|
||||
@ -37,7 +37,7 @@ Feature: Bfs
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (:Start)-bfs[r](e, m| true, 10)->(m) WHERE size(r) > 3
|
||||
MATCH (:Start)-[r *bfs..10]->(m) WHERE size(r) > 3
|
||||
RETURN size(r) AS s, (r[0]).id AS r0, (r[2]).id AS r2
|
||||
"""
|
||||
Then the result should be:
|
||||
@ -52,7 +52,7 @@ Feature: Bfs
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-bfs[r :r0](e, m| true, 10)->(m)
|
||||
MATCH ()-[r:r0 *bfs..10]->(m)
|
||||
RETURN size(r) AS s, (r[0]).id AS r0
|
||||
"""
|
||||
Then the result should be:
|
||||
@ -67,7 +67,7 @@ Feature: Bfs
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-bfs[r :r0|:r1](e, m| true, 10)->(m) WHERE size(r) > 1
|
||||
MATCH ()-[r :r0|:r1 *bfs..10 ]->(m) WHERE size(r) > 1
|
||||
RETURN size(r) AS s, (r[0]).id AS r0, (r[1]).id AS r1
|
||||
"""
|
||||
Then the result should be:
|
||||
|
@ -1378,7 +1378,7 @@ TYPED_TEST(CypherMainVisitorTest, ReturnAll) {
|
||||
|
||||
TYPED_TEST(CypherMainVisitorTest, MatchBfsReturn) {
|
||||
TypeParam ast_generator(
|
||||
"MATCH (n) -bfs[r:type1|type2](e, n|e.prop = 42, 10)-> (m) RETURN r");
|
||||
"MATCH (n) -[r:type1|type2 *bfs..10 (e, n|e.prop = 42)]-> (m) RETURN r");
|
||||
auto *query = ast_generator.query_;
|
||||
ASSERT_EQ(query->clauses_.size(), 2U);
|
||||
auto *match = dynamic_cast<Match *>(query->clauses_[0]);
|
||||
@ -1387,6 +1387,7 @@ TYPED_TEST(CypherMainVisitorTest, MatchBfsReturn) {
|
||||
ASSERT_EQ(match->patterns_[0]->atoms_.size(), 3U);
|
||||
auto *bfs = dynamic_cast<BreadthFirstAtom *>(match->patterns_[0]->atoms_[1]);
|
||||
ASSERT_TRUE(bfs);
|
||||
EXPECT_TRUE(bfs->has_range_);
|
||||
EXPECT_EQ(bfs->direction_, EdgeAtom::Direction::OUT);
|
||||
EXPECT_THAT(
|
||||
bfs->edge_types_,
|
||||
@ -1395,7 +1396,7 @@ TYPED_TEST(CypherMainVisitorTest, MatchBfsReturn) {
|
||||
EXPECT_EQ(bfs->identifier_->name_, "r");
|
||||
EXPECT_EQ(bfs->traversed_edge_identifier_->name_, "e");
|
||||
EXPECT_EQ(bfs->next_node_identifier_->name_, "n");
|
||||
CheckLiteral(ast_generator.context_, bfs->max_depth_, 10);
|
||||
CheckLiteral(ast_generator.context_, bfs->upper_bound_, 10);
|
||||
auto *eq = dynamic_cast<EqualOperator *>(bfs->filter_expression_);
|
||||
ASSERT_TRUE(eq);
|
||||
}
|
||||
|
@ -222,7 +222,7 @@ TEST(Interpreter, Bfs) {
|
||||
auto dba = dbms.active();
|
||||
|
||||
interpreter.Interpret(
|
||||
"MATCH (n {id: 0})-bfs[r](e, n | n.reachable and e.reachable, 5)->(m) "
|
||||
"MATCH (n {id: 0})-[r *bfs..5 (e, n | n.reachable and e.reachable)]->(m) "
|
||||
"RETURN r",
|
||||
*dba, stream, {});
|
||||
|
||||
|
@ -662,43 +662,6 @@ TEST(ExpressionEvaluator, LabelsTest) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, EdgeTypeTest) {
|
||||
AstTreeStorage storage;
|
||||
NoContextExpressionEvaluator eval;
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
auto v1 = dba->InsertVertex();
|
||||
auto v2 = dba->InsertVertex();
|
||||
auto e = dba->InsertEdge(v1, v2, dba->EdgeType("TYPE1"));
|
||||
auto *identifier = storage.Create<Identifier>("e");
|
||||
auto edge_symbol = eval.symbol_table.CreateSymbol("e", true);
|
||||
eval.symbol_table[*identifier] = edge_symbol;
|
||||
eval.frame[edge_symbol] = e;
|
||||
{
|
||||
auto *op = storage.Create<EdgeTypeTest>(
|
||||
identifier, std::vector<GraphDbTypes::EdgeType>{
|
||||
dba->EdgeType("TYPE0"), dba->EdgeType("TYPE1"),
|
||||
dba->EdgeType("TYPE2")});
|
||||
auto value = op->Accept(eval.eval);
|
||||
EXPECT_EQ(value.Value<bool>(), true);
|
||||
}
|
||||
{
|
||||
auto *op = storage.Create<EdgeTypeTest>(
|
||||
identifier, std::vector<GraphDbTypes::EdgeType>{
|
||||
dba->EdgeType("TYPE0"), dba->EdgeType("TYPE2")});
|
||||
auto value = op->Accept(eval.eval);
|
||||
EXPECT_EQ(value.Value<bool>(), false);
|
||||
}
|
||||
{
|
||||
eval.frame[edge_symbol] = TypedValue::Null;
|
||||
auto *op = storage.Create<EdgeTypeTest>(
|
||||
identifier, std::vector<GraphDbTypes::EdgeType>{
|
||||
dba->EdgeType("TYPE0"), dba->EdgeType("TYPE2")});
|
||||
auto value = op->Accept(eval.eval);
|
||||
EXPECT_TRUE(value.IsNull());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, Aggregation) {
|
||||
AstTreeStorage storage;
|
||||
auto aggr = storage.Create<Aggregation>(storage.Create<PrimitiveLiteral>(42),
|
||||
|
@ -55,7 +55,7 @@ TEST_F(QueryExecution, MissingOptionalIntoExpand) {
|
||||
|
||||
std::string expand = "-->";
|
||||
std::string variable = "-[*1]->";
|
||||
std::string bfs = "-bfs[](n, e | true, 1)->";
|
||||
std::string bfs = "-[*bfs..1]->";
|
||||
|
||||
EXPECT_EQ(Exec(false, expand), 1);
|
||||
EXPECT_EQ(Exec(true, expand), 1);
|
||||
|
@ -1357,18 +1357,13 @@ 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, {}, false, "m", false);
|
||||
// add an edge type filter
|
||||
r_m.edge_->edge_types_.push_back(type_1);
|
||||
r_m.edge_->edge_types_.push_back(type_2);
|
||||
auto *filter_expr = storage.Create<EdgeTypeTest>(r_m.edge_->identifier_,
|
||||
r_m.edge_->edge_types_);
|
||||
auto edge_filter = std::make_shared<Filter>(r_m.op_, filter_expr);
|
||||
auto r_m =
|
||||
MakeExpand(storage, symbol_table, n.op_, n.sym_, "r",
|
||||
EdgeAtom::Direction::OUT, {type_1, type_2}, false, "m", false);
|
||||
|
||||
// make a named expression and a produce
|
||||
auto output = NEXPR("m", IDENT("m"));
|
||||
auto produce = MakeProduce(edge_filter, output);
|
||||
auto produce = MakeProduce(r_m.op_, output);
|
||||
|
||||
// fill up the symbol table
|
||||
symbol_table[*output] = symbol_table.CreateSymbol("named_expression_1", true);
|
||||
|
@ -408,7 +408,7 @@ TEST(TestLogicalPlanner, MatchPathReturn) {
|
||||
auto relationship = dba->EdgeType("relationship");
|
||||
QUERY(MATCH(PATTERN(NODE("n"), EDGE("r", relationship), NODE("m"))),
|
||||
RETURN("n"));
|
||||
CheckPlan(storage, ExpectScanAll(), ExpectExpand(), ExpectFilter(),
|
||||
CheckPlan(storage, ExpectScanAll(), ExpectExpand(),
|
||||
ExpectProduce());
|
||||
}
|
||||
|
||||
@ -421,7 +421,7 @@ TEST(TestLogicalPlanner, MatchNamedPatternReturn) {
|
||||
QUERY(
|
||||
MATCH(NAMED_PATTERN("p", NODE("n"), EDGE("r", relationship), NODE("m"))),
|
||||
RETURN("n"));
|
||||
CheckPlan(storage, ExpectScanAll(), ExpectExpand(), ExpectFilter(),
|
||||
CheckPlan(storage, ExpectScanAll(), ExpectExpand(),
|
||||
ExpectConstructNamedPath(), ExpectProduce());
|
||||
}
|
||||
|
||||
@ -434,7 +434,7 @@ TEST(TestLogicalPlanner, MatchNamedPatternWithPredicateReturn) {
|
||||
QUERY(
|
||||
MATCH(NAMED_PATTERN("p", NODE("n"), EDGE("r", relationship), NODE("m"))),
|
||||
WHERE(EQ(LITERAL(2), IDENT("p"))), RETURN("n"));
|
||||
CheckPlan(storage, ExpectScanAll(), ExpectExpand(), ExpectFilter(),
|
||||
CheckPlan(storage, ExpectScanAll(), ExpectExpand(),
|
||||
ExpectConstructNamedPath(), ExpectFilter(), ExpectProduce());
|
||||
}
|
||||
|
||||
@ -541,28 +541,6 @@ TEST(TestLogicalPlanner, MultiMatchSameStart) {
|
||||
CheckPlan(storage, ExpectScanAll(), ExpectExpand(), ExpectProduce());
|
||||
}
|
||||
|
||||
TEST(TestLogicalPlanner, MatchExistingEdge) {
|
||||
// Test MATCH (n) -[r]- (m) -[r]- (j) RETURN n
|
||||
AstTreeStorage storage;
|
||||
QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m"), EDGE("r"), NODE("j"))),
|
||||
RETURN("n"));
|
||||
// There is no ExpandUniquenessFilter for referencing the same edge.
|
||||
CheckPlan(storage, ExpectScanAll(), ExpectExpand(), ExpectExpand(),
|
||||
ExpectProduce());
|
||||
}
|
||||
|
||||
TEST(TestLogicalPlanner, MultiMatchExistingEdgeOtherEdge) {
|
||||
// Test MATCH (n) -[r]- (m) MATCH (m) -[r]- (j) -[e]- (l) RETURN n
|
||||
AstTreeStorage storage;
|
||||
QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m"))),
|
||||
MATCH(PATTERN(NODE("m"), EDGE("r"), NODE("j"), EDGE("e"), NODE("l"))),
|
||||
RETURN("n"));
|
||||
// We need ExpandUniquenessFilter for edge `e` against `r` in second MATCH.
|
||||
CheckPlan(storage, ExpectScanAll(), ExpectExpand(), ExpectExpand(),
|
||||
ExpectExpand(), ExpectExpandUniquenessFilter<EdgeAccessor>(),
|
||||
ExpectProduce());
|
||||
}
|
||||
|
||||
TEST(TestLogicalPlanner, MatchWithReturn) {
|
||||
// Test MATCH (old) WITH old AS new RETURN new
|
||||
AstTreeStorage storage;
|
||||
@ -772,7 +750,7 @@ TEST(TestLogicalPlanner, MatchMerge) {
|
||||
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 ExpectFilter(),
|
||||
std::list<BaseOpChecker *> on_match{new ExpectExpand(),
|
||||
new ExpectSetProperty()};
|
||||
std::list<BaseOpChecker *> on_create{new ExpectCreateExpand(),
|
||||
new ExpectSetProperties()};
|
||||
@ -1345,7 +1323,8 @@ TEST(TestLogicalPlanner, MatchBreadthFirst) {
|
||||
auto *bfs = storage.Create<query::BreadthFirstAtom>(
|
||||
IDENT("r"), Direction::OUT,
|
||||
std::vector<GraphDbTypes::EdgeType>{edge_type}, IDENT("r"), IDENT("n"),
|
||||
IDENT("n"), LITERAL(10));
|
||||
IDENT("n"));
|
||||
bfs->upper_bound_ = LITERAL(10);
|
||||
QUERY(MATCH(PATTERN(NODE("n"), bfs, NODE("m"))), RETURN("r"));
|
||||
CheckPlan(storage, ExpectScanAll(), ExpectExpandBreadthFirst(),
|
||||
ExpectProduce());
|
||||
|
@ -45,8 +45,8 @@ TEST(TestSymbolGenerator, MatchNamedPattern) {
|
||||
SymbolTable symbol_table;
|
||||
AstTreeStorage storage;
|
||||
// MATCH p = (node_atom_1) RETURN node_atom_1
|
||||
auto query_ast = QUERY(MATCH(NAMED_PATTERN("p", NODE("node_atom_1"))),
|
||||
RETURN("p"));
|
||||
auto query_ast =
|
||||
QUERY(MATCH(NAMED_PATTERN("p", NODE("node_atom_1"))), RETURN("p"));
|
||||
SymbolGenerator symbol_generator(symbol_table);
|
||||
query_ast->Accept(symbol_generator);
|
||||
// symbols for p, node_atom_1 and named_expr in return
|
||||
@ -80,49 +80,6 @@ TEST(TestSymbolGenerator, MatchNodeUnboundReturn) {
|
||||
EXPECT_THROW(query_ast->Accept(symbol_generator), UnboundVariableError);
|
||||
}
|
||||
|
||||
TEST(TestSymbolGenerator, MatchSameEdge) {
|
||||
SymbolTable symbol_table;
|
||||
AstTreeStorage storage;
|
||||
// AST with match pattern referencing an edge multiple times:
|
||||
// MATCH (n) -[r]- (n) -[r]- (n) RETURN r
|
||||
// This usually throws a redeclaration error, but we support it.
|
||||
auto query_ast = QUERY(
|
||||
MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("n"), EDGE("r"), NODE("n"))),
|
||||
RETURN("r"));
|
||||
SymbolGenerator symbol_generator(symbol_table);
|
||||
query_ast->Accept(symbol_generator);
|
||||
// symbols for pattern, `n`, `r` and named_expr in return
|
||||
EXPECT_EQ(symbol_table.max_position(), 4);
|
||||
auto match = dynamic_cast<Match *>(query_ast->clauses_[0]);
|
||||
auto pattern = match->patterns_[0];
|
||||
std::vector<Symbol> node_symbols;
|
||||
std::vector<Symbol> edge_symbols;
|
||||
bool is_node{true};
|
||||
for (auto &atom : pattern->atoms_) {
|
||||
auto symbol = symbol_table[*atom->identifier_];
|
||||
if (is_node) {
|
||||
node_symbols.emplace_back(symbol);
|
||||
} else {
|
||||
edge_symbols.emplace_back(symbol);
|
||||
}
|
||||
is_node = !is_node;
|
||||
}
|
||||
auto &node_symbol = node_symbols.front();
|
||||
EXPECT_EQ(node_symbol.type(), Symbol::Type::Vertex);
|
||||
for (auto &symbol : node_symbols) {
|
||||
EXPECT_EQ(node_symbol, symbol);
|
||||
}
|
||||
auto &edge_symbol = edge_symbols.front();
|
||||
EXPECT_EQ(edge_symbol.type(), Symbol::Type::Edge);
|
||||
for (auto &symbol : edge_symbols) {
|
||||
EXPECT_EQ(edge_symbol, symbol);
|
||||
}
|
||||
auto ret = dynamic_cast<Return *>(query_ast->clauses_[1]);
|
||||
auto named_expr = ret->body_.named_expressions[0];
|
||||
auto ret_symbol = symbol_table[*named_expr->expression_];
|
||||
EXPECT_EQ(edge_symbol, ret_symbol);
|
||||
}
|
||||
|
||||
TEST(TestSymbolGenerator, CreatePropertyUnbound) {
|
||||
SymbolTable symbol_table;
|
||||
AstTreeStorage storage;
|
||||
@ -1048,10 +1005,10 @@ 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, n_prop);
|
||||
auto *bfs = storage.Create<BreadthFirstAtom>(
|
||||
IDENT("r"), EdgeAtom::Direction::OUT,
|
||||
std::vector<GraphDbTypes::EdgeType>{}, IDENT("r"), IDENT("n"), r_prop);
|
||||
bfs->upper_bound_ = n_prop;
|
||||
auto *ret_r = IDENT("r");
|
||||
auto *query =
|
||||
QUERY(MATCH(PATTERN(node_n, bfs, NODE("m"))), RETURN(ret_r, AS("r")));
|
||||
@ -1074,10 +1031,11 @@ TEST(TestSymbolGenerator, MatchBfsReturn) {
|
||||
TEST(TestSymbolGenerator, MatchBfsUsesEdgeSymbolError) {
|
||||
// Test MATCH (n) -bfs[r](e, n | r, 10)-> (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"),
|
||||
LITERAL(10));
|
||||
auto *bfs =
|
||||
storage.Create<BreadthFirstAtom>(IDENT("r"), EdgeAtom::Direction::OUT,
|
||||
std::vector<GraphDbTypes::EdgeType>{},
|
||||
IDENT("e"), IDENT("n"), IDENT("r"));
|
||||
bfs->upper_bound_ = LITERAL(10);
|
||||
auto *query = QUERY(MATCH(PATTERN(NODE("n"), bfs, NODE("m"))), RETURN("r"));
|
||||
SymbolTable symbol_table;
|
||||
SymbolGenerator symbol_generator(symbol_table);
|
||||
@ -1088,10 +1046,11 @@ TEST(TestSymbolGenerator, MatchBfsUsesPreviousOuterSymbol) {
|
||||
// Test MATCH (a) -bfs[r](e, n | a, 10)-> (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"),
|
||||
LITERAL(10));
|
||||
auto *bfs =
|
||||
storage.Create<BreadthFirstAtom>(IDENT("r"), EdgeAtom::Direction::OUT,
|
||||
std::vector<GraphDbTypes::EdgeType>{},
|
||||
IDENT("e"), IDENT("n"), IDENT("a"));
|
||||
bfs->upper_bound_ = LITERAL(10);
|
||||
auto *query = QUERY(MATCH(PATTERN(node_a, bfs, NODE("m"))), RETURN("r"));
|
||||
SymbolTable symbol_table;
|
||||
SymbolGenerator symbol_generator(symbol_table);
|
||||
@ -1103,10 +1062,11 @@ TEST(TestSymbolGenerator, MatchBfsUsesPreviousOuterSymbol) {
|
||||
TEST(TestSymbolGenerator, MatchBfsUsesLaterSymbolError) {
|
||||
// Test MATCH (n) -bfs[r](e, n | m, 10)-> (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"),
|
||||
LITERAL(10));
|
||||
auto *bfs =
|
||||
storage.Create<BreadthFirstAtom>(IDENT("r"), EdgeAtom::Direction::OUT,
|
||||
std::vector<GraphDbTypes::EdgeType>{},
|
||||
IDENT("e"), IDENT("n"), IDENT("m"));
|
||||
bfs->upper_bound_ = LITERAL(10);
|
||||
auto *query = QUERY(MATCH(PATTERN(NODE("n"), bfs, NODE("m"))), RETURN("r"));
|
||||
SymbolTable symbol_table;
|
||||
SymbolGenerator symbol_generator(symbol_table);
|
||||
|
@ -308,8 +308,8 @@ TEST(TestVariableStartPlanner, MatchBfs) {
|
||||
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)),
|
||||
LITERAL(10));
|
||||
IDENT("r"), IDENT("n"), 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:
|
||||
TypedValue r1_list(std::vector<TypedValue>{r1}); // [r1]
|
||||
|
@ -261,25 +261,25 @@ TEST(RecordAccessor, VertexEdgeConnectionsWithExistingVertex) {
|
||||
auto e32 = dba->InsertEdge(v3, v2, edge_type);
|
||||
dba->AdvanceCommand();
|
||||
|
||||
TEST_EDGE_ITERABLE(v1.out_with_destination(v1));
|
||||
TEST_EDGE_ITERABLE(v1.out_with_destination(v2), {e12});
|
||||
TEST_EDGE_ITERABLE(v1.out_with_destination(v3));
|
||||
TEST_EDGE_ITERABLE(v2.out_with_destination(v1));
|
||||
TEST_EDGE_ITERABLE(v2.out_with_destination(v2), {e22});
|
||||
TEST_EDGE_ITERABLE(v2.out_with_destination(v3), {e23a, e23b});
|
||||
TEST_EDGE_ITERABLE(v3.out_with_destination(v1));
|
||||
TEST_EDGE_ITERABLE(v3.out_with_destination(v2), {e32});
|
||||
TEST_EDGE_ITERABLE(v3.out_with_destination(v3));
|
||||
TEST_EDGE_ITERABLE(v1.out(v1));
|
||||
TEST_EDGE_ITERABLE(v1.out(v2), {e12});
|
||||
TEST_EDGE_ITERABLE(v1.out(v3));
|
||||
TEST_EDGE_ITERABLE(v2.out(v1));
|
||||
TEST_EDGE_ITERABLE(v2.out(v2), {e22});
|
||||
TEST_EDGE_ITERABLE(v2.out(v3), {e23a, e23b});
|
||||
TEST_EDGE_ITERABLE(v3.out(v1));
|
||||
TEST_EDGE_ITERABLE(v3.out(v2), {e32});
|
||||
TEST_EDGE_ITERABLE(v3.out(v3));
|
||||
|
||||
TEST_EDGE_ITERABLE(v1.in_with_destination(v1));
|
||||
TEST_EDGE_ITERABLE(v1.in_with_destination(v2));
|
||||
TEST_EDGE_ITERABLE(v1.in_with_destination(v3));
|
||||
TEST_EDGE_ITERABLE(v2.in_with_destination(v1), {e12});
|
||||
TEST_EDGE_ITERABLE(v2.in_with_destination(v2), {e22});
|
||||
TEST_EDGE_ITERABLE(v2.in_with_destination(v3), {e32});
|
||||
TEST_EDGE_ITERABLE(v3.in_with_destination(v1));
|
||||
TEST_EDGE_ITERABLE(v3.in_with_destination(v2), {e23a, e23b});
|
||||
TEST_EDGE_ITERABLE(v3.in_with_destination(v3));
|
||||
TEST_EDGE_ITERABLE(v1.in(v1));
|
||||
TEST_EDGE_ITERABLE(v1.in(v2));
|
||||
TEST_EDGE_ITERABLE(v1.in(v3));
|
||||
TEST_EDGE_ITERABLE(v2.in(v1), {e12});
|
||||
TEST_EDGE_ITERABLE(v2.in(v2), {e22});
|
||||
TEST_EDGE_ITERABLE(v2.in(v3), {e32});
|
||||
TEST_EDGE_ITERABLE(v3.in(v1));
|
||||
TEST_EDGE_ITERABLE(v3.in(v2), {e23a, e23b});
|
||||
TEST_EDGE_ITERABLE(v3.in(v3));
|
||||
}
|
||||
|
||||
TEST(RecordAccessor, VertexEdgeConnectionsWithEdgeType) {
|
||||
@ -302,15 +302,15 @@ TEST(RecordAccessor, VertexEdgeConnectionsWithEdgeType) {
|
||||
std::vector<GraphDbTypes::EdgeType> edges_a{a};
|
||||
std::vector<GraphDbTypes::EdgeType> edges_b{b};
|
||||
std::vector<GraphDbTypes::EdgeType> edges_ac{a, c};
|
||||
TEST_EDGE_ITERABLE(v1.in_with_types(&edges_a));
|
||||
TEST_EDGE_ITERABLE(v1.in_with_types(&edges_b), {eb_1, eb_2});
|
||||
TEST_EDGE_ITERABLE(v1.out_with_types(&edges_a), {ea});
|
||||
TEST_EDGE_ITERABLE(v1.out_with_types(&edges_b));
|
||||
TEST_EDGE_ITERABLE(v1.out_with_types(&edges_ac), {ea, ec});
|
||||
TEST_EDGE_ITERABLE(v2.in_with_types(&edges_a), {ea});
|
||||
TEST_EDGE_ITERABLE(v2.in_with_types(&edges_b));
|
||||
TEST_EDGE_ITERABLE(v2.out_with_types(&edges_a));
|
||||
TEST_EDGE_ITERABLE(v2.out_with_types(&edges_b), {eb_1, eb_2});
|
||||
TEST_EDGE_ITERABLE(v1.in(&edges_a));
|
||||
TEST_EDGE_ITERABLE(v1.in(&edges_b), {eb_1, eb_2});
|
||||
TEST_EDGE_ITERABLE(v1.out(&edges_a), {ea});
|
||||
TEST_EDGE_ITERABLE(v1.out(&edges_b));
|
||||
TEST_EDGE_ITERABLE(v1.out(&edges_ac), {ea, ec});
|
||||
TEST_EDGE_ITERABLE(v2.in(&edges_a), {ea});
|
||||
TEST_EDGE_ITERABLE(v2.in(&edges_b));
|
||||
TEST_EDGE_ITERABLE(v2.out(&edges_a));
|
||||
TEST_EDGE_ITERABLE(v2.out(&edges_b), {eb_1, eb_2});
|
||||
}
|
||||
|
||||
#undef TEST_EDGE_ITERABLE
|
||||
|
Loading…
Reference in New Issue
Block a user