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 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) {}
|
||||||
|
@ -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
|
||||||
|
@ -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";
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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});
|
||||||
|
@ -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.") \
|
||||||
|
@ -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:
|
||||||
|
@ -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()) {
|
||||||
|
@ -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
|
||||||
|
@ -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>;
|
||||||
|
Loading…
Reference in New Issue
Block a user