Fix cursor exhaustion by adding EmptyResult operator (#667)
This commit is contained in:
parent
d6d4153fb7
commit
0f77c85824
@ -114,6 +114,7 @@ extern const Event UnionOperator;
|
||||
extern const Event CartesianOperator;
|
||||
extern const Event CallProcedureOperator;
|
||||
extern const Event ForeachOperator;
|
||||
extern const Event EmptyResultOperator;
|
||||
} // namespace EventCounter
|
||||
|
||||
namespace memgraph::query::plan {
|
||||
@ -3058,6 +3059,56 @@ void EdgeUniquenessFilter::EdgeUniquenessFilterCursor::Shutdown() { input_cursor
|
||||
|
||||
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,
|
||||
bool advance_command)
|
||||
: input_(input), symbols_(symbols), advance_command_(advance_command) {}
|
||||
|
@ -132,6 +132,7 @@ class Cartesian;
|
||||
class CallProcedure;
|
||||
class LoadCsv;
|
||||
class Foreach;
|
||||
class EmptyResult;
|
||||
|
||||
using LogicalOperatorCompositeVisitor = utils::CompositeVisitor<
|
||||
Once, CreateNode, CreateExpand, ScanAll, ScanAllByLabel,
|
||||
@ -140,7 +141,7 @@ using LogicalOperatorCompositeVisitor = utils::CompositeVisitor<
|
||||
Expand, ExpandVariable, ConstructNamedPath, Filter, Produce, Delete,
|
||||
SetProperty, SetProperties, SetLabels, RemoveProperty, RemoveLabels,
|
||||
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>;
|
||||
|
||||
@ -1554,6 +1555,41 @@ edge lists).")
|
||||
(:serialize (:slk))
|
||||
(: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)
|
||||
((input "std::shared_ptr<LogicalOperator>" :scope :public
|
||||
:slk-save #'slk-save-operator-pointer
|
||||
|
@ -156,6 +156,7 @@ PRE_VISIT(RemoveProperty);
|
||||
PRE_VISIT(RemoveLabels);
|
||||
PRE_VISIT(EdgeUniquenessFilter);
|
||||
PRE_VISIT(Accumulate);
|
||||
PRE_VISIT(EmptyResult);
|
||||
|
||||
bool PlanPrinter::PreVisit(query::plan::Aggregate &op) {
|
||||
WithPrintLn([&](auto &out) {
|
||||
@ -705,6 +706,17 @@ bool PlanToJsonVisitor::PreVisit(EdgeUniquenessFilter &op) {
|
||||
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) {
|
||||
json self;
|
||||
self["name"] = "Accumulate";
|
||||
|
@ -80,6 +80,7 @@ class PlanPrinter : public virtual HierarchicalLogicalOperatorVisitor {
|
||||
bool PreVisit(Optional &) override;
|
||||
bool PreVisit(Cartesian &) override;
|
||||
|
||||
bool PreVisit(EmptyResult &) override;
|
||||
bool PreVisit(Produce &) override;
|
||||
bool PreVisit(Accumulate &) override;
|
||||
bool PreVisit(Aggregate &) override;
|
||||
@ -195,6 +196,7 @@ class PlanToJsonVisitor : public virtual HierarchicalLogicalOperatorVisitor {
|
||||
bool PreVisit(ScanAllByLabelProperty &) override;
|
||||
bool PreVisit(ScanAllById &) override;
|
||||
|
||||
bool PreVisit(EmptyResult &) override;
|
||||
bool PreVisit(Produce &) override;
|
||||
bool PreVisit(Accumulate &) override;
|
||||
bool PreVisit(Aggregate &) override;
|
||||
|
@ -11,10 +11,11 @@
|
||||
|
||||
#include "query/plan/read_write_type_checker.hpp"
|
||||
|
||||
#define PRE_VISIT(TOp, RWType, continue_visiting) \
|
||||
bool ReadWriteTypeChecker::PreVisit(TOp &op) { \
|
||||
UpdateType(RWType); \
|
||||
return continue_visiting; \
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define PRE_VISIT(TOp, RWType, continue_visiting) \
|
||||
bool ReadWriteTypeChecker::PreVisit(TOp &) { /*NOLINT(bugprone-macro-parentheses)*/ \
|
||||
UpdateType(RWType); \
|
||||
return continue_visiting; \
|
||||
}
|
||||
|
||||
namespace memgraph::query::plan {
|
||||
@ -54,6 +55,7 @@ bool ReadWriteTypeChecker::PreVisit(Cartesian &op) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PRE_VISIT(EmptyResult, RWType::NONE, true)
|
||||
PRE_VISIT(Produce, RWType::NONE, true)
|
||||
PRE_VISIT(Accumulate, RWType::NONE, true)
|
||||
PRE_VISIT(Aggregate, RWType::NONE, true)
|
||||
@ -86,7 +88,7 @@ bool ReadWriteTypeChecker::PreVisit([[maybe_unused]] Foreach &op) {
|
||||
|
||||
#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) {
|
||||
// Update type only if it's not the NONE type and the current operator's type
|
||||
|
@ -73,6 +73,7 @@ class ReadWriteTypeChecker : public virtual HierarchicalLogicalOperatorVisitor {
|
||||
bool PreVisit(Optional &) override;
|
||||
bool PreVisit(Cartesian &) override;
|
||||
|
||||
bool PreVisit(EmptyResult &) override;
|
||||
bool PreVisit(Produce &) override;
|
||||
bool PreVisit(Accumulate &) override;
|
||||
bool PreVisit(Aggregate &) override;
|
||||
|
@ -298,6 +298,15 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor {
|
||||
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 {
|
||||
prev_ops_.push_back(&op);
|
||||
return true;
|
||||
|
@ -180,7 +180,7 @@ class RuleBasedPlanner {
|
||||
}
|
||||
}
|
||||
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");
|
||||
if (auto *ret = utils::Downcast<Return>(clause)) {
|
||||
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);
|
||||
input_op =
|
||||
std::make_unique<plan::Unwind>(std::move(input_op), unwind->named_expression_->expression_, symbol);
|
||||
|
||||
} else if (auto *call_proc = utils::Downcast<query::CallProcedure>(clause)) {
|
||||
std::vector<Symbol> result_symbols;
|
||||
result_symbols.reserve(call_proc->result_identifiers_.size());
|
||||
@ -224,6 +225,7 @@ class RuleBasedPlanner {
|
||||
input_op =
|
||||
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);
|
||||
|
||||
} else if (auto *foreach = utils::Downcast<query::Foreach>(clause)) {
|
||||
is_write = true;
|
||||
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;
|
||||
}
|
||||
|
||||
@ -418,7 +424,8 @@ class RuleBasedPlanner {
|
||||
std::optional<ExpansionLambda> weight_lambda;
|
||||
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),
|
||||
symbol_table.at(*edge->weight_lambda_.inner_node),
|
||||
edge->weight_lambda_.expression});
|
||||
|
@ -37,6 +37,7 @@
|
||||
M(RemovePropertyOperator, "Number of times RemoveProperty operator was used.") \
|
||||
M(RemoveLabelsOperator, "Number of times RemoveLabels 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(AggregateOperator, "Number of times Aggregate operator was used.") \
|
||||
M(SkipOperator, "Number of times Skip operator was used.") \
|
||||
|
@ -97,6 +97,22 @@ Feature: Update clauses
|
||||
| a | b | c |
|
||||
| (: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
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
|
@ -853,7 +853,7 @@ TEST_F(InterpreterTest, ProfileQueryWithLiterals) {
|
||||
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"};
|
||||
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());
|
||||
auto expected_it = expected_rows.begin();
|
||||
for (const auto &row : stream.GetResults()) {
|
||||
|
@ -121,14 +121,14 @@ TYPED_TEST(TestPlanner, CreateExpand) {
|
||||
FakeDbAccessor dba;
|
||||
auto relationship = "relationship";
|
||||
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) {
|
||||
// Test CREATE (n), (m)
|
||||
AstStorage storage;
|
||||
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) {
|
||||
@ -138,7 +138,8 @@ TYPED_TEST(TestPlanner, CreateNodeExpandNode) {
|
||||
auto relationship = "rel";
|
||||
auto *query = QUERY(SINGLE_QUERY(
|
||||
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) {
|
||||
@ -148,7 +149,8 @@ TYPED_TEST(TestPlanner, CreateNamedPattern) {
|
||||
auto relationship = "rel";
|
||||
auto *query =
|
||||
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) {
|
||||
@ -158,7 +160,7 @@ TYPED_TEST(TestPlanner, MatchCreateExpand) {
|
||||
auto relationship = "relationship";
|
||||
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))),
|
||||
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) {
|
||||
@ -260,7 +262,7 @@ TYPED_TEST(TestPlanner, MatchDelete) {
|
||||
// Test MATCH (n) DELETE n
|
||||
AstStorage storage;
|
||||
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) {
|
||||
@ -271,7 +273,8 @@ TYPED_TEST(TestPlanner, MatchNodeSet) {
|
||||
auto label = "label";
|
||||
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), SET(PROPERTY_LOOKUP("n", prop), LITERAL(42)),
|
||||
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) {
|
||||
@ -282,7 +285,8 @@ TYPED_TEST(TestPlanner, MatchRemove) {
|
||||
auto label = "label";
|
||||
auto *query =
|
||||
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) {
|
||||
@ -387,7 +391,8 @@ TYPED_TEST(TestPlanner, CreateMultiExpand) {
|
||||
AstStorage storage;
|
||||
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")))));
|
||||
CheckPlan<TypeParam>(query, storage, ExpectCreateNode(), ExpectCreateExpand(), ExpectCreateExpand());
|
||||
CheckPlan<TypeParam>(query, storage, ExpectCreateNode(), ExpectCreateExpand(), ExpectCreateExpand(),
|
||||
ExpectEmptyResult());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestPlanner, MatchWithSumWhereReturn) {
|
||||
@ -491,7 +496,7 @@ TYPED_TEST(TestPlanner, MatchWithCreate) {
|
||||
AstStorage storage;
|
||||
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")))));
|
||||
CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectProduce(), ExpectCreateExpand());
|
||||
CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectProduce(), ExpectCreateExpand(), ExpectEmptyResult());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestPlanner, MatchReturnSkipLimit) {
|
||||
@ -597,7 +602,7 @@ TYPED_TEST(TestPlanner, CreateWithOrderByWhere) {
|
||||
});
|
||||
auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query);
|
||||
CheckPlan(planner.plan(), symbol_table, ExpectCreateNode(), ExpectCreateExpand(), acc, ExpectProduce(),
|
||||
ExpectOrderBy(), ExpectFilter());
|
||||
ExpectOrderBy(), ExpectFilter(), ExpectEmptyResult());
|
||||
}
|
||||
|
||||
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))));
|
||||
std::list<BaseOpChecker *> on_match{new ExpectScanAll(), new ExpectFilter()};
|
||||
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_create);
|
||||
}
|
||||
@ -874,7 +879,7 @@ TYPED_TEST(TestPlanner, UnwindMergeNodePropertyWithIndex) {
|
||||
std::list<BaseOpChecker *> on_create{new ExpectCreateNode()};
|
||||
auto symbol_table = memgraph::query::MakeSymbolTable(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_create);
|
||||
}
|
||||
@ -1637,7 +1642,7 @@ TYPED_TEST(TestPlanner, Foreach) {
|
||||
auto create = ExpectCreateNode();
|
||||
std::list<BaseOpChecker *> updates{&create};
|
||||
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"));
|
||||
@ -1645,7 +1650,7 @@ TYPED_TEST(TestPlanner, Foreach) {
|
||||
auto del = ExpectDelete();
|
||||
std::list<BaseOpChecker *> updates{&del};
|
||||
std::list<BaseOpChecker *> input;
|
||||
CheckPlan<TypeParam>(query, storage, ExpectForeach({input}, updates));
|
||||
CheckPlan<TypeParam>(query, storage, ExpectForeach({input}, updates), ExpectEmptyResult());
|
||||
}
|
||||
{
|
||||
auto prop = dba.Property("prop");
|
||||
@ -1654,7 +1659,7 @@ TYPED_TEST(TestPlanner, Foreach) {
|
||||
auto set_prop = ExpectSetProperty();
|
||||
std::list<BaseOpChecker *> updates{&set_prop};
|
||||
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"));
|
||||
@ -1666,7 +1671,7 @@ TYPED_TEST(TestPlanner, Foreach) {
|
||||
std::list<BaseOpChecker *> nested_updates{{&create, &del}};
|
||||
auto nested_foreach = ExpectForeach(input, nested_updates);
|
||||
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"));
|
||||
@ -1678,7 +1683,7 @@ TYPED_TEST(TestPlanner, Foreach) {
|
||||
std::list<BaseOpChecker *> input{&input_op};
|
||||
auto *query =
|
||||
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
|
||||
|
@ -66,6 +66,7 @@ class PlanChecker : public virtual HierarchicalLogicalOperatorVisitor {
|
||||
PRE_VISIT(ExpandVariable);
|
||||
PRE_VISIT(Filter);
|
||||
PRE_VISIT(ConstructNamedPath);
|
||||
PRE_VISIT(EmptyResult);
|
||||
PRE_VISIT(Produce);
|
||||
PRE_VISIT(SetProperty);
|
||||
PRE_VISIT(SetProperties);
|
||||
@ -143,6 +144,7 @@ using ExpectExpand = OpChecker<Expand>;
|
||||
using ExpectFilter = OpChecker<Filter>;
|
||||
using ExpectConstructNamedPath = OpChecker<ConstructNamedPath>;
|
||||
using ExpectProduce = OpChecker<Produce>;
|
||||
using ExpectEmptyResult = OpChecker<EmptyResult>;
|
||||
using ExpectSetProperty = OpChecker<SetProperty>;
|
||||
using ExpectSetProperties = OpChecker<SetProperties>;
|
||||
using ExpectSetLabels = OpChecker<SetLabels>;
|
||||
|
Loading…
Reference in New Issue
Block a user