Add filtering BFS by edge type
Summary: Antlr grammar has been updated to support putting edge types after the BFS symbol. Planner collects edge type filters for BFS and inlines them in the operator by joining the filter with the user input BFS filter itself. This requires no change from the standpoint of the operator. On the other hand, in order to use the faster lookup by a single edge type, `ExpandBreadthFirst` operator now accept an optional edge type. The edge type is passed from the planner only if the user is filtering by a single type. Unit tests as well as tck have been updated. Reviewers: florijan, mislav.bradac Reviewed By: florijan Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D777
This commit is contained in:
parent
4d02a4f607
commit
4601f6c368
src/query
frontend
plan
tests
public_benchmark/ldbc/test_cases/queries
qa/tck_engine/tests/memgraph_V1/features
unit
@ -1044,10 +1044,16 @@ class EdgeAtom : public PatternAtom {
|
||||
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 {
|
||||
// TODO: Reconsider inheriting from EdgeAtom, since only `direction_` is used.
|
||||
// TODO: Reconsider inheriting from EdgeAtom, since only `direction_` and
|
||||
// `edge_types_` are used.
|
||||
friend class AstTreeStorage;
|
||||
|
||||
public:
|
||||
@ -1064,7 +1070,7 @@ class BreadthFirstAtom : public EdgeAtom {
|
||||
|
||||
BreadthFirstAtom *Clone(AstTreeStorage &storage) const override {
|
||||
return storage.Create<BreadthFirstAtom>(
|
||||
identifier_->Clone(storage), direction_,
|
||||
identifier_->Clone(storage), direction_, edge_types_,
|
||||
traversed_edge_identifier_->Clone(storage),
|
||||
next_node_identifier_->Clone(storage),
|
||||
filter_expression_->Clone(storage), max_depth_->Clone(storage));
|
||||
@ -1079,10 +1085,11 @@ class BreadthFirstAtom : public EdgeAtom {
|
||||
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, Expression *max_depth)
|
||||
: EdgeAtom(uid, identifier, direction),
|
||||
: EdgeAtom(uid, identifier, direction, edge_types),
|
||||
traversed_edge_identifier_(traversed_edge_identifier),
|
||||
next_node_identifier_(next_node_identifier),
|
||||
filter_expression_(filter_expression),
|
||||
|
@ -428,6 +428,12 @@ antlrcpp::Any CypherMainVisitor::visitRelationshipPattern(
|
||||
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);
|
||||
|
@ -123,7 +123,7 @@ relationshipPattern : ( leftArrowHead SP? dash SP? ( bfsDetail | relationshipDet
|
||||
| ( dash SP? ( bfsDetail | relationshipDetail )? SP? dash )
|
||||
;
|
||||
|
||||
bfsDetail : BFS SP? ( '[' SP? ( bfs_variable=variable SP? )? ']' )? SP? '(' SP? traversed_edge=variable SP? ',' SP? next_node=variable SP? '|' SP? expression SP? ',' SP? expression SP? ')' ;
|
||||
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? )? ( rangeLiteral SP? )? properties SP? ']'
|
||||
| '[' SP? ( variable SP? )? ( relationshipTypes SP? )? ( rangeLiteral SP? )? ( properties SP? )? ']'
|
||||
|
@ -967,12 +967,14 @@ std::unique_ptr<Cursor> ExpandVariable::MakeCursor(GraphDbAccessor &db) {
|
||||
|
||||
ExpandBreadthFirst::ExpandBreadthFirst(
|
||||
Symbol node_symbol, Symbol edge_list_symbol, EdgeAtom::Direction direction,
|
||||
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)
|
||||
const GraphDbTypes::EdgeType &edge_type, 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)
|
||||
: node_symbol_(node_symbol),
|
||||
edge_list_symbol_(edge_list_symbol),
|
||||
direction_(direction),
|
||||
edge_type_(edge_type),
|
||||
max_depth_(max_depth),
|
||||
inner_node_symbol_(inner_node_symbol),
|
||||
inner_edge_symbol_(inner_edge_symbol),
|
||||
@ -1032,12 +1034,24 @@ bool ExpandBreadthFirst::Cursor::Pull(Frame &frame,
|
||||
// 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())
|
||||
expand_pair(edge, edge.to());
|
||||
if (self_.direction_ != EdgeAtom::Direction::OUT)
|
||||
for (const EdgeAccessor &edge : vertex.in())
|
||||
expand_pair(edge, edge.from());
|
||||
if (self_.direction_ != EdgeAtom::Direction::IN) {
|
||||
if (self_.edge_type_) {
|
||||
for (const EdgeAccessor &edge : vertex.out_with_type(self_.edge_type_))
|
||||
expand_pair(edge, edge.to());
|
||||
} else {
|
||||
for (const EdgeAccessor &edge : vertex.out())
|
||||
expand_pair(edge, edge.to());
|
||||
}
|
||||
}
|
||||
if (self_.direction_ != EdgeAtom::Direction::OUT) {
|
||||
if (self_.edge_type_) {
|
||||
for (const EdgeAccessor &edge : vertex.in_with_type(self_.edge_type_))
|
||||
expand_pair(edge, edge.from());
|
||||
} else {
|
||||
for (const EdgeAccessor &edge : vertex.in())
|
||||
expand_pair(edge, edge.from());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// do it all in a loop because we skip some elements
|
||||
|
@ -663,9 +663,10 @@ class ExpandVariable : public LogicalOperator, public ExpandCommon {
|
||||
class ExpandBreadthFirst : public LogicalOperator {
|
||||
public:
|
||||
ExpandBreadthFirst(Symbol node_symbol, Symbol edge_list_symbol,
|
||||
EdgeAtom::Direction direction, Expression *max_depth,
|
||||
Symbol inner_node_symbol, Symbol inner_edge_symbol,
|
||||
Expression *where,
|
||||
EdgeAtom::Direction direction,
|
||||
const GraphDbTypes::EdgeType &edge_type,
|
||||
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 = GraphView::AS_IS);
|
||||
@ -705,6 +706,7 @@ class ExpandBreadthFirst : public LogicalOperator {
|
||||
const Symbol edge_list_symbol_;
|
||||
|
||||
const EdgeAtom::Direction direction_;
|
||||
const GraphDbTypes::EdgeType edge_type_ = nullptr;
|
||||
Expression *max_depth_;
|
||||
|
||||
// symbols for a single node and edge that are currently getting expanded
|
||||
@ -1394,7 +1396,8 @@ class OrderBy : public LogicalOperator {
|
||||
// first pair element is the order-by list
|
||||
// second pair is the remember list
|
||||
// the cache is filled and sorted (only on first pair elem) on first Pull
|
||||
std::vector<std::pair<std::vector<TypedValue>, std::vector<TypedValue>>> cache_;
|
||||
std::vector<std::pair<std::vector<TypedValue>, std::vector<TypedValue>>>
|
||||
cache_;
|
||||
// iterator over the cache_, maintains state between Pulls
|
||||
decltype(cache_.begin()) cache_it_ = cache_.begin();
|
||||
};
|
||||
|
@ -132,15 +132,6 @@ bool HasBoundFilterSymbols(const std::unordered_set<Symbol> &bound_symbols,
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class TBoolOperator>
|
||||
Expression *BoolJoin(AstTreeStorage &storage, Expression *expr1,
|
||||
Expression *expr2) {
|
||||
if (expr1 && expr2) {
|
||||
return storage.Create<TBoolOperator>(expr1, expr2);
|
||||
}
|
||||
return expr1 ? expr1 : expr2;
|
||||
}
|
||||
|
||||
// Ast tree visitor which collects the context for a return body.
|
||||
// The return body of WITH and RETURN clauses consists of:
|
||||
//
|
||||
@ -615,8 +606,8 @@ Expression *ExtractFilters(const std::unordered_set<Symbol> &bound_symbols,
|
||||
filters_it != all_filters.end();) {
|
||||
if (HasBoundFilterSymbols(bound_symbols, *filters_it) &&
|
||||
predicate(*filters_it)) {
|
||||
filter_expr = BoolJoin<FilterAndOperator>(storage, filter_expr,
|
||||
filters_it->expression);
|
||||
filter_expr = impl::BoolJoin<FilterAndOperator>(storage, filter_expr,
|
||||
filters_it->expression);
|
||||
filters_it = all_filters.erase(filters_it);
|
||||
} else {
|
||||
filters_it++;
|
||||
@ -637,13 +628,13 @@ bool BindSymbol(std::unordered_set<Symbol> &bound_symbols,
|
||||
return insertion.second;
|
||||
}
|
||||
|
||||
Expression *FindExpandVariableFilter(
|
||||
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_expand_variable &&
|
||||
return filter.is_for_multi_expand &&
|
||||
filter.used_symbols.find(expands_to_node) ==
|
||||
filter.used_symbols.end();
|
||||
});
|
||||
@ -864,6 +855,10 @@ 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");
|
||||
const auto &symbol = symbol_table.at(*atom->identifier_);
|
||||
for (auto &prop_pair : atom->properties_) {
|
||||
collector.symbols_.clear();
|
||||
@ -924,6 +919,14 @@ void Filters::CollectPatternFilters(Pattern &pattern, SymbolTable &symbol_table,
|
||||
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_),
|
||||
|
@ -51,8 +51,9 @@ class Filters {
|
||||
/// 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 operator.
|
||||
bool is_for_expand_variable = false;
|
||||
/// 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,7 +221,7 @@ bool BindSymbol(std::unordered_set<Symbol> &bound_symbols,
|
||||
// `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 *FindExpandVariableFilter(
|
||||
Expression *ExtractMultiExpandFilter(
|
||||
const std::unordered_set<Symbol> &bound_symbols,
|
||||
const Symbol &expands_to_node,
|
||||
std::vector<Filters::FilterInfo> &all_filters, AstTreeStorage &storage);
|
||||
@ -249,6 +250,15 @@ LogicalOperator *GenWith(With &with, LogicalOperator *input_op,
|
||||
std::unordered_set<Symbol> &bound_symbols,
|
||||
AstTreeStorage &storage);
|
||||
|
||||
template <class TBoolOperator>
|
||||
Expression *BoolJoin(AstTreeStorage &storage, Expression *expr1,
|
||||
Expression *expr2) {
|
||||
if (expr1 && expr2) {
|
||||
return storage.Create<TBoolOperator>(expr1, expr2);
|
||||
}
|
||||
return expr1 ? expr1 : expr2;
|
||||
}
|
||||
|
||||
} // namespace impl
|
||||
|
||||
/// @brief Planner which uses hardcoded rules to produce operators.
|
||||
@ -522,14 +532,18 @@ class RuleBasedPlanner {
|
||||
symbol_table.at(*bf_atom->traversed_edge_identifier_);
|
||||
const auto &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<FilterAndOperator>(
|
||||
storage, impl::ExtractMultiExpandFilter(
|
||||
bound_symbols, node_symbol, all_filters, storage),
|
||||
bf_atom->filter_expression_);
|
||||
last_op = new ExpandBreadthFirst(
|
||||
node_symbol, edge_symbol, expansion.direction,
|
||||
node_symbol, edge_symbol, expansion.direction, edge_type,
|
||||
bf_atom->max_depth_, next_node_symbol, traversed_edge_symbol,
|
||||
bf_atom->filter_expression_,
|
||||
std::shared_ptr<LogicalOperator>(last_op), node1_symbol,
|
||||
existing_node, match_context.graph_view);
|
||||
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::FindExpandVariableFilter(
|
||||
auto *filter_expr = impl::ExtractMultiExpandFilter(
|
||||
bound_symbols, node_symbol, all_filters, storage);
|
||||
last_op = new ExpandVariable(
|
||||
node_symbol, edge_symbol, expansion.direction, edge_type,
|
||||
|
@ -1,5 +1,5 @@
|
||||
MATCH (person1:Person {id:"17592186055119"}), (person2:Person {id:"8796093025131"})
|
||||
OPTIONAL MATCH (person1)-bfs[r](a, b | type(a) = "KNOWS", 15)-(person2)
|
||||
OPTIONAL MATCH (person1)-bfs[r:KNOWS](a, b | true , 15)-(person2)
|
||||
RETURN
|
||||
CASE r IS NULL
|
||||
WHEN true THEN -1
|
||||
|
@ -43,3 +43,33 @@ Feature: Bfs
|
||||
Then the result should be:
|
||||
| s | r0 | r2 |
|
||||
| 4 | 0 | 2 |
|
||||
|
||||
Scenario: Test match BFS single edge type filtered
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()-[:r0 {id: 0}]->()-[:r1 {id: 1}]->()-[:r2 {id: 2}]->()-[:r3 {id: 3}]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-bfs[r :r0](e, m| true, 10)->(m)
|
||||
RETURN size(r) AS s, (r[0]).id AS r0
|
||||
"""
|
||||
Then the result should be:
|
||||
| s | r0 |
|
||||
| 1 | 0 |
|
||||
|
||||
Scenario: Test match BFS multiple edge types filtered
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE ()-[:r0 {id: 0}]->()-[:r1 {id: 1}]->()-[:r2 {id: 2}]->()-[:r3 {id: 3}]->()
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-bfs[r :r0|:r1](e, m| true, 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:
|
||||
| s | r0 | r1 |
|
||||
| 2 | 0 | 1 |
|
||||
|
@ -1493,7 +1493,7 @@ TYPED_TEST(CypherMainVisitorTest, ReturnAll) {
|
||||
|
||||
TYPED_TEST(CypherMainVisitorTest, MatchBfsReturn) {
|
||||
TypeParam ast_generator(
|
||||
"MATCH (n) -bfs[r](e, n|e.prop = 42, 10)-> (m) RETURN r");
|
||||
"MATCH (n) -bfs[r:type1|type2](e, n|e.prop = 42, 10)-> (m) RETURN r");
|
||||
auto *query = ast_generator.query_;
|
||||
ASSERT_EQ(query->clauses_.size(), 2U);
|
||||
auto *match = dynamic_cast<Match *>(query->clauses_[0]);
|
||||
@ -1503,6 +1503,10 @@ TYPED_TEST(CypherMainVisitorTest, MatchBfsReturn) {
|
||||
auto *bfs = dynamic_cast<BreadthFirstAtom *>(match->patterns_[0]->atoms_[1]);
|
||||
ASSERT_TRUE(bfs);
|
||||
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");
|
||||
|
@ -160,7 +160,7 @@ TEST_F(QueryCostEstimator, ExpandVariable) {
|
||||
|
||||
TEST_F(QueryCostEstimator, ExpandBreadthFirst) {
|
||||
MakeOp<ExpandBreadthFirst>(
|
||||
NextSymbol(), NextSymbol(), EdgeAtom::Direction::IN, Literal(3),
|
||||
NextSymbol(), NextSymbol(), EdgeAtom::Direction::IN, nullptr, Literal(3),
|
||||
NextSymbol(), NextSymbol(), Literal(true), last_op_, NextSymbol(), false);
|
||||
EXPECT_COST(CardParam::kExpandBreadthFirst * CostParam::kExpandBreadthFirst);
|
||||
}
|
||||
|
@ -353,10 +353,9 @@ class QueryPlanExpandVariable : public testing::Test {
|
||||
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>(
|
||||
n_from.node_->identifier_,
|
||||
std::vector<GraphDbTypes::Label>{labels[layer]}));
|
||||
n_from.op_, storage.Create<query::LabelsTest>(
|
||||
n_from.node_->identifier_,
|
||||
std::vector<GraphDbTypes::Label>{labels[layer]}));
|
||||
|
||||
auto n_to = NODE(node_to);
|
||||
auto n_to_sym = symbol_table.CreateSymbol(node_to, true);
|
||||
@ -697,9 +696,9 @@ class QueryPlanExpandBreadthFirst : public testing::Test {
|
||||
: 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, LITERAL(max_depth), inner_node,
|
||||
inner_edge, where, last_op, n.sym_, existing_node_input != nullptr,
|
||||
graph_view);
|
||||
node_sym, edge_list_sym, direction, nullptr, LITERAL(max_depth),
|
||||
inner_node, inner_edge, where, last_op, n.sym_,
|
||||
existing_node_input != nullptr, graph_view);
|
||||
|
||||
Frame frame(symbol_table.max_position());
|
||||
auto cursor = last_op->MakeCursor(*dba);
|
||||
|
@ -1297,11 +1297,15 @@ TEST(TestLogicalPlanner, UnwindMatchVariable) {
|
||||
}
|
||||
|
||||
TEST(TestLogicalPlanner, MatchBreadthFirst) {
|
||||
// Test MATCH (n) -bfs[r](r, n|n, 10)-> (m) RETURN r
|
||||
// Test MATCH (n) -bfs[r:type](r, n|n, 10)-> (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, IDENT("r"), IDENT("n"), IDENT("n"),
|
||||
LITERAL(10));
|
||||
IDENT("r"), Direction::OUT,
|
||||
std::vector<GraphDbTypes::EdgeType>{edge_type}, IDENT("r"), IDENT("n"),
|
||||
IDENT("n"), LITERAL(10));
|
||||
QUERY(MATCH(PATTERN(NODE("n"), bfs, NODE("m"))), RETURN("r"));
|
||||
CheckPlan(storage, ExpectScanAll(), ExpectExpandBreadthFirst(),
|
||||
ExpectProduce());
|
||||
|
@ -1019,6 +1019,7 @@ TEST(TestSymbolGenerator, MatchBfsReturn) {
|
||||
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 *ret_r = IDENT("r");
|
||||
auto *query =
|
||||
@ -1043,7 +1044,8 @@ 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, IDENT("e"), IDENT("n"), IDENT("r"),
|
||||
IDENT("r"), EdgeAtom::Direction::OUT,
|
||||
std::vector<GraphDbTypes::EdgeType>{}, IDENT("e"), IDENT("n"), IDENT("r"),
|
||||
LITERAL(10));
|
||||
auto *query = QUERY(MATCH(PATTERN(NODE("n"), bfs, NODE("m"))), RETURN("r"));
|
||||
SymbolTable symbol_table;
|
||||
@ -1056,7 +1058,8 @@ TEST(TestSymbolGenerator, MatchBfsUsesPreviousOuterSymbol) {
|
||||
AstTreeStorage storage;
|
||||
auto *node_a = NODE("a");
|
||||
auto *bfs = storage.Create<BreadthFirstAtom>(
|
||||
IDENT("r"), EdgeAtom::Direction::OUT, IDENT("e"), IDENT("n"), IDENT("a"),
|
||||
IDENT("r"), EdgeAtom::Direction::OUT,
|
||||
std::vector<GraphDbTypes::EdgeType>{}, IDENT("e"), IDENT("n"), IDENT("a"),
|
||||
LITERAL(10));
|
||||
auto *query = QUERY(MATCH(PATTERN(node_a, bfs, NODE("m"))), RETURN("r"));
|
||||
SymbolTable symbol_table;
|
||||
@ -1070,7 +1073,8 @@ 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, IDENT("e"), IDENT("n"), IDENT("m"),
|
||||
IDENT("r"), EdgeAtom::Direction::OUT,
|
||||
std::vector<GraphDbTypes::EdgeType>{}, IDENT("e"), IDENT("n"), IDENT("m"),
|
||||
LITERAL(10));
|
||||
auto *query = QUERY(MATCH(PATTERN(NODE("n"), bfs, NODE("m"))), RETURN("r"));
|
||||
SymbolTable symbol_table;
|
||||
|
@ -307,8 +307,9 @@ TEST(TestVariableStartPlanner, MatchBfs) {
|
||||
// Test MATCH (n) -bfs[r](r, n|n.id <> 3, 10)-> (m) RETURN r
|
||||
AstTreeStorage storage;
|
||||
auto *bfs = storage.Create<query::BreadthFirstAtom>(
|
||||
IDENT("r"), Direction::OUT, IDENT("r"), IDENT("n"),
|
||||
NEQ(PROPERTY_LOOKUP("n", id), LITERAL(3)), LITERAL(10));
|
||||
IDENT("r"), Direction::OUT, std::vector<GraphDbTypes::EdgeType>{},
|
||||
IDENT("r"), IDENT("n"), NEQ(PROPERTY_LOOKUP("n", id), LITERAL(3)),
|
||||
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]
|
||||
|
Loading…
Reference in New Issue
Block a user