Fix cursor exhaustion by adding EmptyResult operator (#667)

This commit is contained in:
Andi 2022-12-09 11:44:07 +01:00 committed by GitHub
parent d6d4153fb7
commit 0f77c85824
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 171 additions and 27 deletions

View File

@ -114,6 +114,7 @@ extern const Event UnionOperator;
extern const Event CartesianOperator; extern const Event CartesianOperator;
extern const Event CallProcedureOperator; extern const Event CallProcedureOperator;
extern const Event ForeachOperator; extern const Event ForeachOperator;
extern const Event EmptyResultOperator;
} // namespace EventCounter } // namespace EventCounter
namespace memgraph::query::plan { namespace memgraph::query::plan {
@ -3058,6 +3059,56 @@ void EdgeUniquenessFilter::EdgeUniquenessFilterCursor::Shutdown() { input_cursor
void EdgeUniquenessFilter::EdgeUniquenessFilterCursor::Reset() { input_cursor_->Reset(); } void EdgeUniquenessFilter::EdgeUniquenessFilterCursor::Reset() { input_cursor_->Reset(); }
EmptyResult::EmptyResult(const std::shared_ptr<LogicalOperator> &input)
: input_(input ? input : std::make_shared<Once>()) {}
ACCEPT_WITH_INPUT(EmptyResult)
std::vector<Symbol> EmptyResult::OutputSymbols(const SymbolTable &) const { // NOLINT(hicpp-named-parameter)
return {};
}
std::vector<Symbol> EmptyResult::ModifiedSymbols(const SymbolTable &) const { // NOLINT(hicpp-named-parameter)
return {};
}
class EmptyResultCursor : public Cursor {
public:
EmptyResultCursor(const EmptyResult &self, utils::MemoryResource *mem)
: input_cursor_(self.input_->MakeCursor(mem)) {}
bool Pull(Frame &frame, ExecutionContext &context) override {
SCOPED_PROFILE_OP("EmptyResult");
if (!pulled_all_input_) {
while (input_cursor_->Pull(frame, context)) {
if (MustAbort(context)) {
throw HintedAbortError();
}
}
pulled_all_input_ = true;
}
return false;
}
void Shutdown() override { input_cursor_->Shutdown(); }
void Reset() override {
input_cursor_->Reset();
pulled_all_input_ = false;
}
private:
const UniqueCursorPtr input_cursor_;
bool pulled_all_input_{false};
};
UniqueCursorPtr EmptyResult::MakeCursor(utils::MemoryResource *mem) const {
EventCounter::IncrementCounter(EventCounter::EmptyResultOperator);
return MakeUniqueCursorPtr<EmptyResultCursor>(mem, *this, mem);
}
Accumulate::Accumulate(const std::shared_ptr<LogicalOperator> &input, const std::vector<Symbol> &symbols, Accumulate::Accumulate(const std::shared_ptr<LogicalOperator> &input, const std::vector<Symbol> &symbols,
bool advance_command) bool advance_command)
: input_(input), symbols_(symbols), advance_command_(advance_command) {} : input_(input), symbols_(symbols), advance_command_(advance_command) {}

View File

@ -132,6 +132,7 @@ class Cartesian;
class CallProcedure; class CallProcedure;
class LoadCsv; class LoadCsv;
class Foreach; class Foreach;
class EmptyResult;
using LogicalOperatorCompositeVisitor = utils::CompositeVisitor< using LogicalOperatorCompositeVisitor = utils::CompositeVisitor<
Once, CreateNode, CreateExpand, ScanAll, ScanAllByLabel, Once, CreateNode, CreateExpand, ScanAll, ScanAllByLabel,
@ -140,7 +141,7 @@ using LogicalOperatorCompositeVisitor = utils::CompositeVisitor<
Expand, ExpandVariable, ConstructNamedPath, Filter, Produce, Delete, Expand, ExpandVariable, ConstructNamedPath, Filter, Produce, Delete,
SetProperty, SetProperties, SetLabels, RemoveProperty, RemoveLabels, SetProperty, SetProperties, SetLabels, RemoveProperty, RemoveLabels,
EdgeUniquenessFilter, Accumulate, Aggregate, Skip, Limit, OrderBy, Merge, EdgeUniquenessFilter, Accumulate, Aggregate, Skip, Limit, OrderBy, Merge,
Optional, Unwind, Distinct, Union, Cartesian, CallProcedure, LoadCsv, Foreach>; Optional, Unwind, Distinct, Union, Cartesian, CallProcedure, LoadCsv, Foreach, EmptyResult>;
using LogicalOperatorLeafVisitor = utils::LeafVisitor<Once>; using LogicalOperatorLeafVisitor = utils::LeafVisitor<Once>;
@ -1554,6 +1555,41 @@ edge lists).")
(:serialize (:slk)) (:serialize (:slk))
(:clone)) (:clone))
(lcp:define-class empty-result (logical-operator)
((input "std::shared_ptr<LogicalOperator>" :scope :public
:slk-save #'slk-save-operator-pointer
:slk-load #'slk-load-operator-pointer))
(:documentation
"Pulls everything from the input and discards it.
On the first Pull from this operator's Cursor the input Cursor will be Pulled
until it is empty. The results won't be accumulated in the temporary cache.
This technique is used for ensuring that the cursor has been exhausted after
a WriteHandleClause. A typical use case is a `MATCH--SET` query with RETURN statement
missing.
@param input Input @c LogicalOperator. ")
(:public
#>cpp
EmptyResult() {}
EmptyResult(const std::shared_ptr<LogicalOperator> &input);
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
std::vector<Symbol> OutputSymbols(const SymbolTable &) const override;
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
bool HasSingleInput() const override { return true; }
std::shared_ptr<LogicalOperator> input() const override { return input_; }
void set_input(std::shared_ptr<LogicalOperator> input) override {
input_ = input;
}
cpp<#)
(:serialize (:slk))
(:clone))
(lcp:define-class accumulate (logical-operator) (lcp:define-class accumulate (logical-operator)
((input "std::shared_ptr<LogicalOperator>" :scope :public ((input "std::shared_ptr<LogicalOperator>" :scope :public
:slk-save #'slk-save-operator-pointer :slk-save #'slk-save-operator-pointer

View File

@ -156,6 +156,7 @@ PRE_VISIT(RemoveProperty);
PRE_VISIT(RemoveLabels); PRE_VISIT(RemoveLabels);
PRE_VISIT(EdgeUniquenessFilter); PRE_VISIT(EdgeUniquenessFilter);
PRE_VISIT(Accumulate); PRE_VISIT(Accumulate);
PRE_VISIT(EmptyResult);
bool PlanPrinter::PreVisit(query::plan::Aggregate &op) { bool PlanPrinter::PreVisit(query::plan::Aggregate &op) {
WithPrintLn([&](auto &out) { WithPrintLn([&](auto &out) {
@ -705,6 +706,17 @@ bool PlanToJsonVisitor::PreVisit(EdgeUniquenessFilter &op) {
return false; return false;
} }
bool PlanToJsonVisitor::PreVisit(EmptyResult &op) {
json self;
self["name"] = "EmptyResult";
op.input_->Accept(*this);
self["input"] = PopOutput();
output_ = std::move(self);
return false;
}
bool PlanToJsonVisitor::PreVisit(Accumulate &op) { bool PlanToJsonVisitor::PreVisit(Accumulate &op) {
json self; json self;
self["name"] = "Accumulate"; self["name"] = "Accumulate";

View File

@ -80,6 +80,7 @@ class PlanPrinter : public virtual HierarchicalLogicalOperatorVisitor {
bool PreVisit(Optional &) override; bool PreVisit(Optional &) override;
bool PreVisit(Cartesian &) override; bool PreVisit(Cartesian &) override;
bool PreVisit(EmptyResult &) override;
bool PreVisit(Produce &) override; bool PreVisit(Produce &) override;
bool PreVisit(Accumulate &) override; bool PreVisit(Accumulate &) override;
bool PreVisit(Aggregate &) override; bool PreVisit(Aggregate &) override;
@ -195,6 +196,7 @@ class PlanToJsonVisitor : public virtual HierarchicalLogicalOperatorVisitor {
bool PreVisit(ScanAllByLabelProperty &) override; bool PreVisit(ScanAllByLabelProperty &) override;
bool PreVisit(ScanAllById &) override; bool PreVisit(ScanAllById &) override;
bool PreVisit(EmptyResult &) override;
bool PreVisit(Produce &) override; bool PreVisit(Produce &) override;
bool PreVisit(Accumulate &) override; bool PreVisit(Accumulate &) override;
bool PreVisit(Aggregate &) override; bool PreVisit(Aggregate &) override;

View File

@ -11,8 +11,9 @@
#include "query/plan/read_write_type_checker.hpp" #include "query/plan/read_write_type_checker.hpp"
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define PRE_VISIT(TOp, RWType, continue_visiting) \ #define PRE_VISIT(TOp, RWType, continue_visiting) \
bool ReadWriteTypeChecker::PreVisit(TOp &op) { \ bool ReadWriteTypeChecker::PreVisit(TOp &) { /*NOLINT(bugprone-macro-parentheses)*/ \
UpdateType(RWType); \ UpdateType(RWType); \
return continue_visiting; \ return continue_visiting; \
} }
@ -54,6 +55,7 @@ bool ReadWriteTypeChecker::PreVisit(Cartesian &op) {
return false; return false;
} }
PRE_VISIT(EmptyResult, RWType::NONE, true)
PRE_VISIT(Produce, RWType::NONE, true) PRE_VISIT(Produce, RWType::NONE, true)
PRE_VISIT(Accumulate, RWType::NONE, true) PRE_VISIT(Accumulate, RWType::NONE, true)
PRE_VISIT(Aggregate, RWType::NONE, true) PRE_VISIT(Aggregate, RWType::NONE, true)
@ -86,7 +88,7 @@ bool ReadWriteTypeChecker::PreVisit([[maybe_unused]] Foreach &op) {
#undef PRE_VISIT #undef PRE_VISIT
bool ReadWriteTypeChecker::Visit(Once &op) { return false; } bool ReadWriteTypeChecker::Visit(Once &) { return false; } // NOLINT(hicpp-named-parameter)
void ReadWriteTypeChecker::UpdateType(RWType op_type) { void ReadWriteTypeChecker::UpdateType(RWType op_type) {
// Update type only if it's not the NONE type and the current operator's type // Update type only if it's not the NONE type and the current operator's type

View File

@ -73,6 +73,7 @@ class ReadWriteTypeChecker : public virtual HierarchicalLogicalOperatorVisitor {
bool PreVisit(Optional &) override; bool PreVisit(Optional &) override;
bool PreVisit(Cartesian &) override; bool PreVisit(Cartesian &) override;
bool PreVisit(EmptyResult &) override;
bool PreVisit(Produce &) override; bool PreVisit(Produce &) override;
bool PreVisit(Accumulate &) override; bool PreVisit(Accumulate &) override;
bool PreVisit(Aggregate &) override; bool PreVisit(Aggregate &) override;

View File

@ -298,6 +298,15 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor {
return true; return true;
} }
bool PreVisit(EmptyResult &op) override {
prev_ops_.push_back(&op);
return true;
}
bool PostVisit(EmptyResult &) override {
prev_ops_.pop_back();
return true;
}
bool PreVisit(Delete &op) override { bool PreVisit(Delete &op) override {
prev_ops_.push_back(&op); prev_ops_.push_back(&op);
return true; return true;

View File

@ -180,7 +180,7 @@ class RuleBasedPlanner {
} }
} }
uint64_t merge_id = 0; uint64_t merge_id = 0;
for (auto *clause : query_part.remaining_clauses) { for (const auto &clause : query_part.remaining_clauses) {
MG_ASSERT(!utils::IsSubtype(*clause, Match::kType), "Unexpected Match in remaining clauses"); MG_ASSERT(!utils::IsSubtype(*clause, Match::kType), "Unexpected Match in remaining clauses");
if (auto *ret = utils::Downcast<Return>(clause)) { if (auto *ret = utils::Downcast<Return>(clause)) {
input_op = impl::GenReturn(*ret, std::move(input_op), *context.symbol_table, is_write, context.bound_symbols, input_op = impl::GenReturn(*ret, std::move(input_op), *context.symbol_table, is_write, context.bound_symbols,
@ -203,6 +203,7 @@ class RuleBasedPlanner {
context.bound_symbols.insert(symbol); context.bound_symbols.insert(symbol);
input_op = input_op =
std::make_unique<plan::Unwind>(std::move(input_op), unwind->named_expression_->expression_, symbol); std::make_unique<plan::Unwind>(std::move(input_op), unwind->named_expression_->expression_, symbol);
} else if (auto *call_proc = utils::Downcast<query::CallProcedure>(clause)) { } else if (auto *call_proc = utils::Downcast<query::CallProcedure>(clause)) {
std::vector<Symbol> result_symbols; std::vector<Symbol> result_symbols;
result_symbols.reserve(call_proc->result_identifiers_.size()); result_symbols.reserve(call_proc->result_identifiers_.size());
@ -224,6 +225,7 @@ class RuleBasedPlanner {
input_op = input_op =
std::make_unique<plan::LoadCsv>(std::move(input_op), load_csv->file_, load_csv->with_header_, std::make_unique<plan::LoadCsv>(std::move(input_op), load_csv->file_, load_csv->with_header_,
load_csv->ignore_bad_, load_csv->delimiter_, load_csv->quote_, row_sym); load_csv->ignore_bad_, load_csv->delimiter_, load_csv->quote_, row_sym);
} else if (auto *foreach = utils::Downcast<query::Foreach>(clause)) { } else if (auto *foreach = utils::Downcast<query::Foreach>(clause)) {
is_write = true; is_write = true;
input_op = HandleForeachClause(foreach, std::move(input_op), *context.symbol_table, context.bound_symbols, input_op = HandleForeachClause(foreach, std::move(input_op), *context.symbol_table, context.bound_symbols,
@ -233,6 +235,10 @@ class RuleBasedPlanner {
} }
} }
} }
// Is this the only situation that should be covered
if (input_op->OutputSymbols(*context.symbol_table).empty()) {
input_op = std::make_unique<EmptyResult>(std::move(input_op));
}
return input_op; return input_op;
} }
@ -418,7 +424,8 @@ class RuleBasedPlanner {
std::optional<ExpansionLambda> weight_lambda; std::optional<ExpansionLambda> weight_lambda;
std::optional<Symbol> total_weight; std::optional<Symbol> total_weight;
if (edge->type_ == EdgeAtom::Type::WEIGHTED_SHORTEST_PATH || edge->type_ == EdgeAtom::Type::ALL_SHORTEST_PATHS) { if (edge->type_ == EdgeAtom::Type::WEIGHTED_SHORTEST_PATH ||
edge->type_ == EdgeAtom::Type::ALL_SHORTEST_PATHS) {
weight_lambda.emplace(ExpansionLambda{symbol_table.at(*edge->weight_lambda_.inner_edge), weight_lambda.emplace(ExpansionLambda{symbol_table.at(*edge->weight_lambda_.inner_edge),
symbol_table.at(*edge->weight_lambda_.inner_node), symbol_table.at(*edge->weight_lambda_.inner_node),
edge->weight_lambda_.expression}); edge->weight_lambda_.expression});

View File

@ -37,6 +37,7 @@
M(RemovePropertyOperator, "Number of times RemoveProperty operator was used.") \ M(RemovePropertyOperator, "Number of times RemoveProperty operator was used.") \
M(RemoveLabelsOperator, "Number of times RemoveLabels operator was used.") \ M(RemoveLabelsOperator, "Number of times RemoveLabels operator was used.") \
M(EdgeUniquenessFilterOperator, "Number of times EdgeUniquenessFilter operator was used.") \ M(EdgeUniquenessFilterOperator, "Number of times EdgeUniquenessFilter operator was used.") \
M(EmptyResultOperator, "Number of times EmptyResult operator was used.") \
M(AccumulateOperator, "Number of times Accumulate operator was used.") \ M(AccumulateOperator, "Number of times Accumulate operator was used.") \
M(AggregateOperator, "Number of times Aggregate operator was used.") \ M(AggregateOperator, "Number of times Aggregate operator was used.") \
M(SkipOperator, "Number of times Skip operator was used.") \ M(SkipOperator, "Number of times Skip operator was used.") \

View File

@ -97,6 +97,22 @@ Feature: Update clauses
| a | b | c | | a | b | c |
| (:q{x: 'y'}) | [:X{x: 'y'}] | ({y: 't'}) | | (:q{x: 'y'}) | [:X{x: 'y'}] | ({y: 't'}) |
Scenario: Match node set properties without return
Given an empty graph
And having executed
"""
CREATE (n1:Node {test: 1})
CREATE (n2:Node {test: 2})
CREATE (n3:Node {test: 3})
"""
When executing query:
"""
MATCH (n:Node)
SET n.test = 4
"""
Then the result should be empty
Scenario: Match, set properties from relationship to relationship, return test Scenario: Match, set properties from relationship to relationship, return test
Given an empty graph Given an empty graph
When executing query: When executing query:

View File

@ -853,7 +853,7 @@ TEST_F(InterpreterTest, ProfileQueryWithLiterals) {
auto stream = Interpret("PROFILE UNWIND range(1, 1000) AS x CREATE (:Node {id: x});", {}); auto stream = Interpret("PROFILE UNWIND range(1, 1000) AS x CREATE (:Node {id: x});", {});
std::vector<std::string> expected_header{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"}; std::vector<std::string> expected_header{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"};
EXPECT_EQ(stream.GetHeader(), expected_header); EXPECT_EQ(stream.GetHeader(), expected_header);
std::vector<std::string> expected_rows{"* CreateNode", "* Unwind", "* Once"}; std::vector<std::string> expected_rows{"* EmptyResult", "* CreateNode", "* Unwind", "* Once"};
ASSERT_EQ(stream.GetResults().size(), expected_rows.size()); ASSERT_EQ(stream.GetResults().size(), expected_rows.size());
auto expected_it = expected_rows.begin(); auto expected_it = expected_rows.begin();
for (const auto &row : stream.GetResults()) { for (const auto &row : stream.GetResults()) {

View File

@ -121,14 +121,14 @@ TYPED_TEST(TestPlanner, CreateExpand) {
FakeDbAccessor dba; FakeDbAccessor dba;
auto relationship = "relationship"; auto relationship = "relationship";
auto *query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"), EDGE("r", Direction::OUT, {relationship}), NODE("m"))))); auto *query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"), EDGE("r", Direction::OUT, {relationship}), NODE("m")))));
CheckPlan<TypeParam>(query, storage, ExpectCreateNode(), ExpectCreateExpand()); CheckPlan<TypeParam>(query, storage, ExpectCreateNode(), ExpectCreateExpand(), ExpectEmptyResult());
} }
TYPED_TEST(TestPlanner, CreateMultipleNode) { TYPED_TEST(TestPlanner, CreateMultipleNode) {
// Test CREATE (n), (m) // Test CREATE (n), (m)
AstStorage storage; AstStorage storage;
auto *query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n")), PATTERN(NODE("m"))))); auto *query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n")), PATTERN(NODE("m")))));
CheckPlan<TypeParam>(query, storage, ExpectCreateNode(), ExpectCreateNode()); CheckPlan<TypeParam>(query, storage, ExpectCreateNode(), ExpectCreateNode(), ExpectEmptyResult());
} }
TYPED_TEST(TestPlanner, CreateNodeExpandNode) { TYPED_TEST(TestPlanner, CreateNodeExpandNode) {
@ -138,7 +138,8 @@ TYPED_TEST(TestPlanner, CreateNodeExpandNode) {
auto relationship = "rel"; auto relationship = "rel";
auto *query = QUERY(SINGLE_QUERY( auto *query = QUERY(SINGLE_QUERY(
CREATE(PATTERN(NODE("n"), EDGE("r", Direction::OUT, {relationship}), NODE("m")), PATTERN(NODE("l"))))); CREATE(PATTERN(NODE("n"), EDGE("r", Direction::OUT, {relationship}), NODE("m")), PATTERN(NODE("l")))));
CheckPlan<TypeParam>(query, storage, ExpectCreateNode(), ExpectCreateExpand(), ExpectCreateNode()); CheckPlan<TypeParam>(query, storage, ExpectCreateNode(), ExpectCreateExpand(), ExpectCreateNode(),
ExpectEmptyResult());
} }
TYPED_TEST(TestPlanner, CreateNamedPattern) { TYPED_TEST(TestPlanner, CreateNamedPattern) {
@ -148,7 +149,8 @@ TYPED_TEST(TestPlanner, CreateNamedPattern) {
auto relationship = "rel"; auto relationship = "rel";
auto *query = auto *query =
QUERY(SINGLE_QUERY(CREATE(NAMED_PATTERN("p", NODE("n"), EDGE("r", Direction::OUT, {relationship}), NODE("m"))))); QUERY(SINGLE_QUERY(CREATE(NAMED_PATTERN("p", NODE("n"), EDGE("r", Direction::OUT, {relationship}), NODE("m")))));
CheckPlan<TypeParam>(query, storage, ExpectCreateNode(), ExpectCreateExpand(), ExpectConstructNamedPath()); CheckPlan<TypeParam>(query, storage, ExpectCreateNode(), ExpectCreateExpand(), ExpectConstructNamedPath(),
ExpectEmptyResult());
} }
TYPED_TEST(TestPlanner, MatchCreateExpand) { TYPED_TEST(TestPlanner, MatchCreateExpand) {
@ -158,7 +160,7 @@ TYPED_TEST(TestPlanner, MatchCreateExpand) {
auto relationship = "relationship"; auto relationship = "relationship";
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))),
CREATE(PATTERN(NODE("n"), EDGE("r", Direction::OUT, {relationship}), NODE("m"))))); CREATE(PATTERN(NODE("n"), EDGE("r", Direction::OUT, {relationship}), NODE("m")))));
CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectCreateExpand()); CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectCreateExpand(), ExpectEmptyResult());
} }
TYPED_TEST(TestPlanner, MatchLabeledNodes) { TYPED_TEST(TestPlanner, MatchLabeledNodes) {
@ -260,7 +262,7 @@ TYPED_TEST(TestPlanner, MatchDelete) {
// Test MATCH (n) DELETE n // Test MATCH (n) DELETE n
AstStorage storage; AstStorage storage;
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), DELETE(IDENT("n")))); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), DELETE(IDENT("n"))));
CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectDelete()); CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectDelete(), ExpectEmptyResult());
} }
TYPED_TEST(TestPlanner, MatchNodeSet) { TYPED_TEST(TestPlanner, MatchNodeSet) {
@ -271,7 +273,8 @@ TYPED_TEST(TestPlanner, MatchNodeSet) {
auto label = "label"; auto label = "label";
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), SET(PROPERTY_LOOKUP("n", prop), LITERAL(42)), auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), SET(PROPERTY_LOOKUP("n", prop), LITERAL(42)),
SET("n", IDENT("n")), SET("n", {label}))); SET("n", IDENT("n")), SET("n", {label})));
CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectSetProperty(), ExpectSetProperties(), ExpectSetLabels()); CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectSetProperty(), ExpectSetProperties(), ExpectSetLabels(),
ExpectEmptyResult());
} }
TYPED_TEST(TestPlanner, MatchRemove) { TYPED_TEST(TestPlanner, MatchRemove) {
@ -282,7 +285,8 @@ TYPED_TEST(TestPlanner, MatchRemove) {
auto label = "label"; auto label = "label";
auto *query = auto *query =
QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), REMOVE(PROPERTY_LOOKUP("n", prop)), REMOVE("n", {label}))); QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), REMOVE(PROPERTY_LOOKUP("n", prop)), REMOVE("n", {label})));
CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectRemoveProperty(), ExpectRemoveLabels()); CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectRemoveProperty(), ExpectRemoveLabels(),
ExpectEmptyResult());
} }
TYPED_TEST(TestPlanner, MatchMultiPattern) { TYPED_TEST(TestPlanner, MatchMultiPattern) {
@ -387,7 +391,8 @@ TYPED_TEST(TestPlanner, CreateMultiExpand) {
AstStorage storage; AstStorage storage;
auto *query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"), EDGE("r", Direction::OUT, {r}), NODE("m")), auto *query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"), EDGE("r", Direction::OUT, {r}), NODE("m")),
PATTERN(NODE("n"), EDGE("p", Direction::OUT, {p}), NODE("l"))))); PATTERN(NODE("n"), EDGE("p", Direction::OUT, {p}), NODE("l")))));
CheckPlan<TypeParam>(query, storage, ExpectCreateNode(), ExpectCreateExpand(), ExpectCreateExpand()); CheckPlan<TypeParam>(query, storage, ExpectCreateNode(), ExpectCreateExpand(), ExpectCreateExpand(),
ExpectEmptyResult());
} }
TYPED_TEST(TestPlanner, MatchWithSumWhereReturn) { TYPED_TEST(TestPlanner, MatchWithSumWhereReturn) {
@ -491,7 +496,7 @@ TYPED_TEST(TestPlanner, MatchWithCreate) {
AstStorage storage; AstStorage storage;
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), WITH("n", AS("a")), auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), WITH("n", AS("a")),
CREATE(PATTERN(NODE("a"), EDGE("r", Direction::OUT, {r_type}), NODE("b"))))); CREATE(PATTERN(NODE("a"), EDGE("r", Direction::OUT, {r_type}), NODE("b")))));
CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectProduce(), ExpectCreateExpand()); CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectProduce(), ExpectCreateExpand(), ExpectEmptyResult());
} }
TYPED_TEST(TestPlanner, MatchReturnSkipLimit) { TYPED_TEST(TestPlanner, MatchReturnSkipLimit) {
@ -597,7 +602,7 @@ TYPED_TEST(TestPlanner, CreateWithOrderByWhere) {
}); });
auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query);
CheckPlan(planner.plan(), symbol_table, ExpectCreateNode(), ExpectCreateExpand(), acc, ExpectProduce(), CheckPlan(planner.plan(), symbol_table, ExpectCreateNode(), ExpectCreateExpand(), acc, ExpectProduce(),
ExpectOrderBy(), ExpectFilter()); ExpectOrderBy(), ExpectFilter(), ExpectEmptyResult());
} }
TYPED_TEST(TestPlanner, ReturnAddSumCountOrderBy) { TYPED_TEST(TestPlanner, ReturnAddSumCountOrderBy) {
@ -854,7 +859,7 @@ TYPED_TEST(TestPlanner, UnwindMergeNodeProperty) {
auto *query = QUERY(SINGLE_QUERY(UNWIND(LIST(LITERAL(1)), AS("i")), MERGE(PATTERN(node_n)))); auto *query = QUERY(SINGLE_QUERY(UNWIND(LIST(LITERAL(1)), AS("i")), MERGE(PATTERN(node_n))));
std::list<BaseOpChecker *> on_match{new ExpectScanAll(), new ExpectFilter()}; std::list<BaseOpChecker *> on_match{new ExpectScanAll(), new ExpectFilter()};
std::list<BaseOpChecker *> on_create{new ExpectCreateNode()}; std::list<BaseOpChecker *> on_create{new ExpectCreateNode()};
CheckPlan<TypeParam>(query, storage, ExpectUnwind(), ExpectMerge(on_match, on_create)); CheckPlan<TypeParam>(query, storage, ExpectUnwind(), ExpectMerge(on_match, on_create), ExpectEmptyResult());
DeleteListContent(&on_match); DeleteListContent(&on_match);
DeleteListContent(&on_create); DeleteListContent(&on_create);
} }
@ -874,7 +879,7 @@ TYPED_TEST(TestPlanner, UnwindMergeNodePropertyWithIndex) {
std::list<BaseOpChecker *> on_create{new ExpectCreateNode()}; std::list<BaseOpChecker *> on_create{new ExpectCreateNode()};
auto symbol_table = memgraph::query::MakeSymbolTable(query); auto symbol_table = memgraph::query::MakeSymbolTable(query);
auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query);
CheckPlan(planner.plan(), symbol_table, ExpectUnwind(), ExpectMerge(on_match, on_create)); CheckPlan(planner.plan(), symbol_table, ExpectUnwind(), ExpectMerge(on_match, on_create), ExpectEmptyResult());
DeleteListContent(&on_match); DeleteListContent(&on_match);
DeleteListContent(&on_create); DeleteListContent(&on_create);
} }
@ -1637,7 +1642,7 @@ TYPED_TEST(TestPlanner, Foreach) {
auto create = ExpectCreateNode(); auto create = ExpectCreateNode();
std::list<BaseOpChecker *> updates{&create}; std::list<BaseOpChecker *> updates{&create};
std::list<BaseOpChecker *> input; std::list<BaseOpChecker *> input;
CheckPlan<TypeParam>(query, storage, ExpectForeach(input, updates)); CheckPlan<TypeParam>(query, storage, ExpectForeach(input, updates), ExpectEmptyResult());
} }
{ {
auto *i = NEXPR("i", IDENT("i")); auto *i = NEXPR("i", IDENT("i"));
@ -1645,7 +1650,7 @@ TYPED_TEST(TestPlanner, Foreach) {
auto del = ExpectDelete(); auto del = ExpectDelete();
std::list<BaseOpChecker *> updates{&del}; std::list<BaseOpChecker *> updates{&del};
std::list<BaseOpChecker *> input; std::list<BaseOpChecker *> input;
CheckPlan<TypeParam>(query, storage, ExpectForeach({input}, updates)); CheckPlan<TypeParam>(query, storage, ExpectForeach({input}, updates), ExpectEmptyResult());
} }
{ {
auto prop = dba.Property("prop"); auto prop = dba.Property("prop");
@ -1654,7 +1659,7 @@ TYPED_TEST(TestPlanner, Foreach) {
auto set_prop = ExpectSetProperty(); auto set_prop = ExpectSetProperty();
std::list<BaseOpChecker *> updates{&set_prop}; std::list<BaseOpChecker *> updates{&set_prop};
std::list<BaseOpChecker *> input; std::list<BaseOpChecker *> input;
CheckPlan<TypeParam>(query, storage, ExpectForeach({input}, updates)); CheckPlan<TypeParam>(query, storage, ExpectForeach({input}, updates), ExpectEmptyResult());
} }
{ {
auto *i = NEXPR("i", IDENT("i")); auto *i = NEXPR("i", IDENT("i"));
@ -1666,7 +1671,7 @@ TYPED_TEST(TestPlanner, Foreach) {
std::list<BaseOpChecker *> nested_updates{{&create, &del}}; std::list<BaseOpChecker *> nested_updates{{&create, &del}};
auto nested_foreach = ExpectForeach(input, nested_updates); auto nested_foreach = ExpectForeach(input, nested_updates);
std::list<BaseOpChecker *> updates{&nested_foreach}; std::list<BaseOpChecker *> updates{&nested_foreach};
CheckPlan<TypeParam>(query, storage, ExpectForeach(input, updates)); CheckPlan<TypeParam>(query, storage, ExpectForeach(input, updates), ExpectEmptyResult());
} }
{ {
auto *i = NEXPR("i", IDENT("i")); auto *i = NEXPR("i", IDENT("i"));
@ -1678,7 +1683,7 @@ TYPED_TEST(TestPlanner, Foreach) {
std::list<BaseOpChecker *> input{&input_op}; std::list<BaseOpChecker *> input{&input_op};
auto *query = auto *query =
QUERY(SINGLE_QUERY(FOREACH(i, {CREATE(PATTERN(NODE("n")))}), FOREACH(j, {CREATE(PATTERN(NODE("n")))}))); QUERY(SINGLE_QUERY(FOREACH(i, {CREATE(PATTERN(NODE("n")))}), FOREACH(j, {CREATE(PATTERN(NODE("n")))})));
CheckPlan<TypeParam>(query, storage, ExpectForeach(input, updates)); CheckPlan<TypeParam>(query, storage, ExpectForeach(input, updates), ExpectEmptyResult());
} }
} }
} // namespace } // namespace

View File

@ -66,6 +66,7 @@ class PlanChecker : public virtual HierarchicalLogicalOperatorVisitor {
PRE_VISIT(ExpandVariable); PRE_VISIT(ExpandVariable);
PRE_VISIT(Filter); PRE_VISIT(Filter);
PRE_VISIT(ConstructNamedPath); PRE_VISIT(ConstructNamedPath);
PRE_VISIT(EmptyResult);
PRE_VISIT(Produce); PRE_VISIT(Produce);
PRE_VISIT(SetProperty); PRE_VISIT(SetProperty);
PRE_VISIT(SetProperties); PRE_VISIT(SetProperties);
@ -143,6 +144,7 @@ using ExpectExpand = OpChecker<Expand>;
using ExpectFilter = OpChecker<Filter>; using ExpectFilter = OpChecker<Filter>;
using ExpectConstructNamedPath = OpChecker<ConstructNamedPath>; using ExpectConstructNamedPath = OpChecker<ConstructNamedPath>;
using ExpectProduce = OpChecker<Produce>; using ExpectProduce = OpChecker<Produce>;
using ExpectEmptyResult = OpChecker<EmptyResult>;
using ExpectSetProperty = OpChecker<SetProperty>; using ExpectSetProperty = OpChecker<SetProperty>;
using ExpectSetProperties = OpChecker<SetProperties>; using ExpectSetProperties = OpChecker<SetProperties>;
using ExpectSetLabels = OpChecker<SetLabels>; using ExpectSetLabels = OpChecker<SetLabels>;