From 22e3164e60b3c758402b0d62fb19e3c2825ba300 Mon Sep 17 00:00:00 2001 From: gvolfing Date: Mon, 28 Nov 2022 16:18:28 +0100 Subject: [PATCH] Add missing license --- tests/unit/CMakeLists.txt | 3 + tests/unit/query_common.hpp | 255 ++++ tests/unit/query_plan_checker.hpp | 140 ++- tests/unit/query_plan_checker_v2.hpp | 559 +++++++++ tests/unit/query_plan_v2.cpp | 1664 ++++++++++++++++++++++++++ 5 files changed, 2620 insertions(+), 1 deletion(-) create mode 100644 tests/unit/query_plan_checker_v2.hpp create mode 100644 tests/unit/query_plan_v2.cpp diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 0e5824318..d9f9df875 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -93,6 +93,9 @@ target_link_libraries(${test_prefix}query_expression_evaluator mg-query) add_unit_test(query_plan.cpp) target_link_libraries(${test_prefix}query_plan mg-query) +add_unit_test(query_plan_v2.cpp) +target_link_libraries(${test_prefix}query_plan_v2 mg-query-v2) + add_unit_test(query_plan_accumulate_aggregate.cpp) target_link_libraries(${test_prefix}query_plan_accumulate_aggregate mg-query) diff --git a/tests/unit/query_common.hpp b/tests/unit/query_common.hpp index 903cacf12..4697124ef 100644 --- a/tests/unit/query_common.hpp +++ b/tests/unit/query_common.hpp @@ -46,6 +46,8 @@ #include "storage/v2/id_types.hpp" #include "utils/string.hpp" +#include "query/v2/frontend/ast/ast.hpp" + namespace memgraph::query { namespace test_common { @@ -81,12 +83,39 @@ std::string ToString(NamedExpression *expr) { struct OrderBy { std::vector expressions; }; + +// new stuff begin + +struct OrderByv2 { + std::vector expressions; +}; + +// new stuff end + struct Skip { Expression *expression = nullptr; }; + +// new stuff begin + +struct Skipv2 { + query::v2::Expression *expression = nullptr; +}; + +// new stuff end + struct Limit { Expression *expression = nullptr; }; + +// new stuff begin + +struct Limitv2 { + query::v2::Expression *expression = nullptr; +}; + +// new stuff end + struct OnMatch { std::vector set; }; @@ -153,6 +182,42 @@ auto GetPropertyLookup(AstStorage &storage, TDbAccessor &, Expression *expr, return storage.Create(expr, storage.GetPropertyIx(prop_pair.first)); } +// new stuff begin + +template +auto GetPropertyLookup(memgraph::query::v2::AstStorage &storage, TDbAccessor &dba, const std::string &name, + memgraph::storage::v3::PropertyId property) { + return storage.Create(storage.Create(name), + storage.GetPropertyIx(dba.PropertyToName(property))); +} + +template +auto GetPropertyLookup(memgraph::query::v2::AstStorage &storage, TDbAccessor &dba, + memgraph::query::v2::Expression *expr, memgraph::storage::v3::PropertyId property) { + return storage.Create(expr, storage.GetPropertyIx(dba.PropertyToName(property))); +} + +template +auto GetPropertyLookup(memgraph::query::v2::AstStorage &storage, TDbAccessor &dba, + memgraph::query::v2::Expression *expr, const std::string &property) { + return storage.Create(expr, storage.GetPropertyIx(property)); +} + +template +auto GetPropertyLookup(memgraph::query::v2::AstStorage &storage, TDbAccessor &, const std::string &name, + const std::pair &prop_pair) { + return storage.Create(storage.Create(name), + storage.GetPropertyIx(prop_pair.first)); +} + +template +auto GetPropertyLookup(memgraph::query::v2::AstStorage &storage, TDbAccessor &, memgraph::query::v2::Expression *expr, + const std::pair &prop_pair) { + return storage.Create(expr, storage.GetPropertyIx(prop_pair.first)); +} + +// new stuff end + /// Create an EdgeAtom with given name, direction and edge_type. /// /// Name is used to create the Identifier which is assigned to the edge. @@ -211,6 +276,15 @@ auto GetNode(AstStorage &storage, const std::string &name, std::optional label = std::nullopt) { + auto node = storage.Create(storage.Create(name)); + if (label) node->labels_.emplace_back(storage.GetLabelIx(*label)); + return node; +} +// new stuff end + /// Create a Pattern with given atoms. auto GetPattern(AstStorage &storage, std::vector atoms) { auto pattern = storage.Create(); @@ -227,6 +301,26 @@ auto GetPattern(AstStorage &storage, const std::string &name, std::vector atoms) { + auto pattern = storage.Create(); + pattern->identifier_ = storage.Create(memgraph::utils::RandomString(20), false); + pattern->atoms_.insert(pattern->atoms_.begin(), atoms.begin(), atoms.end()); + return pattern; +} + +/// Create a Pattern with given name and atoms. +auto GetPattern(memgraph::query::v2::AstStorage &storage, const std::string &name, + std::vector atoms) { + auto pattern = storage.Create(); + pattern->identifier_ = storage.Create(name, true); + pattern->atoms_.insert(pattern->atoms_.begin(), atoms.begin(), atoms.end()); + return pattern; +} + +// new stuff end + /// This function fills an AST node which with given patterns. /// /// The function is most commonly used to create Match and Create clauses. @@ -236,6 +330,16 @@ auto GetWithPatterns(TWithPatterns *with_patterns, std::vector patter return with_patterns; } +// new stuff begin + +template +auto GetWithPatterns(TWithPatterns *with_patterns, std::vector patterns) { + with_patterns->patterns_.insert(with_patterns->patterns_.begin(), patterns.begin(), patterns.end()); + return with_patterns; +} + +// new stuff end + /// Create a query with given clauses. auto GetSingleQuery(SingleQuery *single_query, Clause *clause) { @@ -271,6 +375,45 @@ auto GetSingleQuery(SingleQuery *single_query, Clause *clause, T *...clauses) { return GetSingleQuery(single_query, clauses...); } +// new stuff begin + +auto GetSingleQuery(query::v2::SingleQuery *single_query, query::v2::Clause *clause) { + single_query->clauses_.emplace_back(clause); + return single_query; +} +auto GetSingleQuery(query::v2::SingleQuery *single_query, query::v2::Match *match, query::v2::Where *where) { + match->where_ = where; + single_query->clauses_.emplace_back(match); + return single_query; +} +auto GetSingleQuery(query::v2::SingleQuery *single_query, query::v2::With *with, query::v2::Where *where) { + with->where_ = where; + single_query->clauses_.emplace_back(with); + return single_query; +} +template +auto GetSingleQuery(query::v2::SingleQuery *single_query, query::v2::Match *match, query::v2::Where *where, + T *...clauses) { + match->where_ = where; + single_query->clauses_.emplace_back(match); + return GetSingleQuery(single_query, clauses...); +} +template +auto GetSingleQuery(query::v2::SingleQuery *single_query, query::v2::With *with, query::v2::Where *where, + T *...clauses) { + with->where_ = where; + single_query->clauses_.emplace_back(with); + return GetSingleQuery(single_query, clauses...); +} + +template +auto GetSingleQuery(query::v2::SingleQuery *single_query, query::v2::Clause *clause, T *...clauses) { + single_query->clauses_.emplace_back(clause); + return GetSingleQuery(single_query, clauses...); +} + +// new stuff end + auto GetCypherUnion(CypherUnion *cypher_union, SingleQuery *single_query) { cypher_union->single_query_ = single_query; return cypher_union; @@ -290,6 +433,24 @@ auto GetQuery(AstStorage &storage, SingleQuery *single_query, T *...cypher_union return query; } +// new stuff begin + +auto GetQuery(query::v2::AstStorage &storage, query::v2::SingleQuery *single_query) { + auto *query = storage.Create(); + query->single_query_ = single_query; + return query; +} + +template +auto GetQuery(query::v2::AstStorage &storage, query::v2::SingleQuery *single_query, T *...cypher_unions) { + auto *query = storage.Create(); + query->single_query_ = single_query; + query->cypher_unions_ = std::vector{cypher_unions...}; + return query; +} + +// new stuff end + // Helper functions for constructing RETURN and WITH clauses. void FillReturnBody(AstStorage &, ReturnBody &body, NamedExpression *named_expr) { body.named_expressions.emplace_back(named_expr); @@ -353,6 +514,80 @@ void FillReturnBody(AstStorage &storage, ReturnBody &body, const std::string &na FillReturnBody(storage, body, rest...); } +// new stuff begin + +void FillReturnBody(query::v2::AstStorage &, query::v2::ReturnBody &body, query::v2::NamedExpression *named_expr) { + body.named_expressions.emplace_back(named_expr); +} +void FillReturnBody(query::v2::AstStorage &storage, query::v2::ReturnBody &body, const std::string &name) { + if (name == "*") { + body.all_identifiers = true; + } else { + auto *ident = storage.Create(name); + auto *named_expr = storage.Create(name, ident); + body.named_expressions.emplace_back(named_expr); + } +} +void FillReturnBody(query::v2::AstStorage &, query::v2::ReturnBody &body, Limitv2 limit) { + body.limit = limit.expression; +} +void FillReturnBody(query::v2::AstStorage &, query::v2::ReturnBody &body, Skipv2 skip, Limitv2 limit = Limitv2{}) { + body.skip = skip.expression; + body.limit = limit.expression; +} +void FillReturnBody(query::v2::AstStorage &, query::v2::ReturnBody &body, OrderByv2 order_by, + Limitv2 limit = Limitv2{}) { + body.order_by = order_by.expressions; + body.limit = limit.expression; +} +void FillReturnBody(query::v2::AstStorage &, query::v2::ReturnBody &body, OrderByv2 order_by, Skipv2 skip, + Limitv2 limit = Limitv2{}) { + body.order_by = order_by.expressions; + body.skip = skip.expression; + body.limit = limit.expression; +} +void FillReturnBody(query::v2::AstStorage &, query::v2::ReturnBody &body, query::v2::Expression *expr, + query::v2::NamedExpression *named_expr) { + // This overload supports `RETURN(expr, AS(name))` construct, since + // NamedExpression does not inherit Expression. + named_expr->expression_ = expr; + body.named_expressions.emplace_back(named_expr); +} +void FillReturnBody(query::v2::AstStorage &storage, query::v2::ReturnBody &body, const std::string &name, + query::v2::NamedExpression *named_expr) { + named_expr->expression_ = storage.Create(name); + body.named_expressions.emplace_back(named_expr); +} +template +void FillReturnBody(query::v2::AstStorage &storage, query::v2::ReturnBody &body, query::v2::Expression *expr, + query::v2::NamedExpression *named_expr, T... rest) { + named_expr->expression_ = expr; + body.named_expressions.emplace_back(named_expr); + FillReturnBody(storage, body, rest...); +} +template +void FillReturnBody(query::v2::AstStorage &storage, query::v2::ReturnBody &body, query::v2::NamedExpression *named_expr, + T... rest) { + body.named_expressions.emplace_back(named_expr); + FillReturnBody(storage, body, rest...); +} +template +void FillReturnBody(query::v2::AstStorage &storage, query::v2::ReturnBody &body, const std::string &name, + query::v2::NamedExpression *named_expr, T... rest) { + named_expr->expression_ = storage.Create(name); + body.named_expressions.emplace_back(named_expr); + FillReturnBody(storage, body, rest...); +} +template +void FillReturnBody(query::v2::AstStorage &storage, query::v2::ReturnBody &body, const std::string &name, T... rest) { + auto *ident = storage.Create(name); + auto *named_expr = storage.Create(name, ident); + body.named_expressions.emplace_back(named_expr); + FillReturnBody(storage, body, rest...); +} + +// new stuff end + /// Create the return clause with given expressions. /// /// The supported expression combination of arguments is: @@ -374,6 +609,18 @@ auto GetReturn(AstStorage &storage, bool distinct, T... exprs) { return ret; } +// new stuff begin + +template +auto GetReturn(query::v2::AstStorage &storage, bool distinct, T... exprs) { + auto ret = storage.Create(); + ret->body_.distinct = distinct; + FillReturnBody(storage, ret->body_, exprs...); + return ret; +} + +// new stuff end + /// Create the with clause with given expressions. /// /// The supported expression combination is the same as for @c GetReturn. @@ -486,12 +733,16 @@ auto GetForeach(AstStorage &storage, NamedExpression *named_expr, const std::vec #define EDGE(...) memgraph::query::test_common::GetEdge(storage, __VA_ARGS__) #define EDGE_VARIABLE(...) memgraph::query::test_common::GetEdgeVariable(storage, __VA_ARGS__) #define PATTERN(...) memgraph::query::test_common::GetPattern(storage, {__VA_ARGS__}) +#define PATTERN(...) memgraph::query::test_common::GetPattern(storage, {__VA_ARGS__}) #define NAMED_PATTERN(name, ...) memgraph::query::test_common::GetPattern(storage, name, {__VA_ARGS__}) #define OPTIONAL_MATCH(...) \ memgraph::query::test_common::GetWithPatterns(storage.Create(true), {__VA_ARGS__}) #define MATCH(...) \ memgraph::query::test_common::GetWithPatterns(storage.Create(), {__VA_ARGS__}) +#define MATCH_V2(...) \ + memgraph::query::test_common::GetWithPatterns(storage.Create(), {__VA_ARGS__}) #define WHERE(expr) storage.Create((expr)) +#define WHERE_V2(expr) storage.Create((expr)) #define CREATE(...) \ memgraph::query::test_common::GetWithPatterns(storage.Create(), {__VA_ARGS__}) #define IDENT(...) storage.Create(__VA_ARGS__) @@ -501,6 +752,8 @@ auto GetForeach(AstStorage &storage, NamedExpression *named_expr, const std::vec storage.Create( \ std::unordered_map{__VA_ARGS__}) #define PROPERTY_PAIR(property_name) std::make_pair(property_name, dba.NameToProperty(property_name)) +#define PRIMARY_PROPERTY_PAIR(property_name) std::make_pair(property_name, dba.NameToPrimaryProperty(property_name)) +#define SECONDARY_PROPERTY_PAIR(property_name) std::make_pair(property_name, dba.NameToSecondaryProperty(property_name)) #define PROPERTY_LOOKUP(...) memgraph::query::test_common::GetPropertyLookup(storage, dba, __VA_ARGS__) #define PARAMETER_LOOKUP(token_position) storage.Create((token_position)) #define NEXPR(name, expr) storage.Create((name), (expr)) @@ -536,6 +789,8 @@ auto GetForeach(AstStorage &storage, NamedExpression *named_expr, const std::vec std::vector{(property)}) #define QUERY(...) memgraph::query::test_common::GetQuery(storage, __VA_ARGS__) #define SINGLE_QUERY(...) memgraph::query::test_common::GetSingleQuery(storage.Create(), __VA_ARGS__) +#define SINGLE_QUERY_V2(...) \ + memgraph::query::test_common::GetSingleQuery(storage.Create(), __VA_ARGS__) #define UNION(...) memgraph::query::test_common::GetCypherUnion(storage.Create(true), __VA_ARGS__) #define UNION_ALL(...) memgraph::query::test_common::GetCypherUnion(storage.Create(false), __VA_ARGS__) #define FOREACH(...) memgraph::query::test_common::GetForeach(storage, __VA_ARGS__) diff --git a/tests/unit/query_plan_checker.hpp b/tests/unit/query_plan_checker.hpp index 335b6ab2b..d04264c24 100644 --- a/tests/unit/query_plan_checker.hpp +++ b/tests/unit/query_plan_checker.hpp @@ -17,6 +17,7 @@ #include "query/plan/operator.hpp" #include "query/plan/planner.hpp" #include "query/plan/preprocess.hpp" +#include "query/v2/plan/operator.hpp" namespace memgraph::query::plan { @@ -90,7 +91,7 @@ class PlanChecker : public virtual HierarchicalLogicalOperatorVisitor { } PRE_VISIT(Unwind); PRE_VISIT(Distinct); - + bool PreVisit(Foreach &op) override { CheckOp(op); return false; @@ -336,6 +337,39 @@ class ExpectScanAllByLabelProperty : public OpChecker { memgraph::storage::PropertyId property_; }; +class ExpectScanAllByPrimaryKey : public OpChecker { + public: + ExpectScanAllByPrimaryKey(memgraph::storage::v3::LabelId label, const std::vector &properties) + : label_(label), properties_(properties) {} + + void ExpectOp(v2::plan::ScanAllByPrimaryKey &scan_all, const SymbolTable &) override { + EXPECT_EQ(scan_all.label_, label_); + // EXPECT_EQ(scan_all.property_, property_); + + // TODO(gvolfing) maybe assert the size of the 2 vectors. + // TODO(gvolfing) maybe use some std alg if Expression lets us. + + bool primary_property_match = true; + for (const auto &expected_prop : properties_) { + bool has_match = false; + for (const auto &prop : scan_all.primary_key_) { + if (typeid(prop).hash_code() == typeid(expected_prop).hash_code()) { + has_match = true; + } + } + if (!has_match) { + primary_property_match = false; + } + } + + EXPECT_TRUE(primary_property_match); + } + + private: + memgraph::storage::v3::LabelId label_; + std::vector properties_; +}; + class ExpectCartesian : public OpChecker { public: ExpectCartesian(const std::list> &left, @@ -486,4 +520,108 @@ class FakeDbAccessor { std::vector> label_property_index_; }; +// class FakeDistributedDbAccessor { +// public: +// int64_t VerticesCount(memgraph::storage::v3::LabelId label) const { +// auto found = label_index_.find(label); +// if (found != label_index_.end()) return found->second; +// return 0; +// } + +// int64_t VerticesCount(memgraph::storage::v3::LabelId label, memgraph::storage::PropertyId property) const { +// for (auto &index : label_property_index_) { +// if (std::get<0>(index) == label && std::get<1>(index) == property) { +// return std::get<2>(index); +// } +// } +// return 0; +// } + +// bool LabelIndexExists(memgraph::storage::v3::LabelId label) const { +// return label_index_.find(label) != label_index_.end(); +// } + +// bool LabelPropertyIndexExists(memgraph::storage::v3::LabelId label, memgraph::storage::PropertyId property) const { +// for (auto &index : label_property_index_) { +// if (std::get<0>(index) == label && std::get<1>(index) == property) { +// return true; +// } +// } +// return false; +// } + +// void SetIndexCount(memgraph::storage::v3::LabelId label, int64_t count) { label_index_[label] = count; } + +// void SetIndexCount(memgraph::storage::v3::LabelId label, memgraph::storage::PropertyId property, int64_t count) { +// for (auto &index : label_property_index_) { +// if (std::get<0>(index) == label && std::get<1>(index) == property) { +// std::get<2>(index) = count; +// return; +// } +// } +// label_property_index_.emplace_back(label, property, count); +// } + +// memgraph::storage::v3::LabelId NameToLabel(const std::string &name) { +// auto found = primary_labels_.find(name); +// if (found != primary_labels_.end()) return found->second; +// return primary_labels_.emplace(name, +// memgraph::storage::v3::LabelId::FromUint(primary_labels_.size())).first->second; +// } + +// memgraph::storage::v3::LabelId Label(const std::string &name) { return NameToLabel(name); } + +// memgraph::storage::EdgeTypeId NameToEdgeType(const std::string &name) { +// auto found = edge_types_.find(name); +// if (found != edge_types_.end()) return found->second; +// return edge_types_.emplace(name, memgraph::storage::EdgeTypeId::FromUint(edge_types_.size())).first->second; +// } + +// memgraph::storage::PropertyId NameToPrimaryProperty(const std::string &name) { +// auto found = primary_properties_.find(name); +// if (found != primary_properties_.end()) return found->second; +// return primary_properties_.emplace(name, +// memgraph::storage::PropertyId::FromUint(primary_properties_.size())).first->second; +// } + +// memgraph::storage::PropertyId NameToSecondaryProperty(const std::string &name) { +// auto found = secondary_properties_.find(name); +// if (found != secondary_properties_.end()) return found->second; +// return secondary_properties_.emplace(name, +// memgraph::storage::PropertyId::FromUint(secondary_properties_.size())).first->second; +// } + +// memgraph::storage::PropertyId PrimaryProperty(const std::string &name) { return NameToPrimaryProperty(name); } +// memgraph::storage::PropertyId SecondaryProperty(const std::string &name) { return NameToSecondaryProperty(name); } + +// std::string PrimaryPropertyToName(memgraph::storage::PropertyId property) const { +// for (const auto &kv : primary_properties_) { +// if (kv.second == property) return kv.first; +// } +// LOG_FATAL("Unable to find primary property name"); +// } + +// std::string SecondaryPropertyToName(memgraph::storage::PropertyId property) const { +// for (const auto &kv : secondary_properties_) { +// if (kv.second == property) return kv.first; +// } +// LOG_FATAL("Unable to find secondary property name"); +// } + +// std::string PrimaryPropertyName(memgraph::storage::PropertyId property) const { return +// PrimaryPropertyToName(property); } std::string SecondaryPropertyName(memgraph::storage::PropertyId property) const +// { return SecondaryPropertyToName(property); } + +// private: +// std::unordered_map primary_labels_; +// std::unordered_map secondary_labels_; +// std::unordered_map edge_types_; +// std::unordered_map primary_properties_; +// std::unordered_map secondary_properties_; + +// std::unordered_map label_index_; +// std::vector> +// label_property_index_; +// }; + } // namespace memgraph::query::plan diff --git a/tests/unit/query_plan_checker_v2.hpp b/tests/unit/query_plan_checker_v2.hpp new file mode 100644 index 000000000..6a6a12680 --- /dev/null +++ b/tests/unit/query_plan_checker_v2.hpp @@ -0,0 +1,559 @@ +// Copyright 2022 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include +#include + +#include "query/frontend/semantic/symbol_generator.hpp" +#include "query/frontend/semantic/symbol_table.hpp" +#include "query/v2/plan/operator.hpp" +#include "query/v2/plan/planner.hpp" +#include "query/v2/plan/preprocess.hpp" + +namespace memgraph::query::v2::plan { + +class BaseOpChecker { + public: + virtual ~BaseOpChecker() {} + + virtual void CheckOp(LogicalOperator &, const SymbolTable &) = 0; +}; + +class PlanChecker : public virtual HierarchicalLogicalOperatorVisitor { + public: + using HierarchicalLogicalOperatorVisitor::PostVisit; + using HierarchicalLogicalOperatorVisitor::PreVisit; + using HierarchicalLogicalOperatorVisitor::Visit; + + PlanChecker(const std::list> &checkers, const SymbolTable &symbol_table) + : symbol_table_(symbol_table) { + for (const auto &checker : checkers) checkers_.emplace_back(checker.get()); + } + + PlanChecker(const std::list &checkers, const SymbolTable &symbol_table) + : checkers_(checkers), symbol_table_(symbol_table) {} + +#define PRE_VISIT(TOp) \ + bool PreVisit(TOp &op) override { \ + CheckOp(op); \ + return true; \ + } + +#define VISIT(TOp) \ + bool Visit(TOp &op) override { \ + CheckOp(op); \ + return true; \ + } + + PRE_VISIT(CreateNode); + PRE_VISIT(CreateExpand); + PRE_VISIT(Delete); + PRE_VISIT(ScanAll); + PRE_VISIT(ScanAllByLabel); + PRE_VISIT(ScanAllByLabelPropertyValue); + PRE_VISIT(ScanAllByLabelPropertyRange); + PRE_VISIT(ScanAllByLabelProperty); + PRE_VISIT(Expand); + PRE_VISIT(ExpandVariable); + PRE_VISIT(Filter); + PRE_VISIT(ConstructNamedPath); + PRE_VISIT(Produce); + PRE_VISIT(SetProperty); + PRE_VISIT(SetProperties); + PRE_VISIT(SetLabels); + PRE_VISIT(RemoveProperty); + PRE_VISIT(RemoveLabels); + PRE_VISIT(EdgeUniquenessFilter); + PRE_VISIT(Accumulate); + PRE_VISIT(Aggregate); + PRE_VISIT(Skip); + PRE_VISIT(Limit); + PRE_VISIT(OrderBy); + bool PreVisit(Merge &op) override { + CheckOp(op); + op.input()->Accept(*this); + return false; + } + bool PreVisit(Optional &op) override { + CheckOp(op); + op.input()->Accept(*this); + return false; + } + PRE_VISIT(Unwind); + PRE_VISIT(Distinct); + + bool PreVisit(Foreach &op) override { + CheckOp(op); + return false; + } + + bool Visit(Once &) override { + // Ignore checking Once, it is implicitly at the end. + return true; + } + + bool PreVisit(Cartesian &op) override { + CheckOp(op); + return false; + } + + PRE_VISIT(CallProcedure); + +#undef PRE_VISIT +#undef VISIT + + void CheckOp(LogicalOperator &op) { + ASSERT_FALSE(checkers_.empty()); + checkers_.back()->CheckOp(op, symbol_table_); + checkers_.pop_back(); + } + + std::list checkers_; + const SymbolTable &symbol_table_; +}; + +template +class OpChecker : public BaseOpChecker { + public: + void CheckOp(LogicalOperator &op, const SymbolTable &symbol_table) override { + auto *expected_op = dynamic_cast(&op); + ASSERT_TRUE(expected_op) << "op is '" << op.GetTypeInfo().name << "' expected '" << TOp::kType.name << "'!"; + ExpectOp(*expected_op, symbol_table); + } + + virtual void ExpectOp(TOp &, const SymbolTable &) {} +}; + +// using ExpectScanAllByPrimaryKey = OpChecker; + +using ExpectCreateNode = OpChecker; +using ExpectCreateExpand = OpChecker; +using ExpectDelete = OpChecker; +using ExpectScanAll = OpChecker; +using ExpectScanAllByLabel = OpChecker; +// using ExpectScanAllById = OpChecker; +using ExpectExpand = OpChecker; +using ExpectFilter = OpChecker; +using ExpectConstructNamedPath = OpChecker; +using ExpectProduce = OpChecker; +using ExpectSetProperty = OpChecker; +using ExpectSetProperties = OpChecker; +using ExpectSetLabels = OpChecker; +using ExpectRemoveProperty = OpChecker; +using ExpectRemoveLabels = OpChecker; +using ExpectEdgeUniquenessFilter = OpChecker; +using ExpectSkip = OpChecker; +using ExpectLimit = OpChecker; +using ExpectOrderBy = OpChecker; +using ExpectUnwind = OpChecker; +using ExpectDistinct = OpChecker; + +// class ExpectForeach : public OpChecker { +// public: +// ExpectForeach(const std::list &input, const std::list &updates) +// : input_(input), updates_(updates) {} + +// void ExpectOp(Foreach &foreach, const SymbolTable &symbol_table) override { +// PlanChecker check_input(input_, symbol_table); +// foreach +// .input_->Accept(check_input); +// PlanChecker check_updates(updates_, symbol_table); +// foreach +// .update_clauses_->Accept(check_updates); +// } + +// private: +// std::list input_; +// std::list updates_; +// }; + +// class ExpectExpandVariable : public OpChecker { +// public: +// void ExpectOp(ExpandVariable &op, const SymbolTable &) override { +// EXPECT_EQ(op.type_, memgraph::query::EdgeAtom::Type::DEPTH_FIRST); +// } +// }; + +// class ExpectExpandBfs : public OpChecker { +// public: +// void ExpectOp(ExpandVariable &op, const SymbolTable &) override { +// EXPECT_EQ(op.type_, memgraph::query::EdgeAtom::Type::BREADTH_FIRST); +// } +// }; + +// class ExpectAccumulate : public OpChecker { +// public: +// explicit ExpectAccumulate(const std::unordered_set &symbols) : symbols_(symbols) {} + +// void ExpectOp(Accumulate &op, const SymbolTable &) override { +// std::unordered_set got_symbols(op.symbols_.begin(), op.symbols_.end()); +// EXPECT_EQ(symbols_, got_symbols); +// } + +// private: +// const std::unordered_set symbols_; +// }; + +// class ExpectAggregate : public OpChecker { +// public: +// ExpectAggregate(const std::vector &aggregations, +// const std::unordered_set &group_by) +// : aggregations_(aggregations), group_by_(group_by) {} + +// void ExpectOp(Aggregate &op, const SymbolTable &symbol_table) override { +// auto aggr_it = aggregations_.begin(); +// for (const auto &aggr_elem : op.aggregations_) { +// ASSERT_NE(aggr_it, aggregations_.end()); +// auto aggr = *aggr_it++; +// // TODO: Proper expression equality +// EXPECT_EQ(typeid(aggr_elem.value).hash_code(), typeid(aggr->expression1_).hash_code()); +// EXPECT_EQ(typeid(aggr_elem.key).hash_code(), typeid(aggr->expression2_).hash_code()); +// EXPECT_EQ(aggr_elem.op, aggr->op_); +// EXPECT_EQ(aggr_elem.output_sym, symbol_table.at(*aggr)); +// } +// EXPECT_EQ(aggr_it, aggregations_.end()); +// // TODO: Proper group by expression equality +// std::unordered_set got_group_by; +// for (auto *expr : op.group_by_) got_group_by.insert(typeid(*expr).hash_code()); +// std::unordered_set expected_group_by; +// for (auto *expr : group_by_) expected_group_by.insert(typeid(*expr).hash_code()); +// EXPECT_EQ(got_group_by, expected_group_by); +// } + +// private: +// std::vector aggregations_; +// std::unordered_set group_by_; +// }; + +// class ExpectMerge : public OpChecker { +// public: +// ExpectMerge(const std::list &on_match, const std::list &on_create) +// : on_match_(on_match), on_create_(on_create) {} + +// void ExpectOp(Merge &merge, const SymbolTable &symbol_table) override { +// PlanChecker check_match(on_match_, symbol_table); +// merge.merge_match_->Accept(check_match); +// PlanChecker check_create(on_create_, symbol_table); +// merge.merge_create_->Accept(check_create); +// } + +// private: +// const std::list &on_match_; +// const std::list &on_create_; +// }; + +// class ExpectOptional : public OpChecker { +// public: +// explicit ExpectOptional(const std::list &optional) : optional_(optional) {} + +// ExpectOptional(const std::vector &optional_symbols, const std::list &optional) +// : optional_symbols_(optional_symbols), optional_(optional) {} + +// void ExpectOp(Optional &optional, const SymbolTable &symbol_table) override { +// if (!optional_symbols_.empty()) { +// EXPECT_THAT(optional.optional_symbols_, testing::UnorderedElementsAreArray(optional_symbols_)); +// } +// PlanChecker check_optional(optional_, symbol_table); +// optional.optional_->Accept(check_optional); +// } + +// private: +// std::vector optional_symbols_; +// const std::list &optional_; +// }; + +// class ExpectScanAllByLabelPropertyValue : public OpChecker { +// public: +// ExpectScanAllByLabelPropertyValue(memgraph::storage::LabelId label, +// const std::pair &prop_pair, +// memgraph::query::Expression *expression) +// : label_(label), property_(prop_pair.second), expression_(expression) {} + +// void ExpectOp(ScanAllByLabelPropertyValue &scan_all, const SymbolTable &) override { +// EXPECT_EQ(scan_all.label_, label_); +// EXPECT_EQ(scan_all.property_, property_); +// // TODO: Proper expression equality +// EXPECT_EQ(typeid(scan_all.expression_).hash_code(), typeid(expression_).hash_code()); +// } + +// private: +// memgraph::storage::LabelId label_; +// memgraph::storage::PropertyId property_; +// memgraph::query::Expression *expression_; +// }; + +// class ExpectScanAllByLabelPropertyRange : public OpChecker { +// public: +// ExpectScanAllByLabelPropertyRange(memgraph::storage::LabelId label, memgraph::storage::PropertyId property, +// std::optional lower_bound, +// std::optional upper_bound) +// : label_(label), property_(property), lower_bound_(lower_bound), upper_bound_(upper_bound) {} + +// void ExpectOp(ScanAllByLabelPropertyRange &scan_all, const SymbolTable &) override { +// EXPECT_EQ(scan_all.label_, label_); +// EXPECT_EQ(scan_all.property_, property_); +// if (lower_bound_) { +// ASSERT_TRUE(scan_all.lower_bound_); +// // TODO: Proper expression equality +// EXPECT_EQ(typeid(scan_all.lower_bound_->value()).hash_code(), typeid(lower_bound_->value()).hash_code()); +// EXPECT_EQ(scan_all.lower_bound_->type(), lower_bound_->type()); +// } +// if (upper_bound_) { +// ASSERT_TRUE(scan_all.upper_bound_); +// // TODO: Proper expression equality +// EXPECT_EQ(typeid(scan_all.upper_bound_->value()).hash_code(), typeid(upper_bound_->value()).hash_code()); +// EXPECT_EQ(scan_all.upper_bound_->type(), upper_bound_->type()); +// } +// } + +// private: +// memgraph::storage::LabelId label_; +// memgraph::storage::PropertyId property_; +// std::optional lower_bound_; +// std::optional upper_bound_; +// }; + +// class ExpectScanAllByLabelProperty : public OpChecker { +// public: +// ExpectScanAllByLabelProperty(memgraph::storage::LabelId label, +// const std::pair &prop_pair) +// : label_(label), property_(prop_pair.second) {} + +// void ExpectOp(ScanAllByLabelProperty &scan_all, const SymbolTable &) override { +// EXPECT_EQ(scan_all.label_, label_); +// EXPECT_EQ(scan_all.property_, property_); +// } + +// private: +// memgraph::storage::LabelId label_; +// memgraph::storage::PropertyId property_; +// }; + +class ExpectScanAllByPrimaryKey : public OpChecker { + public: + ExpectScanAllByPrimaryKey(memgraph::storage::v3::LabelId label, const std::vector &properties) + : label_(label), properties_(properties) {} + + void ExpectOp(v2::plan::ScanAllByPrimaryKey &scan_all, const SymbolTable &) override { + EXPECT_EQ(scan_all.label_, label_); + // EXPECT_EQ(scan_all.property_, property_); + + // TODO(gvolfing) maybe assert the size of the 2 vectors. + // TODO(gvolfing) maybe use some std alg if Expression lets us. + + bool primary_property_match = true; + for (const auto &expected_prop : properties_) { + bool has_match = false; + for (const auto &prop : scan_all.primary_key_) { + if (typeid(prop).hash_code() == typeid(expected_prop).hash_code()) { + has_match = true; + } + } + if (!has_match) { + primary_property_match = false; + } + } + + EXPECT_TRUE(primary_property_match); + } + + private: + memgraph::storage::v3::LabelId label_; + std::vector properties_; +}; + +class ExpectCartesian : public OpChecker { + public: + ExpectCartesian(const std::list> &left, + const std::list> &right) + : left_(left), right_(right) {} + + void ExpectOp(Cartesian &op, const SymbolTable &symbol_table) override { + ASSERT_TRUE(op.left_op_); + PlanChecker left_checker(left_, symbol_table); + op.left_op_->Accept(left_checker); + ASSERT_TRUE(op.right_op_); + PlanChecker right_checker(right_, symbol_table); + op.right_op_->Accept(right_checker); + } + + private: + const std::list> &left_; + const std::list> &right_; +}; + +class ExpectCallProcedure : public OpChecker { + public: + ExpectCallProcedure(const std::string &name, const std::vector &args, + const std::vector &fields, const std::vector &result_syms) + : name_(name), args_(args), fields_(fields), result_syms_(result_syms) {} + + void ExpectOp(CallProcedure &op, const SymbolTable &symbol_table) override { + EXPECT_EQ(op.procedure_name_, name_); + EXPECT_EQ(op.arguments_.size(), args_.size()); + for (size_t i = 0; i < args_.size(); ++i) { + const auto *op_arg = op.arguments_[i]; + const auto *expected_arg = args_[i]; + // TODO: Proper expression equality + EXPECT_EQ(op_arg->GetTypeInfo(), expected_arg->GetTypeInfo()); + } + EXPECT_EQ(op.result_fields_, fields_); + EXPECT_EQ(op.result_symbols_, result_syms_); + } + + private: + std::string name_; + std::vector args_; + std::vector fields_; + std::vector result_syms_; +}; + +template +std::list> MakeCheckers(T arg) { + std::list> l; + l.emplace_back(std::make_unique(arg)); + return l; +} + +template +std::list> MakeCheckers(T arg, Rest &&...rest) { + auto l = MakeCheckers(std::forward(rest)...); + l.emplace_front(std::make_unique(arg)); + return std::move(l); +} + +template +TPlanner MakePlanner(TDbAccessor *dba, AstStorage &storage, SymbolTable &symbol_table, CypherQuery *query) { + auto planning_context = MakePlanningContext(&storage, &symbol_table, query, dba); + auto query_parts = CollectQueryParts(symbol_table, storage, query); + auto single_query_parts = query_parts.query_parts.at(0).single_query_parts; + return TPlanner(single_query_parts, planning_context); +} + +class FakeDistributedDbAccessor { + public: + int64_t VerticesCount(memgraph::storage::v3::LabelId label) const { + auto found = label_index_.find(label); + if (found != label_index_.end()) return found->second; + return 0; + } + + int64_t VerticesCount(memgraph::storage::v3::LabelId label, memgraph::storage::v3::PropertyId property) const { + for (auto &index : label_property_index_) { + if (std::get<0>(index) == label && std::get<1>(index) == property) { + return std::get<2>(index); + } + } + return 0; + } + + bool LabelIndexExists(memgraph::storage::v3::LabelId label) const { + return label_index_.find(label) != label_index_.end(); + } + + bool LabelPropertyIndexExists(memgraph::storage::v3::LabelId label, + memgraph::storage::v3::PropertyId property) const { + for (auto &index : label_property_index_) { + if (std::get<0>(index) == label && std::get<1>(index) == property) { + return true; + } + } + return false; + } + + void SetIndexCount(memgraph::storage::v3::LabelId label, int64_t count) { label_index_[label] = count; } + + void SetIndexCount(memgraph::storage::v3::LabelId label, memgraph::storage::v3::PropertyId property, int64_t count) { + for (auto &index : label_property_index_) { + if (std::get<0>(index) == label && std::get<1>(index) == property) { + std::get<2>(index) = count; + return; + } + } + label_property_index_.emplace_back(label, property, count); + } + + memgraph::storage::v3::LabelId NameToLabel(const std::string &name) { + auto found = primary_labels_.find(name); + if (found != primary_labels_.end()) return found->second; + return primary_labels_.emplace(name, memgraph::storage::v3::LabelId::FromUint(primary_labels_.size())) + .first->second; + } + + memgraph::storage::v3::LabelId Label(const std::string &name) { return NameToLabel(name); } + + memgraph::storage::v3::EdgeTypeId NameToEdgeType(const std::string &name) { + auto found = edge_types_.find(name); + if (found != edge_types_.end()) return found->second; + return edge_types_.emplace(name, memgraph::storage::v3::EdgeTypeId::FromUint(edge_types_.size())).first->second; + } + + memgraph::storage::v3::PropertyId NameToPrimaryProperty(const std::string &name) { + auto found = primary_properties_.find(name); + if (found != primary_properties_.end()) return found->second; + return primary_properties_.emplace(name, memgraph::storage::v3::PropertyId::FromUint(primary_properties_.size())) + .first->second; + } + + memgraph::storage::v3::PropertyId NameToSecondaryProperty(const std::string &name) { + auto found = secondary_properties_.find(name); + if (found != secondary_properties_.end()) return found->second; + return secondary_properties_ + .emplace(name, memgraph::storage::v3::PropertyId::FromUint(secondary_properties_.size())) + .first->second; + } + + memgraph::storage::v3::PropertyId PrimaryProperty(const std::string &name) { return NameToPrimaryProperty(name); } + memgraph::storage::v3::PropertyId SecondaryProperty(const std::string &name) { return NameToSecondaryProperty(name); } + + std::string PrimaryPropertyToName(memgraph::storage::v3::PropertyId property) const { + for (const auto &kv : primary_properties_) { + if (kv.second == property) return kv.first; + } + LOG_FATAL("Unable to find primary property name"); + } + + std::string SecondaryPropertyToName(memgraph::storage::v3::PropertyId property) const { + for (const auto &kv : secondary_properties_) { + if (kv.second == property) return kv.first; + } + LOG_FATAL("Unable to find secondary property name"); + } + + std::string PrimaryPropertyName(memgraph::storage::v3::PropertyId property) const { + return PrimaryPropertyToName(property); + } + std::string SecondaryPropertyName(memgraph::storage::v3::PropertyId property) const { + return SecondaryPropertyToName(property); + } + + memgraph::storage::v3::PropertyId NameToProperty(const std::string &name) { + return storage::v3::PropertyId::FromUint(0); + } + + std::vector ExtractPrimaryKey(storage::v3::LabelId label, + std::vector property_filters) { + return std::vector{}; + } + + private: + std::unordered_map primary_labels_; + std::unordered_map secondary_labels_; + std::unordered_map edge_types_; + std::unordered_map primary_properties_; + std::unordered_map secondary_properties_; + + std::unordered_map label_index_; + std::vector> + label_property_index_; +}; + +} // namespace memgraph::query::v2::plan diff --git a/tests/unit/query_plan_v2.cpp b/tests/unit/query_plan_v2.cpp new file mode 100644 index 000000000..009d0ca56 --- /dev/null +++ b/tests/unit/query_plan_v2.cpp @@ -0,0 +1,1664 @@ +// Copyright 2022 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include "query_plan_checker_v2.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "query/v2/frontend/ast/ast.hpp" +// #include "query/frontend/semantic/symbol_generator.hpp" +#include "expr/semantic/symbol_generator.hpp" +#include "query/frontend/semantic/symbol_table.hpp" +#include "query/v2/plan/operator.hpp" +#include "query/v2/plan/planner.hpp" + +#include "query_common.hpp" + +namespace memgraph::query { +::std::ostream &operator<<(::std::ostream &os, const Symbol &sym) { + return os << "Symbol{\"" << sym.name() << "\" [" << sym.position() << "] " << Symbol::TypeToString(sym.type()) << "}"; +} +} // namespace memgraph::query + +// using namespace memgraph::query::v2::plan; +using namespace memgraph::expr::plan; +using memgraph::query::AstStorage; +using memgraph::query::SingleQuery; +using memgraph::query::Symbol; +using memgraph::query::SymbolGenerator; +using memgraph::query::v2::SymbolTable; +using Type = memgraph::query::v2::EdgeAtom::Type; +using Direction = memgraph::query::v2::EdgeAtom::Direction; +using Bound = ScanAllByLabelPropertyRange::Bound; + +namespace { + +class Planner { + public: + template + Planner(std::vector single_query_parts, PlanningContext context) { + memgraph::expr::Parameters parameters; + PostProcessor post_processor(parameters); + plan_ = MakeLogicalPlanForSingleQuery(single_query_parts, &context); + plan_ = post_processor.Rewrite(std::move(plan_), &context); + } + + auto &plan() { return *plan_; } + + private: + std::unique_ptr plan_; +}; + +template +auto CheckPlan(LogicalOperator &plan, const SymbolTable &symbol_table, TChecker... checker) { + std::list checkers{&checker...}; + PlanChecker plan_checker(checkers, symbol_table); + plan.Accept(plan_checker); + EXPECT_TRUE(plan_checker.checkers_.empty()); +} + +template +auto CheckPlan(memgraph::query::CypherQuery *query, AstStorage &storage, TChecker... checker) { + auto symbol_table = memgraph::query::MakeSymbolTable(query); + FakeDistributedDbAccessor dba; + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, checker...); +} + +template +class TestPlanner : public ::testing::Test {}; + +using PlannerTypes = ::testing::Types; + +void DeleteListContent(std::list *list) { + for (BaseOpChecker *ptr : *list) { + delete ptr; + } +} +TYPED_TEST_CASE(TestPlanner, PlannerTypes); + +TYPED_TEST(TestPlanner, MatchFilterPropIsNotNull) { + FakeDistributedDbAccessor dba; + auto label = dba.Label("prim_label_one"); + auto prim_prop_one = PRIMARY_PROPERTY_PAIR("prim_prop_one"); + // auto prim_prop_two = PRIMARY_PROPERTY_PAIR("prim_prop_two"); + auto sec_prop_one = PRIMARY_PROPERTY_PAIR("sec_prop_one"); + auto sec_prop_two = PRIMARY_PROPERTY_PAIR("sec_prop_two"); + auto sec_prop_three = PRIMARY_PROPERTY_PAIR("sec_prop_three"); + dba.SetIndexCount(label, 1); + dba.SetIndexCount(label, prim_prop_one.second, 1); + // dba.SetIndexCount(label, prim_prop_two.second, 1); + dba.SetIndexCount(label, sec_prop_one.second, 1); + dba.SetIndexCount(label, sec_prop_two.second, 1); + dba.SetIndexCount(label, sec_prop_three.second, 1); + memgraph::query::v2::AstStorage storage; + { + memgraph::query::v2::Expression *expected_primary_key; + + // Pray and hope for the best... + expected_primary_key = PROPERTY_LOOKUP("n", prim_prop_one); + + // auto asd1 = NODE("n", "label"); + // auto asd2 = PATTERN(NODE("n", "label")); + // auto asd3 = MATCH_V2(PATTERN(NODE("n", "label"))); + // auto asd4 = WHERE_V2(PROPERTY_LOOKUP("n", prim_prop_one)); + + auto *query = QUERY(SINGLE_QUERY_V2(MATCH_V2(PATTERN(NODE("n", "label"))), + WHERE_V2(PROPERTY_LOOKUP("n", prim_prop_one)), RETURN("n"))); + auto symbol_table = (memgraph::expr::MakeSymbolTable(query)); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAllByPrimaryKey(label, {expected_primary_key}), ExpectProduce()); + + // // Test MATCH (n :label) -[r]- (m) WHERE n.prop IS NOT NULL RETURN n + // auto *query2 = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"), EDGE("r"), NODE("m"))), + // WHERE(NOT(IS_NULL(PROPERTY_LOOKUP("n", prop)))), RETURN("n"))); + // auto symbol_table = memgraph::query::MakeSymbolTable(query); + // auto planner = MakePlanner(&dba, storage, symbol_table, query); + // // We expect ScanAllByLabelProperty to come instead of ScanAll > Filter. + // CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelProperty(label, prop), ExpectExpand(), + // ExpectProduce()); + } +} + +/* + + + + + + +TYPED_TEST(TestPlanner, MatchNodeReturn) { + // Test MATCH (n) RETURN n + AstStorage storage; + auto *as_n = NEXPR("n", IDENT("n")); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), RETURN(as_n))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + FakeDistributedDbAccessor dba; + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, CreateNodeReturn) { + // Test CREATE (n) RETURN n AS n + AstStorage storage; + auto ident_n = IDENT("n"); + auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"))), RETURN(ident_n, AS("n")))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto acc = ExpectAccumulate({symbol_table.at(*ident_n)}); + FakeDistributedDbAccessor dba; + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectCreateNode(), acc, ExpectProduce()); +} + +TYPED_TEST(TestPlanner, CreateExpand) { + // Test CREATE (n) -[r :rel1]-> (m) + AstStorage storage; + FakeDistributedDbAccessor dba; + auto relationship = "relationship"; + auto *query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"), EDGE("r", Direction::OUT, {relationship}), NODE("m"))))); + CheckPlan(query, storage, ExpectCreateNode(), ExpectCreateExpand()); +} + +TYPED_TEST(TestPlanner, CreateMultipleNode) { + // Test CREATE (n), (m) + AstStorage storage; + auto *query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n")), PATTERN(NODE("m"))))); + CheckPlan(query, storage, ExpectCreateNode(), ExpectCreateNode()); +} + +TYPED_TEST(TestPlanner, CreateNodeExpandNode) { + // Test CREATE (n) -[r :rel]-> (m), (l) + AstStorage storage; + FakeDistributedDbAccessor dba; + auto relationship = "rel"; + auto *query = QUERY(SINGLE_QUERY( + CREATE(PATTERN(NODE("n"), EDGE("r", Direction::OUT, {relationship}), NODE("m")), PATTERN(NODE("l"))))); + CheckPlan(query, storage, ExpectCreateNode(), ExpectCreateExpand(), ExpectCreateNode()); +} + +TYPED_TEST(TestPlanner, CreateNamedPattern) { + // Test CREATE p = (n) -[r :rel]-> (m) + AstStorage storage; + FakeDistributedDbAccessor dba; + auto relationship = "rel"; + auto *query = + QUERY(SINGLE_QUERY(CREATE(NAMED_PATTERN("p", NODE("n"), EDGE("r", Direction::OUT, {relationship}), NODE("m"))))); + CheckPlan(query, storage, ExpectCreateNode(), ExpectCreateExpand(), ExpectConstructNamedPath()); +} + +TYPED_TEST(TestPlanner, MatchCreateExpand) { + // Test MATCH (n) CREATE (n) -[r :rel1]-> (m) + AstStorage storage; + FakeDistributedDbAccessor dba; + auto relationship = "relationship"; + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), + CREATE(PATTERN(NODE("n"), EDGE("r", Direction::OUT, {relationship}), NODE("m"))))); + CheckPlan(query, storage, ExpectScanAll(), ExpectCreateExpand()); +} + +TYPED_TEST(TestPlanner, MatchLabeledNodes) { + // Test MATCH (n :label) RETURN n + AstStorage storage; + FakeDistributedDbAccessor dba; + auto label = "label"; + auto *as_n = NEXPR("n", IDENT("n")); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", label))), RETURN(as_n))); + { + // Without created label index + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectFilter(), ExpectProduce()); + } + { + // With created label index + dba.SetIndexCount(dba.Label(label), 0); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabel(), ExpectProduce()); + } +} + +TYPED_TEST(TestPlanner, MatchPathReturn) { + // Test MATCH (n) -[r :relationship]- (m) RETURN n + AstStorage storage; + FakeDistributedDbAccessor dba; + auto relationship = "relationship"; + auto *as_n = NEXPR("n", IDENT("n")); + auto *query = QUERY( + SINGLE_QUERY(MATCH(PATTERN(NODE("n"), EDGE("r", Direction::BOTH, {relationship}), NODE("m"))), RETURN(as_n))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectExpand(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, MatchNamedPatternReturn) { + // Test MATCH p = (n) -[r :relationship]- (m) RETURN p + AstStorage storage; + FakeDistributedDbAccessor dba; + auto relationship = "relationship"; + auto *as_p = NEXPR("p", IDENT("p")); + auto *query = QUERY(SINGLE_QUERY( + MATCH(NAMED_PATTERN("p", NODE("n"), EDGE("r", Direction::BOTH, {relationship}), NODE("m"))), RETURN(as_p))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectExpand(), ExpectConstructNamedPath(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, MatchNamedPatternWithPredicateReturn) { + // Test MATCH p = (n) -[r :relationship]- (m) WHERE 2 = p RETURN p + AstStorage storage; + FakeDistributedDbAccessor dba; + auto relationship = "relationship"; + auto *as_p = NEXPR("p", IDENT("p")); + auto *query = + QUERY(SINGLE_QUERY(MATCH(NAMED_PATTERN("p", NODE("n"), EDGE("r", Direction::BOTH, {relationship}), NODE("m"))), + WHERE(EQ(LITERAL(2), IDENT("p"))), RETURN(as_p))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectExpand(), ExpectConstructNamedPath(), ExpectFilter(), + ExpectProduce()); +} + +TYPED_TEST(TestPlanner, OptionalMatchNamedPatternReturn) { + // Test OPTIONAL MATCH p = (n) -[r]- (m) RETURN p + AstStorage storage; + auto node_n = NODE("n"); + auto edge = EDGE("r"); + auto node_m = NODE("m"); + auto pattern = NAMED_PATTERN("p", node_n, edge, node_m); + auto as_p = AS("p"); + auto *query = QUERY(SINGLE_QUERY(OPTIONAL_MATCH(pattern), RETURN("p", as_p))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto get_symbol = [&symbol_table](const auto *ast_node) { return symbol_table.at(*ast_node->identifier_); }; + std::vector optional_symbols{get_symbol(pattern), get_symbol(node_n), get_symbol(edge), get_symbol(node_m)}; + FakeDistributedDbAccessor dba; + auto planner = MakePlanner(&dba, storage, symbol_table, query); + std::list optional{new ExpectScanAll(), new ExpectExpand(), new ExpectConstructNamedPath()}; + CheckPlan(planner.plan(), symbol_table, ExpectOptional(optional_symbols, optional), ExpectProduce()); + DeleteListContent(&optional); +} + +TYPED_TEST(TestPlanner, MatchWhereReturn) { + // Test MATCH (n) WHERE n.property < 42 RETURN n + AstStorage storage; + FakeDistributedDbAccessor dba; + auto property = dba.Property("property"); + auto *as_n = NEXPR("n", IDENT("n")); + auto *query = QUERY( + SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), WHERE(LESS(PROPERTY_LOOKUP("n", property), LITERAL(42))), RETURN(as_n))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectFilter(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, MatchDelete) { + // Test MATCH (n) DELETE n + AstStorage storage; + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), DELETE(IDENT("n")))); + CheckPlan(query, storage, ExpectScanAll(), ExpectDelete()); +} + +TYPED_TEST(TestPlanner, MatchNodeSet) { + // Test MATCH (n) SET n.prop = 42, n = n, n :label + AstStorage storage; + FakeDistributedDbAccessor dba; + auto prop = dba.Property("prop"); + 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(query, storage, ExpectScanAll(), ExpectSetProperty(), ExpectSetProperties(), ExpectSetLabels()); +} + +TYPED_TEST(TestPlanner, MatchRemove) { + // Test MATCH (n) REMOVE n.prop REMOVE n :label + AstStorage storage; + FakeDistributedDbAccessor dba; + auto prop = dba.Property("prop"); + auto label = "label"; + auto *query = + QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), REMOVE(PROPERTY_LOOKUP("n", prop)), REMOVE("n", {label}))); + CheckPlan(query, storage, ExpectScanAll(), ExpectRemoveProperty(), ExpectRemoveLabels()); +} + +TYPED_TEST(TestPlanner, MatchMultiPattern) { + // Test MATCH (n) -[r]- (m), (j) -[e]- (i) RETURN n + AstStorage storage; + auto *query = QUERY(SINGLE_QUERY( + MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m")), PATTERN(NODE("j"), EDGE("e"), NODE("i"))), RETURN("n"))); + // We expect the expansions after the first to have a uniqueness filter in a + // single MATCH clause. + CheckPlan(query, storage, ExpectScanAll(), ExpectExpand(), ExpectScanAll(), ExpectExpand(), + ExpectEdgeUniquenessFilter(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, MatchMultiPatternSameStart) { + // Test MATCH (n), (n) -[e]- (m) RETURN n + AstStorage storage; + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n")), PATTERN(NODE("n"), EDGE("e"), NODE("m"))), RETURN("n"))); + // We expect the second pattern to generate only an Expand, since another + // ScanAll would be redundant. + CheckPlan(query, storage, ExpectScanAll(), ExpectExpand(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, MatchMultiPatternSameExpandStart) { + // Test MATCH (n) -[r]- (m), (m) -[e]- (l) RETURN n + AstStorage storage; + auto *query = QUERY(SINGLE_QUERY( + MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m")), PATTERN(NODE("m"), EDGE("e"), NODE("l"))), RETURN("n"))); + // We expect the second pattern to generate only an Expand. Another + // ScanAll would be redundant, as it would generate the nodes obtained from + // expansion. Additionally, a uniqueness filter is expected. + CheckPlan(query, storage, ExpectScanAll(), ExpectExpand(), ExpectExpand(), ExpectEdgeUniquenessFilter(), + ExpectProduce()); +} + +TYPED_TEST(TestPlanner, MultiMatch) { + // Test MATCH (n) -[r]- (m) MATCH (j) -[e]- (i) -[f]- (h) RETURN n + AstStorage storage; + auto *node_n = NODE("n"); + auto *edge_r = EDGE("r"); + auto *node_m = NODE("m"); + auto *node_j = NODE("j"); + auto *edge_e = EDGE("e"); + auto *node_i = NODE("i"); + auto *edge_f = EDGE("f"); + auto *node_h = NODE("h"); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n, edge_r, node_m)), + MATCH(PATTERN(node_j, edge_e, node_i, edge_f, node_h)), RETURN("n"))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + FakeDistributedDbAccessor dba; + auto planner = MakePlanner(&dba, storage, symbol_table, query); + // Multiple MATCH clauses form a Cartesian product, so the uniqueness should + // not cross MATCH boundaries. + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectExpand(), ExpectScanAll(), ExpectExpand(), + ExpectExpand(), ExpectEdgeUniquenessFilter(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, MultiMatchSameStart) { + // Test MATCH (n) MATCH (n) -[r]- (m) RETURN n + AstStorage storage; + auto *as_n = NEXPR("n", IDENT("n")); + auto *query = + QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m"))), RETURN(as_n))); + // Similar to MatchMultiPatternSameStart, we expect only Expand from second + // MATCH clause. + auto symbol_table = memgraph::query::MakeSymbolTable(query); + FakeDistributedDbAccessor dba; + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectExpand(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, MatchWithReturn) { + // Test MATCH (old) WITH old AS new RETURN new + AstStorage storage; + auto *as_new = NEXPR("new", IDENT("new")); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("old"))), WITH("old", AS("new")), RETURN(as_new))); + // No accumulation since we only do reads. + auto symbol_table = memgraph::query::MakeSymbolTable(query); + FakeDistributedDbAccessor dba; + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectProduce(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, MatchWithWhereReturn) { + // Test MATCH (old) WITH old AS new WHERE new.prop < 42 RETURN new + FakeDistributedDbAccessor dba; + auto prop = dba.Property("prop"); + AstStorage storage; + auto *as_new = NEXPR("new", IDENT("new")); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("old"))), WITH("old", AS("new")), + WHERE(LESS(PROPERTY_LOOKUP("new", prop), LITERAL(42))), RETURN(as_new))); + // No accumulation since we only do reads. + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectProduce(), ExpectFilter(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, CreateMultiExpand) { + // Test CREATE (n) -[r :r]-> (m), (n) - [p :p]-> (l) + FakeDistributedDbAccessor dba; + auto r = "r"; + auto p = "p"; + 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(query, storage, ExpectCreateNode(), ExpectCreateExpand(), ExpectCreateExpand()); +} + +TYPED_TEST(TestPlanner, MatchWithSumWhereReturn) { + // Test MATCH (n) WITH SUM(n.prop) + 42 AS sum WHERE sum < 42 + // RETURN sum AS result + FakeDistributedDbAccessor dba; + auto prop = dba.Property("prop"); + AstStorage storage; + auto sum = SUM(PROPERTY_LOOKUP("n", prop)); + auto literal = LITERAL(42); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), WITH(ADD(sum, literal), AS("sum")), + WHERE(LESS(IDENT("sum"), LITERAL(42))), RETURN("sum", AS("result")))); + auto aggr = ExpectAggregate({sum}, {literal}); + CheckPlan(query, storage, ExpectScanAll(), aggr, ExpectProduce(), ExpectFilter(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, MatchReturnSum) { + // Test MATCH (n) RETURN SUM(n.prop1) AS sum, n.prop2 AS group + FakeDistributedDbAccessor dba; + auto prop1 = dba.Property("prop1"); + auto prop2 = dba.Property("prop2"); + AstStorage storage; + auto sum = SUM(PROPERTY_LOOKUP("n", prop1)); + auto n_prop2 = PROPERTY_LOOKUP("n", prop2); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), RETURN(sum, AS("sum"), n_prop2, AS("group")))); + auto aggr = ExpectAggregate({sum}, {n_prop2}); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), aggr, ExpectProduce()); +} + +TYPED_TEST(TestPlanner, CreateWithSum) { + // Test CREATE (n) WITH SUM(n.prop) AS sum + FakeDistributedDbAccessor dba; + auto prop = dba.Property("prop"); + AstStorage storage; + auto ident_n = IDENT("n"); + auto n_prop = PROPERTY_LOOKUP(ident_n, prop); + auto sum = SUM(n_prop); + auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"))), WITH(sum, AS("sum")))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto acc = ExpectAccumulate({symbol_table.at(*ident_n)}); + auto aggr = ExpectAggregate({sum}, {}); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + // We expect both the accumulation and aggregation because the part before + // WITH updates the database. + CheckPlan(planner.plan(), symbol_table, ExpectCreateNode(), acc, aggr, ExpectProduce()); +} + +TYPED_TEST(TestPlanner, MatchWithCreate) { + // Test MATCH (n) WITH n AS a CREATE (a) -[r :r]-> (b) + FakeDistributedDbAccessor dba; + auto r_type = "r"; + 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(query, storage, ExpectScanAll(), ExpectProduce(), ExpectCreateExpand()); +} + +TYPED_TEST(TestPlanner, MatchReturnSkipLimit) { + // Test MATCH (n) RETURN n SKIP 2 LIMIT 1 + AstStorage storage; + auto *as_n = NEXPR("n", IDENT("n")); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), RETURN(as_n, SKIP(LITERAL(2)), LIMIT(LITERAL(1))))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + FakeDistributedDbAccessor dba; + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectProduce(), ExpectSkip(), ExpectLimit()); +} + +TYPED_TEST(TestPlanner, CreateWithSkipReturnLimit) { + // Test CREATE (n) WITH n AS m SKIP 2 RETURN m LIMIT 1 + AstStorage storage; + auto ident_n = IDENT("n"); + auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"))), WITH(ident_n, AS("m"), SKIP(LITERAL(2))), + RETURN("m", LIMIT(LITERAL(1))))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto acc = ExpectAccumulate({symbol_table.at(*ident_n)}); + FakeDistributedDbAccessor dba; + auto planner = MakePlanner(&dba, storage, symbol_table, query); + // Since we have a write query, we need to have Accumulate. This is a bit + // different than Neo4j 3.0, which optimizes WITH followed by RETURN as a + // single RETURN clause and then moves Skip and Limit before Accumulate. + // This causes different behaviour. A newer version of Neo4j does the same + // thing as us here (but who knows if they change it again). + CheckPlan(planner.plan(), symbol_table, ExpectCreateNode(), acc, ExpectProduce(), ExpectSkip(), ExpectProduce(), + ExpectLimit()); +} + +TYPED_TEST(TestPlanner, CreateReturnSumSkipLimit) { + // Test CREATE (n) RETURN SUM(n.prop) AS s SKIP 2 LIMIT 1 + FakeDistributedDbAccessor dba; + auto prop = dba.Property("prop"); + AstStorage storage; + auto ident_n = IDENT("n"); + auto n_prop = PROPERTY_LOOKUP(ident_n, prop); + auto sum = SUM(n_prop); + auto query = + QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"))), RETURN(sum, AS("s"), SKIP(LITERAL(2)), LIMIT(LITERAL(1))))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto acc = ExpectAccumulate({symbol_table.at(*ident_n)}); + auto aggr = ExpectAggregate({sum}, {}); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectCreateNode(), acc, aggr, ExpectProduce(), ExpectSkip(), ExpectLimit()); +} + +TYPED_TEST(TestPlanner, MatchReturnOrderBy) { + // Test MATCH (n) RETURN n AS m ORDER BY n.prop + FakeDistributedDbAccessor dba; + auto prop = dba.Property("prop"); + AstStorage storage; + auto *as_m = NEXPR("m", IDENT("n")); + auto *node_n = NODE("n"); + auto ret = RETURN(as_m, ORDER_BY(PROPERTY_LOOKUP("n", prop))); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n)), ret)); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectProduce(), ExpectOrderBy()); +} + +TYPED_TEST(TestPlanner, CreateWithOrderByWhere) { + // Test CREATE (n) -[r :r]-> (m) + // WITH n AS new ORDER BY new.prop, r.prop WHERE m.prop < 42 + FakeDistributedDbAccessor dba; + auto prop = dba.Property("prop"); + auto r_type = "r"; + AstStorage storage; + auto ident_n = IDENT("n"); + auto ident_r = IDENT("r"); + auto ident_m = IDENT("m"); + auto new_prop = PROPERTY_LOOKUP("new", prop); + auto r_prop = PROPERTY_LOOKUP(ident_r, prop); + auto m_prop = PROPERTY_LOOKUP(ident_m, prop); + auto query = + QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"), EDGE("r", Direction::OUT, {r_type}), NODE("m"))), + WITH(ident_n, AS("new"), ORDER_BY(new_prop, r_prop)), WHERE(LESS(m_prop, LITERAL(42))))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + // Since this is a write query, we expect to accumulate to old used symbols. + auto acc = ExpectAccumulate({ + symbol_table.at(*ident_n), // `n` in WITH + symbol_table.at(*ident_r), // `r` in ORDER BY + symbol_table.at(*ident_m), // `m` in WHERE + }); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectCreateNode(), ExpectCreateExpand(), acc, ExpectProduce(), + ExpectOrderBy(), ExpectFilter()); +} + +TYPED_TEST(TestPlanner, ReturnAddSumCountOrderBy) { + // Test RETURN SUM(1) + COUNT(2) AS result ORDER BY result + AstStorage storage; + auto sum = SUM(LITERAL(1)); + auto count = COUNT(LITERAL(2)); + auto *query = QUERY(SINGLE_QUERY(RETURN(ADD(sum, count), AS("result"), ORDER_BY(IDENT("result"))))); + auto aggr = ExpectAggregate({sum, count}, {}); + CheckPlan(query, storage, aggr, ExpectProduce(), ExpectOrderBy()); +} + +TYPED_TEST(TestPlanner, MatchMerge) { + // Test MATCH (n) MERGE (n) -[r :r]- (m) + // ON MATCH SET n.prop = 42 ON CREATE SET m = n + // RETURN n AS n + FakeDistributedDbAccessor dba; + auto r_type = "r"; + auto prop = dba.Property("prop"); + AstStorage storage; + auto ident_n = IDENT("n"); + auto query = + QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), + MERGE(PATTERN(NODE("n"), EDGE("r", Direction::BOTH, {r_type}), NODE("m")), + ON_MATCH(SET(PROPERTY_LOOKUP("n", prop), LITERAL(42))), ON_CREATE(SET("m", IDENT("n")))), + RETURN(ident_n, AS("n")))); + std::list on_match{new ExpectExpand(), new ExpectSetProperty()}; + std::list on_create{new ExpectCreateExpand(), new ExpectSetProperties()}; + auto symbol_table = memgraph::query::MakeSymbolTable(query); + // We expect Accumulate after Merge, because it is considered as a write. + auto acc = ExpectAccumulate({symbol_table.at(*ident_n)}); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectMerge(on_match, on_create), acc, ExpectProduce()); + DeleteListContent(&on_match); + DeleteListContent(&on_create); +} + +TYPED_TEST(TestPlanner, MatchOptionalMatchWhereReturn) { + // Test MATCH (n) OPTIONAL MATCH (n) -[r]- (m) WHERE m.prop < 42 RETURN r + FakeDistributedDbAccessor dba; + auto prop = dba.Property("prop"); + AstStorage storage; + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), OPTIONAL_MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m"))), + WHERE(LESS(PROPERTY_LOOKUP("m", prop), LITERAL(42))), RETURN("r"))); + std::list optional{new ExpectScanAll(), new ExpectExpand(), new ExpectFilter()}; + CheckPlan(query, storage, ExpectScanAll(), ExpectOptional(optional), ExpectProduce()); + DeleteListContent(&optional); +} + +TYPED_TEST(TestPlanner, MatchUnwindReturn) { + // Test MATCH (n) UNWIND [1,2,3] AS x RETURN n, x + AstStorage storage; + auto *as_n = NEXPR("n", IDENT("n")); + auto *as_x = NEXPR("x", IDENT("x")); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), UNWIND(LIST(LITERAL(1), LITERAL(2), LITERAL(3)), AS("x")), + RETURN(as_n, as_x))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + FakeDistributedDbAccessor dba; + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectUnwind(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, ReturnDistinctOrderBySkipLimit) { + // Test RETURN DISTINCT 1 ORDER BY 1 SKIP 1 LIMIT 1 + AstStorage storage; + auto *query = QUERY( + SINGLE_QUERY(RETURN_DISTINCT(LITERAL(1), AS("1"), ORDER_BY(LITERAL(1)), SKIP(LITERAL(1)), LIMIT(LITERAL(1))))); + CheckPlan(query, storage, ExpectProduce(), ExpectDistinct(), ExpectOrderBy(), ExpectSkip(), ExpectLimit()); +} + +TYPED_TEST(TestPlanner, CreateWithDistinctSumWhereReturn) { + // Test CREATE (n) WITH DISTINCT SUM(n.prop) AS s WHERE s < 42 RETURN s + FakeDistributedDbAccessor dba; + auto prop = dba.Property("prop"); + AstStorage storage; + auto node_n = NODE("n"); + auto sum = SUM(PROPERTY_LOOKUP("n", prop)); + auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(node_n)), WITH_DISTINCT(sum, AS("s")), + WHERE(LESS(IDENT("s"), LITERAL(42))), RETURN("s"))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto acc = ExpectAccumulate({symbol_table.at(*node_n->identifier_)}); + auto aggr = ExpectAggregate({sum}, {}); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectCreateNode(), acc, aggr, ExpectProduce(), ExpectDistinct(), + ExpectFilter(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, MatchCrossReferenceVariable) { + // Test MATCH (n {prop: m.prop}), (m {prop: n.prop}) RETURN n + FakeDistributedDbAccessor dba; + auto prop = PROPERTY_PAIR("prop"); + AstStorage storage; + auto node_n = NODE("n"); + auto m_prop = PROPERTY_LOOKUP("m", prop.second); + std::get<0>(node_n->properties_)[storage.GetPropertyIx(prop.first)] = m_prop; + auto node_m = NODE("m"); + auto n_prop = PROPERTY_LOOKUP("n", prop.second); + std::get<0>(node_m->properties_)[storage.GetPropertyIx(prop.first)] = n_prop; + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n), PATTERN(node_m)), RETURN("n"))); + // We expect both ScanAll to come before filters (2 are joined into one), + // because they need to populate the symbol values. + CheckPlan(query, storage, ExpectScanAll(), ExpectScanAll(), ExpectFilter(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, MatchWhereBeforeExpand) { + // Test MATCH (n) -[r]- (m) WHERE n.prop < 42 RETURN n + FakeDistributedDbAccessor dba; + auto prop = dba.Property("prop"); + AstStorage storage; + auto *as_n = NEXPR("n", IDENT("n")); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m"))), + WHERE(LESS(PROPERTY_LOOKUP("n", prop), LITERAL(42))), RETURN(as_n))); + // We expect Filter to come immediately after ScanAll, since it only uses `n`. + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectFilter(), ExpectExpand(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, MatchFilterPropIsNotNull) { + FakeDistributedDbAccessor dba; + auto label = dba.Label("label"); + auto prop = PROPERTY_PAIR("prop"); + dba.SetIndexCount(label, 1); + dba.SetIndexCount(label, prop.second, 1); + AstStorage storage; + + { + // Test MATCH (n :label) -[r]- (m) WHERE n.prop IS NOT NULL RETURN n + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"), EDGE("r"), NODE("m"))), + WHERE(NOT(IS_NULL(PROPERTY_LOOKUP("n", prop)))), RETURN("n"))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + // We expect ScanAllByLabelProperty to come instead of ScanAll > Filter. + CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelProperty(label, prop), ExpectExpand(), ExpectProduce()); + } + + { + // Test MATCH (n :label) -[r]- (m) WHERE n.prop IS NOT NULL OR true RETURN n + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"), EDGE("r"), NODE("m"))), + WHERE(OR(NOT(IS_NULL(PROPERTY_LOOKUP("n", prop))), LITERAL(true))), RETURN("n"))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + // We expect ScanAllBy > Filter because of the "or true" condition. + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectFilter(), ExpectExpand(), ExpectProduce()); + } + + { + // Test MATCH (n :label) -[r]- (m) + // WHERE n.prop IS NOT NULL AND n.x = 2 RETURN n + auto prop_x = PROPERTY_PAIR("x"); + auto *query = QUERY( + SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"), EDGE("r"), NODE("m"))), + WHERE(AND(NOT(IS_NULL(PROPERTY_LOOKUP("n", prop))), EQ(PROPERTY_LOOKUP("n", prop_x), LITERAL(2)))), + RETURN("n"))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + // We expect ScanAllByLabelProperty > Filter + // to come instead of ScanAll > Filter. + CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelProperty(label, prop), ExpectFilter(), ExpectExpand(), + ExpectProduce()); + } +} + +TYPED_TEST(TestPlanner, MultiMatchWhere) { + // Test MATCH (n) -[r]- (m) MATCH (l) WHERE n.prop < 42 RETURN n + FakeDistributedDbAccessor dba; + auto prop = dba.Property("prop"); + AstStorage storage; + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m"))), MATCH(PATTERN(NODE("l"))), + WHERE(LESS(PROPERTY_LOOKUP("n", prop), LITERAL(42))), RETURN("n"))); + // Even though WHERE is in the second MATCH clause, we expect Filter to come + // before second ScanAll, since it only uses the value from first ScanAll. + CheckPlan(query, storage, ExpectScanAll(), ExpectFilter(), ExpectExpand(), ExpectScanAll(), + ExpectProduce()); +} + +TYPED_TEST(TestPlanner, MatchOptionalMatchWhere) { + // Test MATCH (n) -[r]- (m) OPTIONAL MATCH (l) WHERE n.prop < 42 RETURN n + FakeDistributedDbAccessor dba; + auto prop = dba.Property("prop"); + AstStorage storage; + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m"))), OPTIONAL_MATCH(PATTERN(NODE("l"))), + WHERE(LESS(PROPERTY_LOOKUP("n", prop), LITERAL(42))), RETURN("n"))); + // Even though WHERE is in the second MATCH clause, and it uses the value from + // first ScanAll, it must remain part of the Optional. It should come before + // optional ScanAll. + std::list optional{new ExpectFilter(), new ExpectScanAll()}; + CheckPlan(query, storage, ExpectScanAll(), ExpectExpand(), ExpectOptional(optional), ExpectProduce()); + DeleteListContent(&optional); +} + +TYPED_TEST(TestPlanner, MatchReturnAsterisk) { + // Test MATCH (n) -[e]- (m) RETURN *, m.prop + FakeDistributedDbAccessor dba; + auto prop = dba.Property("prop"); + AstStorage storage; + auto ret = RETURN(PROPERTY_LOOKUP("m", prop), AS("m.prop")); + ret->body_.all_identifiers = true; + auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), EDGE("e"), NODE("m"))), ret)); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectExpand(), ExpectProduce()); + std::vector output_names; + for (const auto &output_symbol : planner.plan().OutputSymbols(symbol_table)) { + output_names.emplace_back(output_symbol.name()); + } + std::vector expected_names{"e", "m", "n", "m.prop"}; + EXPECT_EQ(output_names, expected_names); +} + +TYPED_TEST(TestPlanner, MatchReturnAsteriskSum) { + // Test MATCH (n) RETURN *, SUM(n.prop) AS s + FakeDistributedDbAccessor dba; + auto prop = dba.Property("prop"); + AstStorage storage; + auto sum = SUM(PROPERTY_LOOKUP("n", prop)); + auto ret = RETURN(sum, AS("s")); + ret->body_.all_identifiers = true; + auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), ret)); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + auto *produce = dynamic_cast(&planner.plan()); + ASSERT_TRUE(produce); + const auto &named_expressions = produce->named_expressions_; + ASSERT_EQ(named_expressions.size(), 2); + auto *expanded_ident = dynamic_cast(named_expressions[0]->expression_); + ASSERT_TRUE(expanded_ident); + auto aggr = ExpectAggregate({sum}, {expanded_ident}); + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), aggr, ExpectProduce()); + std::vector output_names; + for (const auto &output_symbol : planner.plan().OutputSymbols(symbol_table)) { + output_names.emplace_back(output_symbol.name()); + } + std::vector expected_names{"n", "s"}; + EXPECT_EQ(output_names, expected_names); +} + +TYPED_TEST(TestPlanner, UnwindMergeNodeProperty) { + // Test UNWIND [1] AS i MERGE (n {prop: i}) + AstStorage storage; + FakeDistributedDbAccessor dba; + auto node_n = NODE("n"); + std::get<0>(node_n->properties_)[storage.GetPropertyIx("prop")] = IDENT("i"); + auto *query = QUERY(SINGLE_QUERY(UNWIND(LIST(LITERAL(1)), AS("i")), MERGE(PATTERN(node_n)))); + std::list on_match{new ExpectScanAll(), new ExpectFilter()}; + std::list on_create{new ExpectCreateNode()}; + CheckPlan(query, storage, ExpectUnwind(), ExpectMerge(on_match, on_create)); + DeleteListContent(&on_match); + DeleteListContent(&on_create); +} + +TYPED_TEST(TestPlanner, UnwindMergeNodePropertyWithIndex) { + // Test UNWIND [1] AS i MERGE (n :label {prop: i}) with label-property index + AstStorage storage; + FakeDistributedDbAccessor dba; + const auto label_name = "label"; + const auto label = dba.Label(label_name); + const auto property = PROPERTY_PAIR("prop"); + dba.SetIndexCount(label, property.second, 1); + auto node_n = NODE("n", label_name); + std::get<0>(node_n->properties_)[storage.GetPropertyIx(property.first)] = IDENT("i"); + auto *query = QUERY(SINGLE_QUERY(UNWIND(LIST(LITERAL(1)), AS("i")), MERGE(PATTERN(node_n)))); + std::list on_match{new ExpectScanAllByLabelPropertyValue(label, property, IDENT("i"))}; + std::list on_create{new ExpectCreateNode()}; + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectUnwind(), ExpectMerge(on_match, on_create)); + DeleteListContent(&on_match); + DeleteListContent(&on_create); +} + +TYPED_TEST(TestPlanner, MultipleOptionalMatchReturn) { + // Test OPTIONAL MATCH (n) OPTIONAL MATCH (m) RETURN n + AstStorage storage; + auto *query = + QUERY(SINGLE_QUERY(OPTIONAL_MATCH(PATTERN(NODE("n"))), OPTIONAL_MATCH(PATTERN(NODE("m"))), RETURN("n"))); + std::list optional{new ExpectScanAll()}; + CheckPlan(query, storage, ExpectOptional(optional), ExpectOptional(optional), ExpectProduce()); + DeleteListContent(&optional); +} + +TYPED_TEST(TestPlanner, FunctionAggregationReturn) { + // Test RETURN sqrt(SUM(2)) AS result, 42 AS group_by + AstStorage storage; + auto sum = SUM(LITERAL(2)); + auto group_by_literal = LITERAL(42); + auto *query = QUERY(SINGLE_QUERY(RETURN(FN("sqrt", sum), AS("result"), group_by_literal, AS("group_by")))); + auto aggr = ExpectAggregate({sum}, {group_by_literal}); + CheckPlan(query, storage, aggr, ExpectProduce()); +} + +TYPED_TEST(TestPlanner, FunctionWithoutArguments) { + // Test RETURN pi() AS pi + AstStorage storage; + auto *query = QUERY(SINGLE_QUERY(RETURN(FN("pi"), AS("pi")))); + CheckPlan(query, storage, ExpectProduce()); +} + +TYPED_TEST(TestPlanner, ListLiteralAggregationReturn) { + // Test RETURN [SUM(2)] AS result, 42 AS group_by + AstStorage storage; + auto sum = SUM(LITERAL(2)); + auto group_by_literal = LITERAL(42); + auto *query = QUERY(SINGLE_QUERY(RETURN(LIST(sum), AS("result"), group_by_literal, AS("group_by")))); + auto aggr = ExpectAggregate({sum}, {group_by_literal}); + CheckPlan(query, storage, aggr, ExpectProduce()); +} + +TYPED_TEST(TestPlanner, MapLiteralAggregationReturn) { + // Test RETURN {sum: SUM(2)} AS result, 42 AS group_by + AstStorage storage; + FakeDistributedDbAccessor dba; + auto sum = SUM(LITERAL(2)); + auto group_by_literal = LITERAL(42); + auto *query = QUERY( + SINGLE_QUERY(RETURN(MAP({storage.GetPropertyIx("sum"), sum}), AS("result"), group_by_literal, AS("group_by")))); + auto aggr = ExpectAggregate({sum}, {group_by_literal}); + CheckPlan(query, storage, aggr, ExpectProduce()); +} + +TYPED_TEST(TestPlanner, EmptyListIndexAggregation) { + // Test RETURN [][SUM(2)] AS result, 42 AS group_by + AstStorage storage; + auto sum = SUM(LITERAL(2)); + auto empty_list = LIST(); + auto group_by_literal = LITERAL(42); + auto *query = QUERY(SINGLE_QUERY(RETURN(storage.Create(empty_list, sum), + AS("result"), group_by_literal, AS("group_by")))); + // We expect to group by '42' and the empty list, because it is a + // sub-expression of a binary operator which contains an aggregation. This is + // similar to grouping by '1' in `RETURN 1 + SUM(2)`. + auto aggr = ExpectAggregate({sum}, {empty_list, group_by_literal}); + CheckPlan(query, storage, aggr, ExpectProduce()); +} + +TYPED_TEST(TestPlanner, ListSliceAggregationReturn) { + // Test RETURN [1, 2][0..SUM(2)] AS result, 42 AS group_by + AstStorage storage; + auto sum = SUM(LITERAL(2)); + auto list = LIST(LITERAL(1), LITERAL(2)); + auto group_by_literal = LITERAL(42); + auto *query = + QUERY(SINGLE_QUERY(RETURN(SLICE(list, LITERAL(0), sum), AS("result"), group_by_literal, AS("group_by")))); + // Similarly to EmptyListIndexAggregation test, we expect grouping by list and + // '42', because slicing is an operator. + auto aggr = ExpectAggregate({sum}, {list, group_by_literal}); + CheckPlan(query, storage, aggr, ExpectProduce()); +} + +TYPED_TEST(TestPlanner, ListWithAggregationAndGroupBy) { + // Test RETURN [sum(2), 42] + AstStorage storage; + auto sum = SUM(LITERAL(2)); + auto group_by_literal = LITERAL(42); + auto *query = QUERY(SINGLE_QUERY(RETURN(LIST(sum, group_by_literal), AS("result")))); + auto aggr = ExpectAggregate({sum}, {group_by_literal}); + CheckPlan(query, storage, aggr, ExpectProduce()); +} + +TYPED_TEST(TestPlanner, AggregatonWithListWithAggregationAndGroupBy) { + // Test RETURN sum(2), [sum(3), 42] + AstStorage storage; + auto sum2 = SUM(LITERAL(2)); + auto sum3 = SUM(LITERAL(3)); + auto group_by_literal = LITERAL(42); + auto *query = QUERY(SINGLE_QUERY(RETURN(sum2, AS("sum2"), LIST(sum3, group_by_literal), AS("list")))); + auto aggr = ExpectAggregate({sum2, sum3}, {group_by_literal}); + CheckPlan(query, storage, aggr, ExpectProduce()); +} + +TYPED_TEST(TestPlanner, MapWithAggregationAndGroupBy) { + // Test RETURN {lit: 42, sum: sum(2)} + AstStorage storage; + FakeDistributedDbAccessor dba; + auto sum = SUM(LITERAL(2)); + auto group_by_literal = LITERAL(42); + auto *query = QUERY(SINGLE_QUERY(RETURN( + MAP({storage.GetPropertyIx("sum"), sum}, {storage.GetPropertyIx("lit"), group_by_literal}), AS("result")))); + auto aggr = ExpectAggregate({sum}, {group_by_literal}); + CheckPlan(query, storage, aggr, ExpectProduce()); +} + +TYPED_TEST(TestPlanner, AtomIndexedLabelProperty) { + // Test MATCH (n :label {property: 42, not_indexed: 0}) RETURN n + AstStorage storage; + FakeDistributedDbAccessor dba; + auto label = dba.Label("label"); + auto property = PROPERTY_PAIR("property"); + auto not_indexed = PROPERTY_PAIR("not_indexed"); + dba.SetIndexCount(label, 1); + dba.SetIndexCount(label, property.second, 1); + auto node = NODE("n", "label"); + auto lit_42 = LITERAL(42); + std::get<0>(node->properties_)[storage.GetPropertyIx(property.first)] = lit_42; + std::get<0>(node->properties_)[storage.GetPropertyIx(not_indexed.first)] = LITERAL(0); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node)), RETURN("n"))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelPropertyValue(label, property, lit_42), ExpectFilter(), + ExpectProduce()); +} + +TYPED_TEST(TestPlanner, AtomPropertyWhereLabelIndexing) { + // Test MATCH (n {property: 42}) WHERE n.not_indexed AND n:label RETURN n + AstStorage storage; + FakeDistributedDbAccessor dba; + auto label = dba.Label("label"); + auto property = PROPERTY_PAIR("property"); + auto not_indexed = PROPERTY_PAIR("not_indexed"); + dba.SetIndexCount(label, property.second, 0); + auto node = NODE("n"); + auto lit_42 = LITERAL(42); + std::get<0>(node->properties_)[storage.GetPropertyIx(property.first)] = lit_42; + auto *query = QUERY( + SINGLE_QUERY(MATCH(PATTERN(node)), + WHERE(AND(PROPERTY_LOOKUP("n", not_indexed), + storage.Create( + IDENT("n"), std::vector{storage.GetLabelIx("label")}))), + RETURN("n"))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelPropertyValue(label, property, lit_42), ExpectFilter(), + ExpectProduce()); +} + +TYPED_TEST(TestPlanner, WhereIndexedLabelProperty) { + // Test MATCH (n :label) WHERE n.property = 42 RETURN n + AstStorage storage; + FakeDistributedDbAccessor dba; + auto label = dba.Label("label"); + auto property = PROPERTY_PAIR("property"); + dba.SetIndexCount(label, property.second, 0); + auto lit_42 = LITERAL(42); + auto *query = QUERY( + SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"))), WHERE(EQ(PROPERTY_LOOKUP("n", property), lit_42)), RETURN("n"))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelPropertyValue(label, property, lit_42), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, BestPropertyIndexed) { + // Test MATCH (n :label) WHERE n.property = 1 AND n.better = 42 RETURN n + AstStorage storage; + FakeDistributedDbAccessor dba; + auto label = dba.Label("label"); + auto property = dba.Property("property"); + // Add a vertex with :label+property combination, so that the best + // :label+better remains empty and thus better choice. + dba.SetIndexCount(label, property, 1); + auto better = PROPERTY_PAIR("better"); + dba.SetIndexCount(label, better.second, 0); + auto lit_42 = LITERAL(42); + auto *query = QUERY( + SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"))), + WHERE(AND(EQ(PROPERTY_LOOKUP("n", property), LITERAL(1)), EQ(PROPERTY_LOOKUP("n", better), lit_42))), + RETURN("n"))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelPropertyValue(label, better, lit_42), ExpectFilter(), + ExpectProduce()); +} + +TYPED_TEST(TestPlanner, MultiPropertyIndexScan) { + // Test MATCH (n :label1), (m :label2) WHERE n.prop1 = 1 AND m.prop2 = 2 + // RETURN n, m + FakeDistributedDbAccessor dba; + auto label1 = dba.Label("label1"); + auto label2 = dba.Label("label2"); + auto prop1 = PROPERTY_PAIR("prop1"); + auto prop2 = PROPERTY_PAIR("prop2"); + dba.SetIndexCount(label1, prop1.second, 0); + dba.SetIndexCount(label2, prop2.second, 0); + AstStorage storage; + auto lit_1 = LITERAL(1); + auto lit_2 = LITERAL(2); + auto *query = QUERY(SINGLE_QUERY( + MATCH(PATTERN(NODE("n", "label1")), PATTERN(NODE("m", "label2"))), + WHERE(AND(EQ(PROPERTY_LOOKUP("n", prop1), lit_1), EQ(PROPERTY_LOOKUP("m", prop2), lit_2))), RETURN("n", "m"))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelPropertyValue(label1, prop1, lit_1), + ExpectScanAllByLabelPropertyValue(label2, prop2, lit_2), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, WhereIndexedLabelPropertyRange) { + // Test MATCH (n :label) WHERE n.property REL_OP 42 RETURN n + // REL_OP is one of: `<`, `<=`, `>`, `>=` + FakeDistributedDbAccessor dba; + auto label = dba.Label("label"); + auto property = dba.Property("property"); + dba.SetIndexCount(label, property, 0); + AstStorage storage; + auto lit_42 = LITERAL(42); + auto n_prop = PROPERTY_LOOKUP("n", property); + auto check_planned_range = [&label, &property, &dba](const auto &rel_expr, auto lower_bound, auto upper_bound) { + // Shadow the first storage, so that the query is created in this one. + AstStorage storage; + storage.GetLabelIx("label"); + storage.GetPropertyIx("property"); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"))), WHERE(rel_expr), RETURN("n"))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, + ExpectScanAllByLabelPropertyRange(label, property, lower_bound, upper_bound), ExpectProduce()); + }; + { + // Test relation operators which form an upper bound for range. + std::vector> upper_bound_rel_op{ + std::make_pair(LESS(n_prop, lit_42), Bound::Type::EXCLUSIVE), + std::make_pair(LESS_EQ(n_prop, lit_42), Bound::Type::INCLUSIVE), + std::make_pair(GREATER(lit_42, n_prop), Bound::Type::EXCLUSIVE), + std::make_pair(GREATER_EQ(lit_42, n_prop), Bound::Type::INCLUSIVE)}; + for (const auto &rel_op : upper_bound_rel_op) { + check_planned_range(rel_op.first, std::nullopt, Bound(lit_42, rel_op.second)); + } + } + { + // Test relation operators which form a lower bound for range. + std::vector> lower_bound_rel_op{ + std::make_pair(LESS(lit_42, n_prop), Bound::Type::EXCLUSIVE), + std::make_pair(LESS_EQ(lit_42, n_prop), Bound::Type::INCLUSIVE), + std::make_pair(GREATER(n_prop, lit_42), Bound::Type::EXCLUSIVE), + std::make_pair(GREATER_EQ(n_prop, lit_42), Bound::Type::INCLUSIVE)}; + for (const auto &rel_op : lower_bound_rel_op) { + check_planned_range(rel_op.first, Bound(lit_42, rel_op.second), std::nullopt); + } + } +} + +TYPED_TEST(TestPlanner, WherePreferEqualityIndexOverRange) { + // Test MATCH (n :label) WHERE n.property = 42 AND n.property > 0 RETURN n + AstStorage storage; + FakeDistributedDbAccessor dba; + auto label = dba.Label("label"); + auto property = PROPERTY_PAIR("property"); + dba.SetIndexCount(label, property.second, 0); + auto lit_42 = LITERAL(42); + auto *query = QUERY(SINGLE_QUERY( + MATCH(PATTERN(NODE("n", "label"))), + WHERE(AND(EQ(PROPERTY_LOOKUP("n", property), lit_42), GREATER(PROPERTY_LOOKUP("n", property), LITERAL(0)))), + RETURN("n"))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelPropertyValue(label, property, lit_42), ExpectFilter(), + ExpectProduce()); +} + +TYPED_TEST(TestPlanner, UnableToUsePropertyIndex) { + // Test MATCH (n: label) WHERE n.property = n.property RETURN n + FakeDistributedDbAccessor dba; + auto label = dba.Label("label"); + auto property = dba.Property("property"); + dba.SetIndexCount(label, 0); + dba.SetIndexCount(label, property, 0); + AstStorage storage; + auto *query = + QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"))), + WHERE(EQ(PROPERTY_LOOKUP("n", property), PROPERTY_LOOKUP("n", property))), RETURN("n"))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + // We can only get ScanAllByLabelIndex, because we are comparing properties + // with those on the same node. + CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabel(), ExpectFilter(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, SecondPropertyIndex) { + // Test MATCH (n :label), (m :label) WHERE m.property = n.property RETURN n + FakeDistributedDbAccessor dba; + auto label = dba.Label("label"); + auto property = PROPERTY_PAIR("property"); + dba.SetIndexCount(label, 0); + dba.SetIndexCount(label, dba.Property("property"), 0); + AstStorage storage; + auto n_prop = PROPERTY_LOOKUP("n", property); + auto m_prop = PROPERTY_LOOKUP("m", property); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label")), PATTERN(NODE("m", "label"))), + WHERE(EQ(m_prop, n_prop)), RETURN("n"))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabel(), + // Note: We are scanning for m, therefore property should equal n_prop. + ExpectScanAllByLabelPropertyValue(label, property, n_prop), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, ReturnSumGroupByAll) { + // Test RETURN sum([1,2,3]), all(x in [1] where x = 1) + AstStorage storage; + auto sum = SUM(LIST(LITERAL(1), LITERAL(2), LITERAL(3))); + auto *all = ALL("x", LIST(LITERAL(1)), WHERE(EQ(IDENT("x"), LITERAL(1)))); + auto *query = QUERY(SINGLE_QUERY(RETURN(sum, AS("sum"), all, AS("all")))); + auto aggr = ExpectAggregate({sum}, {all}); + CheckPlan(query, storage, aggr, ExpectProduce()); +} + +TYPED_TEST(TestPlanner, MatchExpandVariable) { + // Test MATCH (n) -[r *..3]-> (m) RETURN r + AstStorage storage; + auto edge = EDGE_VARIABLE("r"); + edge->upper_bound_ = LITERAL(3); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("r"))); + CheckPlan(query, storage, ExpectScanAll(), ExpectExpandVariable(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, MatchExpandVariableNoBounds) { + // Test MATCH (n) -[r *]-> (m) RETURN r + AstStorage storage; + auto edge = EDGE_VARIABLE("r"); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("r"))); + CheckPlan(query, storage, ExpectScanAll(), ExpectExpandVariable(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, MatchExpandVariableInlinedFilter) { + // Test MATCH (n) -[r :type * {prop: 42}]-> (m) RETURN r + FakeDistributedDbAccessor dba; + auto type = "type"; + auto prop = PROPERTY_PAIR("prop"); + AstStorage storage; + auto edge = EDGE_VARIABLE("r", Type::DEPTH_FIRST, Direction::BOTH, {type}); + std::get<0>(edge->properties_)[storage.GetPropertyIx(prop.first)] = LITERAL(42); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("r"))); + CheckPlan(query, storage, ExpectScanAll(), + ExpectExpandVariable(), // Filter is both inlined and post-expand + ExpectFilter(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, MatchExpandVariableNotInlinedFilter) { + // Test MATCH (n) -[r :type * {prop: m.prop}]-> (m) RETURN r + FakeDistributedDbAccessor dba; + auto type = "type"; + auto prop = PROPERTY_PAIR("prop"); + AstStorage storage; + auto edge = EDGE_VARIABLE("r", Type::DEPTH_FIRST, Direction::BOTH, {type}); + std::get<0>(edge->properties_)[storage.GetPropertyIx(prop.first)] = EQ(PROPERTY_LOOKUP("m", prop), LITERAL(42)); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("r"))); + CheckPlan(query, storage, ExpectScanAll(), ExpectExpandVariable(), ExpectFilter(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, MatchExpandVariableTotalWeightSymbol) { + // Test MATCH p = (a {id: 0})-[r* wShortest (e, v | 1) total_weight]->(b) + // RETURN * + FakeDistributedDbAccessor dba; + AstStorage storage; + + auto edge = EDGE_VARIABLE("r", Type::WEIGHTED_SHORTEST_PATH, Direction::BOTH, {}, nullptr, nullptr, nullptr, nullptr, + nullptr, IDENT("total_weight")); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("*"))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + auto *root = dynamic_cast(&planner.plan()); + + ASSERT_TRUE(root); + + const auto &nes = root->named_expressions_; + EXPECT_TRUE(nes.size() == 4); + + std::vector names(nes.size()); + std::transform(nes.begin(), nes.end(), names.begin(), [](const auto *ne) { return ne->name_; }); + + EXPECT_TRUE(root->named_expressions_.size() == 4); + EXPECT_TRUE(memgraph::utils::Contains(names, "m")); + EXPECT_TRUE(memgraph::utils::Contains(names, "n")); + EXPECT_TRUE(memgraph::utils::Contains(names, "r")); + EXPECT_TRUE(memgraph::utils::Contains(names, "total_weight")); +} + +TYPED_TEST(TestPlanner, UnwindMatchVariable) { + // Test UNWIND [1,2,3] AS depth MATCH (n) -[r*d]-> (m) RETURN r + AstStorage storage; + auto edge = EDGE_VARIABLE("r", Type::DEPTH_FIRST, Direction::OUT); + edge->lower_bound_ = IDENT("d"); + edge->upper_bound_ = IDENT("d"); + auto *query = QUERY(SINGLE_QUERY(UNWIND(LIST(LITERAL(1), LITERAL(2), LITERAL(3)), AS("d")), + MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("r"))); + CheckPlan(query, storage, ExpectUnwind(), ExpectScanAll(), ExpectExpandVariable(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, MatchBfs) { + // Test MATCH (n) -[r:type *..10 (r, n|n)]-> (m) RETURN r + FakeDistributedDbAccessor dba; + AstStorage storage; + auto edge_type = storage.GetEdgeTypeIx("type"); + auto *bfs = + storage.Create(IDENT("r"), memgraph::query::EdgeAtom::Type::BREADTH_FIRST, + Direction::OUT, std::vector{edge_type}); + bfs->filter_lambda_.inner_edge = IDENT("r"); + bfs->filter_lambda_.inner_node = IDENT("n"); + bfs->filter_lambda_.expression = IDENT("n"); + bfs->upper_bound_ = LITERAL(10); + auto *as_r = NEXPR("r", IDENT("r")); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), bfs, NODE("m"))), RETURN(as_r))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectExpandBfs(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, MatchDoubleScanToExpandExisting) { + // Test MATCH (n) -[r]- (m :label) RETURN r + FakeDistributedDbAccessor dba; + auto label = "label"; + dba.SetIndexCount(dba.Label(label), 0); + AstStorage storage; + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m", label))), RETURN("r"))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + // We expect 2x ScanAll and then Expand, since we are guessing that is + // faster (due to low label index vertex count). + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectScanAllByLabel(), ExpectExpand(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, MatchScanToExpand) { + // Test MATCH (n) -[r]- (m :label {property: 1}) RETURN r + FakeDistributedDbAccessor dba; + auto label = dba.Label("label"); + auto property = dba.Property("property"); + // Fill vertices to the max + 1. + dba.SetIndexCount(label, property, FLAGS_query_vertex_count_to_expand_existing + 1); + dba.SetIndexCount(label, FLAGS_query_vertex_count_to_expand_existing + 1); + AstStorage storage; + auto node_m = NODE("m", "label"); + std::get<0>(node_m->properties_)[storage.GetPropertyIx("property")] = LITERAL(1); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), node_m)), RETURN("r"))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + // We expect 1x ScanAll and then Expand, since we are guessing that + // is faster (due to high label index vertex count). + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectExpand(), ExpectFilter(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, MatchWhereAndSplit) { + // Test MATCH (n) -[r]- (m) WHERE n.prop AND r.prop RETURN m + FakeDistributedDbAccessor dba; + auto prop = PROPERTY_PAIR("prop"); + AstStorage storage; + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m"))), + WHERE(AND(PROPERTY_LOOKUP("n", prop), PROPERTY_LOOKUP("r", prop))), RETURN("m"))); + // We expect `n.prop` filter right after scanning `n`. + CheckPlan(query, storage, ExpectScanAll(), ExpectFilter(), ExpectExpand(), ExpectFilter(), + ExpectProduce()); +} + +TYPED_TEST(TestPlanner, ReturnAsteriskOmitsLambdaSymbols) { + // Test MATCH (n) -[r* (ie, in | true)]- (m) RETURN * + AstStorage storage; + auto edge = EDGE_VARIABLE("r", Type::DEPTH_FIRST, Direction::BOTH); + edge->filter_lambda_.inner_edge = IDENT("ie"); + edge->filter_lambda_.inner_node = IDENT("in"); + edge->filter_lambda_.expression = LITERAL(true); + auto ret = storage.Create(); + ret->body_.all_identifiers = true; + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), ret)); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + FakeDistributedDbAccessor dba; + auto planner = MakePlanner(&dba, storage, symbol_table, query); + auto *produce = dynamic_cast(&planner.plan()); + ASSERT_TRUE(produce); + std::vector outputs; + for (const auto &output_symbol : produce->OutputSymbols(symbol_table)) { + outputs.emplace_back(output_symbol.name()); + } + // We expect `*` expanded to `n`, `r` and `m`. + EXPECT_EQ(outputs.size(), 3); + for (const auto &name : {"n", "r", "m"}) { + EXPECT_TRUE(memgraph::utils::Contains(outputs, name)); + } +} + +TYPED_TEST(TestPlanner, FilterRegexMatchIndex) { + // Test MATCH (n :label) WHERE n.prop =~ "regex" RETURN n + AstStorage storage; + FakeDistributedDbAccessor dba; + auto prop = dba.Property("prop"); + auto label = dba.Label("label"); + dba.SetIndexCount(label, 0); + dba.SetIndexCount(label, prop, 0); + auto *regex_match = storage.Create(PROPERTY_LOOKUP("n", prop), LITERAL("regex")); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"))), WHERE(regex_match), RETURN("n"))); + // We expect that we use index by property range where lower bound is an empty + // string. Filter must still remain in place, because we don't have regex + // based index. + Bound lower_bound(LITERAL(""), Bound::Type::INCLUSIVE); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelPropertyRange(label, prop, lower_bound, std::nullopt), + ExpectFilter(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, FilterRegexMatchPreferEqualityIndex) { + // Test MATCH (n :label) WHERE n.prop =~ "regex" AND n.prop = 42 RETURN n + AstStorage storage; + FakeDistributedDbAccessor dba; + auto prop = PROPERTY_PAIR("prop"); + auto label = dba.Label("label"); + dba.SetIndexCount(label, 0); + dba.SetIndexCount(label, prop.second, 0); + auto *regex_match = storage.Create(PROPERTY_LOOKUP("n", prop), LITERAL("regex")); + auto *lit_42 = LITERAL(42); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"))), + WHERE(AND(regex_match, EQ(PROPERTY_LOOKUP("n", prop), lit_42))), RETURN("n"))); + // We expect that we use index by property value equal to 42, because that's + // much better than property range for regex matching. + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelPropertyValue(label, prop, lit_42), ExpectFilter(), + ExpectProduce()); +} + +TYPED_TEST(TestPlanner, FilterRegexMatchPreferEqualityIndex2) { + // Test MATCH (n :label) + // WHERE n.prop =~ "regex" AND n.prop = 42 AND n.prop > 0 RETURN n + AstStorage storage; + FakeDistributedDbAccessor dba; + auto prop = PROPERTY_PAIR("prop"); + auto label = dba.Label("label"); + dba.SetIndexCount(label, 0); + dba.SetIndexCount(label, prop.second, 0); + auto *regex_match = storage.Create(PROPERTY_LOOKUP("n", prop), LITERAL("regex")); + auto *lit_42 = LITERAL(42); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"))), + WHERE(AND(AND(regex_match, EQ(PROPERTY_LOOKUP("n", prop), lit_42)), + GREATER(PROPERTY_LOOKUP("n", prop), LITERAL(0)))), + RETURN("n"))); + // We expect that we use index by property value equal to 42, because that's + // much better than property range. + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelPropertyValue(label, prop, lit_42), ExpectFilter(), + ExpectProduce()); +} + +TYPED_TEST(TestPlanner, FilterRegexMatchPreferRangeIndex) { + // Test MATCH (n :label) WHERE n.prop =~ "regex" AND n.prop > 42 RETURN n + AstStorage storage; + FakeDistributedDbAccessor dba; + auto prop = dba.Property("prop"); + auto label = dba.Label("label"); + dba.SetIndexCount(label, 0); + dba.SetIndexCount(label, prop, 0); + auto *regex_match = storage.Create(PROPERTY_LOOKUP("n", prop), LITERAL("regex")); + auto *lit_42 = LITERAL(42); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"))), + WHERE(AND(regex_match, GREATER(PROPERTY_LOOKUP("n", prop), lit_42))), RETURN("n"))); + // We expect that we use index by property range on a concrete value (42), as + // it is much better than using a range from empty string for regex matching. + Bound lower_bound(lit_42, Bound::Type::EXCLUSIVE); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelPropertyRange(label, prop, lower_bound, std::nullopt), + ExpectFilter(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, CallProcedureStandalone) { + // Test CALL proc(1,2,3) YIELD field AS result + AstStorage storage; + auto *ast_call = storage.Create(); + ast_call->procedure_name_ = "proc"; + ast_call->arguments_ = {LITERAL(1), LITERAL(2), LITERAL(3)}; + ast_call->result_fields_ = {"field"}; + ast_call->result_identifiers_ = {IDENT("result")}; + auto *query = QUERY(SINGLE_QUERY(ast_call)); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + std::vector result_syms; + result_syms.reserve(ast_call->result_identifiers_.size()); + for (const auto *ident : ast_call->result_identifiers_) { + result_syms.push_back(symbol_table.at(*ident)); + } + FakeDistributedDbAccessor dba; + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan( + planner.plan(), symbol_table, + ExpectCallProcedure(ast_call->procedure_name_, ast_call->arguments_, ast_call->result_fields_, result_syms)); +} + +TYPED_TEST(TestPlanner, CallProcedureAfterScanAll) { + // Test MATCH (n) CALL proc(n) YIELD field AS result RETURN result + AstStorage storage; + auto *ast_call = storage.Create(); + ast_call->procedure_name_ = "proc"; + ast_call->arguments_ = {IDENT("n")}; + ast_call->result_fields_ = {"field"}; + ast_call->result_identifiers_ = {IDENT("result")}; + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), ast_call, RETURN("result"))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + std::vector result_syms; + result_syms.reserve(ast_call->result_identifiers_.size()); + for (const auto *ident : ast_call->result_identifiers_) { + result_syms.push_back(symbol_table.at(*ident)); + } + FakeDistributedDbAccessor dba; + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), + ExpectCallProcedure(ast_call->procedure_name_, ast_call->arguments_, ast_call->result_fields_, result_syms), + ExpectProduce()); +} + +TYPED_TEST(TestPlanner, CallProcedureBeforeScanAll) { + // Test CALL proc() YIELD field MATCH (n) WHERE n.prop = field RETURN n + AstStorage storage; + auto *ast_call = storage.Create(); + ast_call->procedure_name_ = "proc"; + ast_call->result_fields_ = {"field"}; + ast_call->result_identifiers_ = {IDENT("field")}; + FakeDistributedDbAccessor dba; + auto property = dba.Property("prop"); + auto *query = QUERY(SINGLE_QUERY(ast_call, MATCH(PATTERN(NODE("n"))), + WHERE(EQ(PROPERTY_LOOKUP("n", property), IDENT("field"))), RETURN("n"))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + std::vector result_syms; + result_syms.reserve(ast_call->result_identifiers_.size()); + for (const auto *ident : ast_call->result_identifiers_) { + result_syms.push_back(symbol_table.at(*ident)); + } + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, + ExpectCallProcedure(ast_call->procedure_name_, ast_call->arguments_, ast_call->result_fields_, result_syms), + ExpectScanAll(), ExpectFilter(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, ScanAllById) { + // Test MATCH (n) WHERE id(n) = 42 RETURN n + AstStorage storage; + auto *query = + QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), WHERE(EQ(FN("id", IDENT("n")), LITERAL(42))), RETURN("n"))); + CheckPlan(query, storage, ExpectScanAllById(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, ScanAllByIdExpandToExisting) { + // Test MATCH (n)-[r]-(m) WHERE id(m) = 42 RETURN r + AstStorage storage; + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m"))), + WHERE(EQ(FN("id", IDENT("m")), LITERAL(42))), RETURN("r"))); + CheckPlan(query, storage, ExpectScanAll(), ExpectScanAllById(), ExpectExpand(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, BfsToExisting) { + // Test MATCH (n)-[r *bfs]-(m) WHERE id(m) = 42 RETURN r + AstStorage storage; + auto *bfs = storage.Create(IDENT("r"), memgraph::query::EdgeAtom::Type::BREADTH_FIRST, + Direction::BOTH); + bfs->filter_lambda_.inner_edge = IDENT("ie"); + bfs->filter_lambda_.inner_node = IDENT("in"); + bfs->filter_lambda_.expression = LITERAL(true); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), bfs, NODE("m"))), + WHERE(EQ(FN("id", IDENT("m")), LITERAL(42))), RETURN("r"))); + CheckPlan(query, storage, ExpectScanAll(), ExpectScanAllById(), ExpectExpandBfs(), ExpectProduce()); +} + +TYPED_TEST(TestPlanner, LabelPropertyInListValidOptimization) { + // Test MATCH (n:label) WHERE n.property IN ['a'] RETURN n + AstStorage storage; + FakeDistributedDbAccessor dba; + auto label = dba.Label("label"); + auto property = PROPERTY_PAIR("property"); + auto *lit_list_a = LIST(LITERAL('a')); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"))), + WHERE(IN_LIST(PROPERTY_LOOKUP("n", property), lit_list_a)), RETURN("n"))); + { + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectFilter(), ExpectProduce()); + } + { + dba.SetIndexCount(label, 1); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabel(), ExpectFilter(), ExpectProduce()); + } + { + dba.SetIndexCount(label, property.second, 1); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectUnwind(), + ExpectScanAllByLabelPropertyValue(label, property, lit_list_a), ExpectProduce()); + } +} + +TYPED_TEST(TestPlanner, LabelPropertyInListWhereLabelPropertyOnLeftNotListOnRight) { + // Test MATCH (n:label) WHERE n.property IN 'a' RETURN n + AstStorage storage; + FakeDistributedDbAccessor dba; + auto label = dba.Label("label"); + auto property = PROPERTY_PAIR("property"); + auto *lit_a = LITERAL('a'); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"))), + WHERE(IN_LIST(PROPERTY_LOOKUP("n", property), lit_a)), RETURN("n"))); + { + dba.SetIndexCount(label, property.second, 1); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectFilter(), ExpectProduce()); + } +} + +TYPED_TEST(TestPlanner, LabelPropertyInListWhereLabelPropertyOnRight) { + // Test MATCH (n:label) WHERE ['a'] IN n.property RETURN n + AstStorage storage; + FakeDistributedDbAccessor dba; + auto label = dba.Label("label"); + auto property = PROPERTY_PAIR("property"); + auto *lit_list_a = LIST(LITERAL('a')); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"))), + WHERE(IN_LIST(lit_list_a, PROPERTY_LOOKUP("n", property))), RETURN("n"))); + { + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectFilter(), ExpectProduce()); + } + { + dba.SetIndexCount(label, 1); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabel(), ExpectFilter(), ExpectProduce()); + } + { + dba.SetIndexCount(label, property.second, 1); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabel(), ExpectFilter(), ExpectProduce()); + } +} + +TYPED_TEST(TestPlanner, Foreach) { + AstStorage storage; + FakeDistributedDbAccessor dba; + { + auto *i = NEXPR("i", IDENT("i")); + auto *query = QUERY(SINGLE_QUERY(FOREACH(i, {CREATE(PATTERN(NODE("n")))}))); + auto create = ExpectCreateNode(); + std::list updates{&create}; + std::list input; + CheckPlan(query, storage, ExpectForeach(input, updates)); + } + { + auto *i = NEXPR("i", IDENT("i")); + auto *query = QUERY(SINGLE_QUERY(FOREACH(i, {DELETE(IDENT("i"))}))); + auto del = ExpectDelete(); + std::list updates{&del}; + std::list input; + CheckPlan(query, storage, ExpectForeach({input}, updates)); + } + { + auto prop = dba.Property("prop"); + auto *i = NEXPR("i", IDENT("i")); + auto *query = QUERY(SINGLE_QUERY(FOREACH(i, {SET(PROPERTY_LOOKUP("i", prop), LITERAL(10))}))); + auto set_prop = ExpectSetProperty(); + std::list updates{&set_prop}; + std::list input; + CheckPlan(query, storage, ExpectForeach({input}, updates)); + } + { + auto *i = NEXPR("i", IDENT("i")); + auto *j = NEXPR("j", IDENT("j")); + auto *query = QUERY(SINGLE_QUERY(FOREACH(i, {FOREACH(j, {CREATE(PATTERN(NODE("n"))), DELETE(IDENT("i"))})}))); + auto create = ExpectCreateNode(); + auto del = ExpectDelete(); + std::list input; + std::list nested_updates{{&create, &del}}; + auto nested_foreach = ExpectForeach(input, nested_updates); + std::list updates{&nested_foreach}; + CheckPlan(query, storage, ExpectForeach(input, updates)); + } + { + auto *i = NEXPR("i", IDENT("i")); + auto *j = NEXPR("j", IDENT("j")); + auto create = ExpectCreateNode(); + std::list empty; + std::list updates{&create}; + auto input_op = ExpectForeach(empty, updates); + std::list input{&input_op}; + auto *query = + QUERY(SINGLE_QUERY(FOREACH(i, {CREATE(PATTERN(NODE("n")))}), FOREACH(j, {CREATE(PATTERN(NODE("n")))}))); + CheckPlan(query, storage, ExpectForeach(input, updates)); + } +} +*/ + +} // namespace