From 814c5eb397be925fbeea2742d6bb1251f71b4465 Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Wed, 23 Nov 2022 15:15:26 +0100 Subject: [PATCH 01/85] Add the possibility of primary-key based indexing Add new possibility to base our indexing solution on. Add ScanAllOperator that represents the semantics and integrate its use through index_lookup. --- src/query/v2/plan/operator.cpp | 15 ++++++++++++++ src/query/v2/plan/operator.lcp | 24 +++++++++++++++++++++- src/query/v2/plan/rewrite/index_lookup.hpp | 22 ++++++++++++++++++++ src/query/v2/plan/vertex_count_cache.hpp | 24 +++++++++++++++++++++- src/query/v2/shard_request_manager.hpp | 5 +++++ 5 files changed, 88 insertions(+), 2 deletions(-) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index 068ca9192..196f405aa 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -92,6 +92,7 @@ extern const Event ScanAllByLabelPropertyRangeOperator; extern const Event ScanAllByLabelPropertyValueOperator; extern const Event ScanAllByLabelPropertyOperator; extern const Event ScanAllByIdOperator; +extern const Event ScanAllByPrimaryKeyOperator; extern const Event ExpandOperator; extern const Event ExpandVariableOperator; extern const Event ConstructNamedPathOperator; @@ -545,6 +546,20 @@ UniqueCursorPtr ScanAllByLabelProperty::MakeCursor(utils::MemoryResource *mem) c throw QueryRuntimeException("ScanAllByLabelProperty is not supported"); } +ScanAllByPrimaryKey::ScanAllByPrimaryKey(const std::shared_ptr<LogicalOperator> &input, Symbol output_symbol, + storage::v3::LabelId label, std::vector<query::v2::Expression *> primary_key, + storage::v3::View view) + : ScanAll(input, output_symbol, view), label_(label), primary_key_(primary_key) { + MG_ASSERT(primary_key.front()); +} + +ACCEPT_WITH_INPUT(ScanAllByPrimaryKey) + +UniqueCursorPtr ScanAllByPrimaryKey::MakeCursor(utils::MemoryResource *mem) const { + // EventCounter::IncrementCounter(EventCounter::ScanAllByPrimaryKeyOperator); + throw QueryRuntimeException("ScanAllByPrimaryKey cursur is yet to be implemented."); +} + ScanAllById::ScanAllById(const std::shared_ptr<LogicalOperator> &input, Symbol output_symbol, Expression *expression, storage::v3::View view) : ScanAll(input, output_symbol, view), expression_(expression) { diff --git a/src/query/v2/plan/operator.lcp b/src/query/v2/plan/operator.lcp index d6277809c..680bb4de3 100644 --- a/src/query/v2/plan/operator.lcp +++ b/src/query/v2/plan/operator.lcp @@ -111,6 +111,7 @@ class ScanAllByLabelPropertyRange; class ScanAllByLabelPropertyValue; class ScanAllByLabelProperty; class ScanAllById; +class ScanAllByPrimaryKey; class Expand; class ExpandVariable; class ConstructNamedPath; @@ -141,7 +142,7 @@ class Foreach; using LogicalOperatorCompositeVisitor = utils::CompositeVisitor< Once, CreateNode, CreateExpand, ScanAll, ScanAllByLabel, ScanAllByLabelPropertyRange, ScanAllByLabelPropertyValue, - ScanAllByLabelProperty, ScanAllById, + ScanAllByLabelProperty, ScanAllById, ScanAllByPrimaryKey, Expand, ExpandVariable, ConstructNamedPath, Filter, Produce, Delete, SetProperty, SetProperties, SetLabels, RemoveProperty, RemoveLabels, EdgeUniquenessFilter, Accumulate, Aggregate, Skip, Limit, OrderBy, Merge, @@ -841,7 +842,28 @@ given label and property. (:serialize (:slk)) (:clone)) +(lcp:define-class scan-all-by-primary-key (scan-all) + ((label "::storage::v3::LabelId" :scope :public) + (primary-key "std::vector<Expression*>" :scope :public) + (expression "Expression *" :scope :public + :slk-save #'slk-save-ast-pointer + :slk-load (slk-load-ast-pointer "Expression"))) + (:documentation + "ScanAll producing a single node with specified by the label and primary key") + (:public + #>cpp + ScanAllByPrimaryKey() {} + ScanAllByPrimaryKey(const std::shared_ptr<LogicalOperator> &input, + Symbol output_symbol, + storage::v3::LabelId label, + std::vector<query::v2::Expression*> primary_key, + storage::v3::View view = storage::v3::View::OLD); + bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; + UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; + cpp<#) + (:serialize (:slk)) + (:clone)) (lcp:define-class scan-all-by-id (scan-all) ((expression "Expression *" :scope :public diff --git a/src/query/v2/plan/rewrite/index_lookup.hpp b/src/query/v2/plan/rewrite/index_lookup.hpp index 57ddba54e..a861153e9 100644 --- a/src/query/v2/plan/rewrite/index_lookup.hpp +++ b/src/query/v2/plan/rewrite/index_lookup.hpp @@ -25,8 +25,10 @@ #include <gflags/gflags.h> +#include "query/v2/frontend/ast/ast.hpp" #include "query/v2/plan/operator.hpp" #include "query/v2/plan/preprocess.hpp" +#include "storage/v3/id_types.hpp" DECLARE_int64(query_vertex_count_to_expand_existing); @@ -584,6 +586,26 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor { // Without labels, we cannot generate any indexed ScanAll. return nullptr; } + + // First, try to see if we can find a vertex based on the possibly + // supplied primary key. + auto property_filters = filters_.PropertyFilters(node_symbol); + storage::v3::LabelId prim_label; + std::vector<query::v2::Expression *> primary_key; + + if (!property_filters.empty()) { + for (const auto &label : labels) { + if (db_->LabelIndexExists(GetLabel(label))) { + prim_label = GetLabel(label); + primary_key = db_->ExtractPrimaryKey(prim_label, property_filters); + break; + } + } + if (!primary_key.empty()) { + return std::make_unique<ScanAllByPrimaryKey>(input, node_symbol, prim_label, primary_key); + } + } + auto found_index = FindBestLabelPropertyIndex(node_symbol, bound_symbols); if (found_index && // Use label+property index if we satisfy max_vertex_count. diff --git a/src/query/v2/plan/vertex_count_cache.hpp b/src/query/v2/plan/vertex_count_cache.hpp index a7bfbdf85..70b8d324b 100644 --- a/src/query/v2/plan/vertex_count_cache.hpp +++ b/src/query/v2/plan/vertex_count_cache.hpp @@ -12,9 +12,11 @@ /// @file #pragma once +#include <iterator> #include <optional> #include "query/v2/bindings/typed_value.hpp" +#include "query/v2/plan/preprocess.hpp" #include "query/v2/shard_request_manager.hpp" #include "storage/v3/conversions.hpp" #include "storage/v3/id_types.hpp" @@ -52,11 +54,31 @@ class VertexCountCache { return 1; } - // For now return true if label is primary label bool LabelIndexExists(storage::v3::LabelId label) { return shard_request_manager_->IsPrimaryLabel(label); } bool LabelPropertyIndexExists(storage::v3::LabelId /*label*/, storage::v3::PropertyId /*property*/) { return false; } + std::vector<query::v2::Expression *> ExtractPrimaryKey(storage::v3::LabelId label, + std::vector<query::v2::plan::FilterInfo> property_filters) { + std::vector<query::v2::Expression *> pk; + const auto schema = shard_request_manager_->GetSchemaForLabel(label); + + std::vector<storage::v3::PropertyId> schema_properties; + schema_properties.reserve(schema.size()); + + std::transform(schema.begin(), schema.end(), std::back_inserter(schema_properties), + [](const auto &schema_elem) { return schema_elem.property_id; }); + + for (const auto &property_filter : property_filters) { + const auto &property_id = NameToProperty(property_filter.property_filter->property_.name); + if (std::find(schema_properties.begin(), schema_properties.end(), property_id) != schema_properties.end()) { + pk.push_back(property_filter.expression); + } + } + + return pk.size() == schema_properties.size() ? pk : std::vector<query::v2::Expression *>{}; + } + msgs::ShardRequestManagerInterface *shard_request_manager_; }; diff --git a/src/query/v2/shard_request_manager.hpp b/src/query/v2/shard_request_manager.hpp index a73201046..44ea02bba 100644 --- a/src/query/v2/shard_request_manager.hpp +++ b/src/query/v2/shard_request_manager.hpp @@ -131,6 +131,7 @@ class ShardRequestManagerInterface { virtual const std::string &EdgeTypeToName(memgraph::storage::v3::EdgeTypeId type) const = 0; virtual bool IsPrimaryLabel(LabelId label) const = 0; virtual bool IsPrimaryKey(LabelId primary_label, PropertyId property) const = 0; + virtual std::vector<coordinator::SchemaProperty> GetSchemaForLabel(LabelId label) const = 0; }; // TODO(kostasrim)rename this class template @@ -244,6 +245,10 @@ class ShardRequestManager : public ShardRequestManagerInterface { }) != schema_it->second.end(); } + std::vector<coordinator::SchemaProperty> GetSchemaForLabel(LabelId label) const override { + return shards_map_.schemas.at(label); + } + bool IsPrimaryLabel(LabelId label) const override { return shards_map_.label_spaces.contains(label); } // TODO(kostasrim) Simplify return result From 1b73ca4860c4e717596be181d3fd7f9b6b501db4 Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Wed, 23 Nov 2022 15:49:59 +0100 Subject: [PATCH 02/85] Remove ScanAllById operator --- src/query/v2/plan/cost_estimator.hpp | 2 -- src/query/v2/plan/operator.cpp | 19 ------------ src/query/v2/plan/operator.lcp | 21 +------------ src/query/v2/plan/pretty_print.cpp | 18 ----------- src/query/v2/plan/pretty_print.hpp | 2 -- src/query/v2/plan/read_write_type_checker.cpp | 1 - src/query/v2/plan/read_write_type_checker.hpp | 1 - src/query/v2/plan/rewrite/index_lookup.hpp | 30 ++----------------- 8 files changed, 3 insertions(+), 91 deletions(-) diff --git a/src/query/v2/plan/cost_estimator.hpp b/src/query/v2/plan/cost_estimator.hpp index 8cba2505f..250274a73 100644 --- a/src/query/v2/plan/cost_estimator.hpp +++ b/src/query/v2/plan/cost_estimator.hpp @@ -149,8 +149,6 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor { return true; } - // TODO: Cost estimate ScanAllById? - // For the given op first increments the cardinality and then cost. #define POST_VISIT_CARD_FIRST(NAME) \ bool PostVisit(NAME &) override { \ diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index 196f405aa..29bf07792 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -91,7 +91,6 @@ extern const Event ScanAllByLabelOperator; extern const Event ScanAllByLabelPropertyRangeOperator; extern const Event ScanAllByLabelPropertyValueOperator; extern const Event ScanAllByLabelPropertyOperator; -extern const Event ScanAllByIdOperator; extern const Event ScanAllByPrimaryKeyOperator; extern const Event ExpandOperator; extern const Event ExpandVariableOperator; @@ -560,24 +559,6 @@ UniqueCursorPtr ScanAllByPrimaryKey::MakeCursor(utils::MemoryResource *mem) cons throw QueryRuntimeException("ScanAllByPrimaryKey cursur is yet to be implemented."); } -ScanAllById::ScanAllById(const std::shared_ptr<LogicalOperator> &input, Symbol output_symbol, Expression *expression, - storage::v3::View view) - : ScanAll(input, output_symbol, view), expression_(expression) { - MG_ASSERT(expression); -} - -ACCEPT_WITH_INPUT(ScanAllById) - -UniqueCursorPtr ScanAllById::MakeCursor(utils::MemoryResource *mem) const { - EventCounter::IncrementCounter(EventCounter::ScanAllByIdOperator); - // TODO Reimplement when we have reliable conversion between hash value and pk - auto vertices = [](Frame & /*frame*/, ExecutionContext & /*context*/) -> std::optional<std::vector<VertexAccessor>> { - return std::nullopt; - }; - return MakeUniqueCursorPtr<ScanAllCursor<decltype(vertices)>>(mem, output_symbol_, input_->MakeCursor(mem), - std::move(vertices), "ScanAllById"); -} - namespace { template <class TEdges> diff --git a/src/query/v2/plan/operator.lcp b/src/query/v2/plan/operator.lcp index 680bb4de3..4a4df3785 100644 --- a/src/query/v2/plan/operator.lcp +++ b/src/query/v2/plan/operator.lcp @@ -110,7 +110,6 @@ class ScanAllByLabel; class ScanAllByLabelPropertyRange; class ScanAllByLabelPropertyValue; class ScanAllByLabelProperty; -class ScanAllById; class ScanAllByPrimaryKey; class Expand; class ExpandVariable; @@ -142,7 +141,7 @@ class Foreach; using LogicalOperatorCompositeVisitor = utils::CompositeVisitor< Once, CreateNode, CreateExpand, ScanAll, ScanAllByLabel, ScanAllByLabelPropertyRange, ScanAllByLabelPropertyValue, - ScanAllByLabelProperty, ScanAllById, ScanAllByPrimaryKey, + ScanAllByLabelProperty, ScanAllByPrimaryKey, Expand, ExpandVariable, ConstructNamedPath, Filter, Produce, Delete, SetProperty, SetProperties, SetLabels, RemoveProperty, RemoveLabels, EdgeUniquenessFilter, Accumulate, Aggregate, Skip, Limit, OrderBy, Merge, @@ -865,24 +864,6 @@ given label and property. (:serialize (:slk)) (:clone)) -(lcp:define-class scan-all-by-id (scan-all) - ((expression "Expression *" :scope :public - :slk-save #'slk-save-ast-pointer - :slk-load (slk-load-ast-pointer "Expression"))) - (:documentation - "ScanAll producing a single node with ID equal to evaluated expression") - (:public - #>cpp - ScanAllById() {} - ScanAllById(const std::shared_ptr<LogicalOperator> &input, - Symbol output_symbol, Expression *expression, - storage::v3::View view = storage::v3::View::OLD); - - bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; - UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; - cpp<#) - (:serialize (:slk)) - (:clone)) (lcp:define-struct expand-common () ( diff --git a/src/query/v2/plan/pretty_print.cpp b/src/query/v2/plan/pretty_print.cpp index b756a6299..76cc6ac26 100644 --- a/src/query/v2/plan/pretty_print.cpp +++ b/src/query/v2/plan/pretty_print.cpp @@ -86,14 +86,6 @@ bool PlanPrinter::PreVisit(query::v2::plan::ScanAllByLabelProperty &op) { return true; } -bool PlanPrinter::PreVisit(ScanAllById &op) { - WithPrintLn([&](auto &out) { - out << "* ScanAllById" - << " (" << op.output_symbol_.name() << ")"; - }); - return true; -} - bool PlanPrinter::PreVisit(query::v2::plan::Expand &op) { WithPrintLn([&](auto &out) { *out_ << "* Expand (" << op.input_symbol_.name() << ")" @@ -488,16 +480,6 @@ bool PlanToJsonVisitor::PreVisit(ScanAllByLabelProperty &op) { return false; } -bool PlanToJsonVisitor::PreVisit(ScanAllById &op) { - json self; - self["name"] = "ScanAllById"; - self["output_symbol"] = ToJson(op.output_symbol_); - op.input_->Accept(*this); - self["input"] = PopOutput(); - output_ = std::move(self); - return false; -} - bool PlanToJsonVisitor::PreVisit(CreateNode &op) { json self; self["name"] = "CreateNode"; diff --git a/src/query/v2/plan/pretty_print.hpp b/src/query/v2/plan/pretty_print.hpp index c7442b196..66fa31556 100644 --- a/src/query/v2/plan/pretty_print.hpp +++ b/src/query/v2/plan/pretty_print.hpp @@ -68,7 +68,6 @@ class PlanPrinter : public virtual HierarchicalLogicalOperatorVisitor { bool PreVisit(ScanAllByLabelPropertyValue &) override; bool PreVisit(ScanAllByLabelPropertyRange &) override; bool PreVisit(ScanAllByLabelProperty &) override; - bool PreVisit(ScanAllById &) override; bool PreVisit(Expand &) override; bool PreVisit(ExpandVariable &) override; @@ -196,7 +195,6 @@ class PlanToJsonVisitor : public virtual HierarchicalLogicalOperatorVisitor { bool PreVisit(ScanAllByLabelPropertyRange &) override; bool PreVisit(ScanAllByLabelPropertyValue &) override; bool PreVisit(ScanAllByLabelProperty &) override; - bool PreVisit(ScanAllById &) override; bool PreVisit(Produce &) override; bool PreVisit(Accumulate &) override; diff --git a/src/query/v2/plan/read_write_type_checker.cpp b/src/query/v2/plan/read_write_type_checker.cpp index 6cc38cedf..ec1459f6a 100644 --- a/src/query/v2/plan/read_write_type_checker.cpp +++ b/src/query/v2/plan/read_write_type_checker.cpp @@ -35,7 +35,6 @@ PRE_VISIT(ScanAllByLabel, RWType::R, true) PRE_VISIT(ScanAllByLabelPropertyRange, RWType::R, true) PRE_VISIT(ScanAllByLabelPropertyValue, RWType::R, true) PRE_VISIT(ScanAllByLabelProperty, RWType::R, true) -PRE_VISIT(ScanAllById, RWType::R, true) PRE_VISIT(Expand, RWType::R, true) PRE_VISIT(ExpandVariable, RWType::R, true) diff --git a/src/query/v2/plan/read_write_type_checker.hpp b/src/query/v2/plan/read_write_type_checker.hpp index a3c2f1a46..e5f429035 100644 --- a/src/query/v2/plan/read_write_type_checker.hpp +++ b/src/query/v2/plan/read_write_type_checker.hpp @@ -59,7 +59,6 @@ class ReadWriteTypeChecker : public virtual HierarchicalLogicalOperatorVisitor { bool PreVisit(ScanAllByLabelPropertyValue &) override; bool PreVisit(ScanAllByLabelPropertyRange &) override; bool PreVisit(ScanAllByLabelProperty &) override; - bool PreVisit(ScanAllById &) override; bool PreVisit(Expand &) override; bool PreVisit(ExpandVariable &) override; diff --git a/src/query/v2/plan/rewrite/index_lookup.hpp b/src/query/v2/plan/rewrite/index_lookup.hpp index a861153e9..41e24d2b6 100644 --- a/src/query/v2/plan/rewrite/index_lookup.hpp +++ b/src/query/v2/plan/rewrite/index_lookup.hpp @@ -273,15 +273,6 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor { return true; } - bool PreVisit(ScanAllById &op) override { - prev_ops_.push_back(&op); - return true; - } - bool PostVisit(ScanAllById &) override { - prev_ops_.pop_back(); - return true; - } - bool PreVisit(ConstructNamedPath &op) override { prev_ops_.push_back(&op); return true; @@ -561,25 +552,8 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor { const auto &view = scan.view_; const auto &modified_symbols = scan.ModifiedSymbols(*symbol_table_); std::unordered_set<Symbol> bound_symbols(modified_symbols.begin(), modified_symbols.end()); - auto are_bound = [&bound_symbols](const auto &used_symbols) { - for (const auto &used_symbol : used_symbols) { - if (!utils::Contains(bound_symbols, used_symbol)) { - return false; - } - } - return true; - }; - // First, try to see if we can find a vertex by ID. - if (!max_vertex_count || *max_vertex_count >= 1) { - for (const auto &filter : filters_.IdFilters(node_symbol)) { - if (filter.id_filter->is_symbol_in_value_ || !are_bound(filter.used_symbols)) continue; - auto *value = filter.id_filter->value_; - filter_exprs_for_removal_.insert(filter.expression); - filters_.EraseFilter(filter); - return std::make_unique<ScanAllById>(input, node_symbol, value, view); - } - } - // Now try to see if we can use label+property index. If not, try to use + + // Try to see if we can use label+property index. If not, try to use // just the label index. const auto labels = filters_.FilteredLabels(node_symbol); if (labels.empty()) { From a65ea4fe01e7398eb66dd0045ef9fef305370713 Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Thu, 24 Nov 2022 09:37:47 +0100 Subject: [PATCH 03/85] Conform clang-tidy --- src/query/v2/plan/operator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index 29bf07792..c55c5cb86 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -554,7 +554,7 @@ ScanAllByPrimaryKey::ScanAllByPrimaryKey(const std::shared_ptr<LogicalOperator> ACCEPT_WITH_INPUT(ScanAllByPrimaryKey) -UniqueCursorPtr ScanAllByPrimaryKey::MakeCursor(utils::MemoryResource *mem) const { +UniqueCursorPtr ScanAllByPrimaryKey::MakeCursor(utils::MemoryResource * /*mem*/) const { // EventCounter::IncrementCounter(EventCounter::ScanAllByPrimaryKeyOperator); throw QueryRuntimeException("ScanAllByPrimaryKey cursur is yet to be implemented."); } From 22e3164e60b3c758402b0d62fb19e3c2825ba300 Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Mon, 28 Nov 2022 16:18:28 +0100 Subject: [PATCH 04/85] 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<SortItem> expressions; }; + +// new stuff begin + +struct OrderByv2 { + std::vector<query::v2::SortItem> 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<Clause *> set; }; @@ -153,6 +182,42 @@ auto GetPropertyLookup(AstStorage &storage, TDbAccessor &, Expression *expr, return storage.Create<PropertyLookup>(expr, storage.GetPropertyIx(prop_pair.first)); } +// new stuff begin + +template <class TDbAccessor> +auto GetPropertyLookup(memgraph::query::v2::AstStorage &storage, TDbAccessor &dba, const std::string &name, + memgraph::storage::v3::PropertyId property) { + return storage.Create<query::v2::PropertyLookup>(storage.Create<query::v2::Identifier>(name), + storage.GetPropertyIx(dba.PropertyToName(property))); +} + +template <class TDbAccessor> +auto GetPropertyLookup(memgraph::query::v2::AstStorage &storage, TDbAccessor &dba, + memgraph::query::v2::Expression *expr, memgraph::storage::v3::PropertyId property) { + return storage.Create<query::v2::PropertyLookup>(expr, storage.GetPropertyIx(dba.PropertyToName(property))); +} + +template <class TDbAccessor> +auto GetPropertyLookup(memgraph::query::v2::AstStorage &storage, TDbAccessor &dba, + memgraph::query::v2::Expression *expr, const std::string &property) { + return storage.Create<query::v2::PropertyLookup>(expr, storage.GetPropertyIx(property)); +} + +template <class TDbAccessor> +auto GetPropertyLookup(memgraph::query::v2::AstStorage &storage, TDbAccessor &, const std::string &name, + const std::pair<std::string, memgraph::storage::v3::PropertyId> &prop_pair) { + return storage.Create<query::v2::PropertyLookup>(storage.Create<query::v2::Identifier>(name), + storage.GetPropertyIx(prop_pair.first)); +} + +template <class TDbAccessor> +auto GetPropertyLookup(memgraph::query::v2::AstStorage &storage, TDbAccessor &, memgraph::query::v2::Expression *expr, + const std::pair<std::string, memgraph::storage::v3::PropertyId> &prop_pair) { + return storage.Create<query::v2::PropertyLookup>(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<std::st return node; } +// new stuff begin +auto GetNode(memgraph::query::v2::AstStorage &storage, const std::string &name, + std::optional<std::string> label = std::nullopt) { + auto node = storage.Create<query::v2::NodeAtom>(storage.Create<query::v2::Identifier>(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<PatternAtom *> atoms) { auto pattern = storage.Create<Pattern>(); @@ -227,6 +301,26 @@ auto GetPattern(AstStorage &storage, const std::string &name, std::vector<Patter return pattern; } +// new stuff begin + +auto GetPattern(memgraph::query::v2::AstStorage &storage, std::vector<query::v2::PatternAtom *> atoms) { + auto pattern = storage.Create<query::v2::Pattern>(); + pattern->identifier_ = storage.Create<query::v2::Identifier>(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<query::v2::PatternAtom *> atoms) { + auto pattern = storage.Create<query::v2::Pattern>(); + pattern->identifier_ = storage.Create<query::v2::Identifier>(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<Pattern *> patter return with_patterns; } +// new stuff begin + +template <class TWithPatterns> +auto GetWithPatterns(TWithPatterns *with_patterns, std::vector<query::v2::Pattern *> 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 <class... T> +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 <class... T> +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 <class... T> +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::v2::CypherQuery>(); + query->single_query_ = single_query; + return query; +} + +template <class... T> +auto GetQuery(query::v2::AstStorage &storage, query::v2::SingleQuery *single_query, T *...cypher_unions) { + auto *query = storage.Create<query::v2::CypherQuery>(); + query->single_query_ = single_query; + query->cypher_unions_ = std::vector<query::v2::CypherUnion *>{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<memgraph::query::v2::Identifier>(name); + auto *named_expr = storage.Create<memgraph::query::v2::NamedExpression>(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<memgraph::query::v2::Identifier>(name); + body.named_expressions.emplace_back(named_expr); +} +template <class... T> +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 <class... T> +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 <class... T> +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<memgraph::query::v2::Identifier>(name); + body.named_expressions.emplace_back(named_expr); + FillReturnBody(storage, body, rest...); +} +template <class... T> +void FillReturnBody(query::v2::AstStorage &storage, query::v2::ReturnBody &body, const std::string &name, T... rest) { + auto *ident = storage.Create<memgraph::query::v2::Identifier>(name); + auto *named_expr = storage.Create<memgraph::query::v2::NamedExpression>(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 <class... T> +auto GetReturn(query::v2::AstStorage &storage, bool distinct, T... exprs) { + auto ret = storage.Create<query::v2::Return>(); + 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<memgraph::query::Match>(true), {__VA_ARGS__}) #define MATCH(...) \ memgraph::query::test_common::GetWithPatterns(storage.Create<memgraph::query::Match>(), {__VA_ARGS__}) +#define MATCH_V2(...) \ + memgraph::query::test_common::GetWithPatterns(storage.Create<memgraph::query::v2::Match>(), {__VA_ARGS__}) #define WHERE(expr) storage.Create<memgraph::query::Where>((expr)) +#define WHERE_V2(expr) storage.Create<memgraph::query::v2::Where>((expr)) #define CREATE(...) \ memgraph::query::test_common::GetWithPatterns(storage.Create<memgraph::query::Create>(), {__VA_ARGS__}) #define IDENT(...) storage.Create<memgraph::query::Identifier>(__VA_ARGS__) @@ -501,6 +752,8 @@ auto GetForeach(AstStorage &storage, NamedExpression *named_expr, const std::vec storage.Create<memgraph::query::MapLiteral>( \ std::unordered_map<memgraph::query::PropertyIx, memgraph::query::Expression *>{__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<memgraph::query::ParameterLookup>((token_position)) #define NEXPR(name, expr) storage.Create<memgraph::query::NamedExpression>((name), (expr)) @@ -536,6 +789,8 @@ auto GetForeach(AstStorage &storage, NamedExpression *named_expr, const std::vec std::vector<memgraph::query::PropertyIx>{(property)}) #define QUERY(...) memgraph::query::test_common::GetQuery(storage, __VA_ARGS__) #define SINGLE_QUERY(...) memgraph::query::test_common::GetSingleQuery(storage.Create<SingleQuery>(), __VA_ARGS__) +#define SINGLE_QUERY_V2(...) \ + memgraph::query::test_common::GetSingleQuery(storage.Create<memgraph::query::v2::SingleQuery>(), __VA_ARGS__) #define UNION(...) memgraph::query::test_common::GetCypherUnion(storage.Create<CypherUnion>(true), __VA_ARGS__) #define UNION_ALL(...) memgraph::query::test_common::GetCypherUnion(storage.Create<CypherUnion>(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<ScanAllByLabelProperty> { memgraph::storage::PropertyId property_; }; +class ExpectScanAllByPrimaryKey : public OpChecker<v2::plan::ScanAllByPrimaryKey> { + public: + ExpectScanAllByPrimaryKey(memgraph::storage::v3::LabelId label, const std::vector<Expression *> &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<Expression *> properties_; +}; + class ExpectCartesian : public OpChecker<Cartesian> { public: ExpectCartesian(const std::list<std::unique_ptr<BaseOpChecker>> &left, @@ -486,4 +520,108 @@ class FakeDbAccessor { std::vector<std::tuple<memgraph::storage::LabelId, memgraph::storage::PropertyId, int64_t>> 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<std::string, memgraph::storage::v3::LabelId> primary_labels_; +// std::unordered_map<std::string, memgraph::storage::v3::LabelId> secondary_labels_; +// std::unordered_map<std::string, memgraph::storage::EdgeTypeId> edge_types_; +// std::unordered_map<std::string, memgraph::storage::PropertyId> primary_properties_; +// std::unordered_map<std::string, memgraph::storage::PropertyId> secondary_properties_; + +// std::unordered_map<memgraph::storage::v3::LabelId, int64_t> label_index_; +// std::vector<std::tuple<memgraph::storage::v3::LabelId, memgraph::storage::PropertyId, int64_t>> +// 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 <gmock/gmock.h> +#include <gtest/gtest.h> + +#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<std::unique_ptr<BaseOpChecker>> &checkers, const SymbolTable &symbol_table) + : symbol_table_(symbol_table) { + for (const auto &checker : checkers) checkers_.emplace_back(checker.get()); + } + + PlanChecker(const std::list<BaseOpChecker *> &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<BaseOpChecker *> checkers_; + const SymbolTable &symbol_table_; +}; + +template <class TOp> +class OpChecker : public BaseOpChecker { + public: + void CheckOp(LogicalOperator &op, const SymbolTable &symbol_table) override { + auto *expected_op = dynamic_cast<TOp *>(&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<ScanAllByPrimaryKey>; + +using ExpectCreateNode = OpChecker<CreateNode>; +using ExpectCreateExpand = OpChecker<CreateExpand>; +using ExpectDelete = OpChecker<Delete>; +using ExpectScanAll = OpChecker<ScanAll>; +using ExpectScanAllByLabel = OpChecker<ScanAllByLabel>; +// using ExpectScanAllById = OpChecker<ScanAllById>; +using ExpectExpand = OpChecker<Expand>; +using ExpectFilter = OpChecker<Filter>; +using ExpectConstructNamedPath = OpChecker<ConstructNamedPath>; +using ExpectProduce = OpChecker<Produce>; +using ExpectSetProperty = OpChecker<SetProperty>; +using ExpectSetProperties = OpChecker<SetProperties>; +using ExpectSetLabels = OpChecker<SetLabels>; +using ExpectRemoveProperty = OpChecker<RemoveProperty>; +using ExpectRemoveLabels = OpChecker<RemoveLabels>; +using ExpectEdgeUniquenessFilter = OpChecker<EdgeUniquenessFilter>; +using ExpectSkip = OpChecker<Skip>; +using ExpectLimit = OpChecker<Limit>; +using ExpectOrderBy = OpChecker<OrderBy>; +using ExpectUnwind = OpChecker<Unwind>; +using ExpectDistinct = OpChecker<Distinct>; + +// class ExpectForeach : public OpChecker<Foreach> { +// public: +// ExpectForeach(const std::list<BaseOpChecker *> &input, const std::list<BaseOpChecker *> &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<BaseOpChecker *> input_; +// std::list<BaseOpChecker *> updates_; +// }; + +// class ExpectExpandVariable : public OpChecker<ExpandVariable> { +// public: +// void ExpectOp(ExpandVariable &op, const SymbolTable &) override { +// EXPECT_EQ(op.type_, memgraph::query::EdgeAtom::Type::DEPTH_FIRST); +// } +// }; + +// class ExpectExpandBfs : public OpChecker<ExpandVariable> { +// public: +// void ExpectOp(ExpandVariable &op, const SymbolTable &) override { +// EXPECT_EQ(op.type_, memgraph::query::EdgeAtom::Type::BREADTH_FIRST); +// } +// }; + +// class ExpectAccumulate : public OpChecker<Accumulate> { +// public: +// explicit ExpectAccumulate(const std::unordered_set<Symbol> &symbols) : symbols_(symbols) {} + +// void ExpectOp(Accumulate &op, const SymbolTable &) override { +// std::unordered_set<Symbol> got_symbols(op.symbols_.begin(), op.symbols_.end()); +// EXPECT_EQ(symbols_, got_symbols); +// } + +// private: +// const std::unordered_set<Symbol> symbols_; +// }; + +// class ExpectAggregate : public OpChecker<Aggregate> { +// public: +// ExpectAggregate(const std::vector<memgraph::query::Aggregation *> &aggregations, +// const std::unordered_set<memgraph::query::Expression *> &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<size_t> got_group_by; +// for (auto *expr : op.group_by_) got_group_by.insert(typeid(*expr).hash_code()); +// std::unordered_set<size_t> 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<memgraph::query::Aggregation *> aggregations_; +// std::unordered_set<memgraph::query::Expression *> group_by_; +// }; + +// class ExpectMerge : public OpChecker<Merge> { +// public: +// ExpectMerge(const std::list<BaseOpChecker *> &on_match, const std::list<BaseOpChecker *> &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<BaseOpChecker *> &on_match_; +// const std::list<BaseOpChecker *> &on_create_; +// }; + +// class ExpectOptional : public OpChecker<Optional> { +// public: +// explicit ExpectOptional(const std::list<BaseOpChecker *> &optional) : optional_(optional) {} + +// ExpectOptional(const std::vector<Symbol> &optional_symbols, const std::list<BaseOpChecker *> &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<Symbol> optional_symbols_; +// const std::list<BaseOpChecker *> &optional_; +// }; + +// class ExpectScanAllByLabelPropertyValue : public OpChecker<ScanAllByLabelPropertyValue> { +// public: +// ExpectScanAllByLabelPropertyValue(memgraph::storage::LabelId label, +// const std::pair<std::string, memgraph::storage::PropertyId> &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<ScanAllByLabelPropertyRange> { +// public: +// ExpectScanAllByLabelPropertyRange(memgraph::storage::LabelId label, memgraph::storage::PropertyId property, +// std::optional<ScanAllByLabelPropertyRange::Bound> lower_bound, +// std::optional<ScanAllByLabelPropertyRange::Bound> 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<ScanAllByLabelPropertyRange::Bound> lower_bound_; +// std::optional<ScanAllByLabelPropertyRange::Bound> upper_bound_; +// }; + +// class ExpectScanAllByLabelProperty : public OpChecker<ScanAllByLabelProperty> { +// public: +// ExpectScanAllByLabelProperty(memgraph::storage::LabelId label, +// const std::pair<std::string, memgraph::storage::PropertyId> &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<v2::plan::ScanAllByPrimaryKey> { + public: + ExpectScanAllByPrimaryKey(memgraph::storage::v3::LabelId label, const std::vector<Expression *> &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<Expression *> properties_; +}; + +class ExpectCartesian : public OpChecker<Cartesian> { + public: + ExpectCartesian(const std::list<std::unique_ptr<BaseOpChecker>> &left, + const std::list<std::unique_ptr<BaseOpChecker>> &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<std::unique_ptr<BaseOpChecker>> &left_; + const std::list<std::unique_ptr<BaseOpChecker>> &right_; +}; + +class ExpectCallProcedure : public OpChecker<CallProcedure> { + public: + ExpectCallProcedure(const std::string &name, const std::vector<memgraph::query::Expression *> &args, + const std::vector<std::string> &fields, const std::vector<Symbol> &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<memgraph::query::Expression *> args_; + std::vector<std::string> fields_; + std::vector<Symbol> result_syms_; +}; + +template <class T> +std::list<std::unique_ptr<BaseOpChecker>> MakeCheckers(T arg) { + std::list<std::unique_ptr<BaseOpChecker>> l; + l.emplace_back(std::make_unique<T>(arg)); + return l; +} + +template <class T, class... Rest> +std::list<std::unique_ptr<BaseOpChecker>> MakeCheckers(T arg, Rest &&...rest) { + auto l = MakeCheckers(std::forward<Rest>(rest)...); + l.emplace_front(std::make_unique<T>(arg)); + return std::move(l); +} + +template <class TPlanner, class TDbAccessor> +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<query::v2::Expression *> ExtractPrimaryKey(storage::v3::LabelId label, + std::vector<query::v2::plan::FilterInfo> property_filters) { + return std::vector<query::v2::Expression *>{}; + } + + private: + std::unordered_map<std::string, memgraph::storage::v3::LabelId> primary_labels_; + std::unordered_map<std::string, memgraph::storage::v3::LabelId> secondary_labels_; + std::unordered_map<std::string, memgraph::storage::v3::EdgeTypeId> edge_types_; + std::unordered_map<std::string, memgraph::storage::v3::PropertyId> primary_properties_; + std::unordered_map<std::string, memgraph::storage::v3::PropertyId> secondary_properties_; + + std::unordered_map<memgraph::storage::v3::LabelId, int64_t> label_index_; + std::vector<std::tuple<memgraph::storage::v3::LabelId, memgraph::storage::v3::PropertyId, int64_t>> + 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 <iostream> +#include <list> +#include <sstream> +#include <tuple> +#include <typeinfo> +#include <unordered_set> +#include <variant> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#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 <class TDbAccessor> + Planner(std::vector<SingleQueryPart> single_query_parts, PlanningContext<TDbAccessor> context) { + memgraph::expr::Parameters parameters; + PostProcessor post_processor(parameters); + plan_ = MakeLogicalPlanForSingleQuery<RuleBasedPlanner>(single_query_parts, &context); + plan_ = post_processor.Rewrite(std::move(plan_), &context); + } + + auto &plan() { return *plan_; } + + private: + std::unique_ptr<LogicalOperator> plan_; +}; + +template <class... TChecker> +auto CheckPlan(LogicalOperator &plan, const SymbolTable &symbol_table, TChecker... checker) { + std::list<BaseOpChecker *> checkers{&checker...}; + PlanChecker plan_checker(checkers, symbol_table); + plan.Accept(plan_checker); + EXPECT_TRUE(plan_checker.checkers_.empty()); +} + +template <class TPlanner, class... TChecker> +auto CheckPlan(memgraph::query::CypherQuery *query, AstStorage &storage, TChecker... checker) { + auto symbol_table = memgraph::query::MakeSymbolTable(query); + FakeDistributedDbAccessor dba; + auto planner = MakePlanner<TPlanner>(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, checker...); +} + +template <class T> +class TestPlanner : public ::testing::Test {}; + +using PlannerTypes = ::testing::Types<Planner>; + +void DeleteListContent(std::list<BaseOpChecker *> *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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<Symbol> optional_symbols{get_symbol(pattern), get_symbol(node_n), get_symbol(edge), get_symbol(node_m)}; + FakeDistributedDbAccessor dba; + auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + std::list<BaseOpChecker *> 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<TypeParam>(&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<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(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<TypeParam>(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<TypeParam>(&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<TypeParam>(&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<TypeParam>(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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(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<BaseOpChecker *> on_match{new ExpectExpand(), new ExpectSetProperty()}; + std::list<BaseOpChecker *> 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<TypeParam>(&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<BaseOpChecker *> optional{new ExpectScanAll(), new ExpectExpand(), new ExpectFilter()}; + CheckPlan<TypeParam>(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<TypeParam>(&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<TypeParam>(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<TypeParam>(&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<TypeParam>(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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(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<BaseOpChecker *> optional{new ExpectFilter(), new ExpectScanAll()}; + CheckPlan<TypeParam>(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<TypeParam>(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectExpand(), ExpectProduce()); + std::vector<std::string> output_names; + for (const auto &output_symbol : planner.plan().OutputSymbols(symbol_table)) { + output_names.emplace_back(output_symbol.name()); + } + std::vector<std::string> 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<TypeParam>(&dba, storage, symbol_table, query); + auto *produce = dynamic_cast<Produce *>(&planner.plan()); + ASSERT_TRUE(produce); + const auto &named_expressions = produce->named_expressions_; + ASSERT_EQ(named_expressions.size(), 2); + auto *expanded_ident = dynamic_cast<memgraph::query::Identifier *>(named_expressions[0]->expression_); + ASSERT_TRUE(expanded_ident); + auto aggr = ExpectAggregate({sum}, {expanded_ident}); + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), aggr, ExpectProduce()); + std::vector<std::string> output_names; + for (const auto &output_symbol : planner.plan().OutputSymbols(symbol_table)) { + output_names.emplace_back(output_symbol.name()); + } + std::vector<std::string> 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<BaseOpChecker *> on_match{new ExpectScanAll(), new ExpectFilter()}; + std::list<BaseOpChecker *> on_create{new ExpectCreateNode()}; + CheckPlan<TypeParam>(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<BaseOpChecker *> on_match{new ExpectScanAllByLabelPropertyValue(label, property, IDENT("i"))}; + std::list<BaseOpChecker *> on_create{new ExpectCreateNode()}; + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectUnwind(), ExpectMerge(on_match, on_create)); + 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<BaseOpChecker *> optional{new ExpectScanAll()}; + CheckPlan<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(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<memgraph::query::SubscriptOperator>(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<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(&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<memgraph::query::LabelsTest>( + IDENT("n"), std::vector<memgraph::query::LabelIx>{storage.GetLabelIx("label")}))), + RETURN("n"))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<std::pair<memgraph::query::Expression *, Bound::Type>> 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<std::pair<memgraph::query::Expression *, Bound::Type>> 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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(&dba, storage, symbol_table, query); + auto *root = dynamic_cast<Produce *>(&planner.plan()); + + ASSERT_TRUE(root); + + const auto &nes = root->named_expressions_; + EXPECT_TRUE(nes.size() == 4); + + std::vector<std::string> 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<TypeParam>(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<memgraph::query::EdgeAtom>(IDENT("r"), memgraph::query::EdgeAtom::Type::BREADTH_FIRST, + Direction::OUT, std::vector<memgraph::query::EdgeTypeIx>{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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(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<memgraph::query::Return>(); + 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<TypeParam>(&dba, storage, symbol_table, query); + auto *produce = dynamic_cast<Produce *>(&planner.plan()); + ASSERT_TRUE(produce); + std::vector<std::string> 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<memgraph::query::RegexMatch>(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<TypeParam>(&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<memgraph::query::RegexMatch>(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<TypeParam>(&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<memgraph::query::RegexMatch>(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<TypeParam>(&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<memgraph::query::RegexMatch>(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<TypeParam>(&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<memgraph::query::CallProcedure>(); + 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<Symbol> 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<TypeParam>(&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<memgraph::query::CallProcedure>(); + 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<Symbol> 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<TypeParam>(&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<memgraph::query::CallProcedure>(); + 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<Symbol> 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<TypeParam>(&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<TypeParam>(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<TypeParam>(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<memgraph::query::EdgeAtom>(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<TypeParam>(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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<BaseOpChecker *> updates{&create}; + std::list<BaseOpChecker *> input; + CheckPlan<TypeParam>(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<BaseOpChecker *> updates{&del}; + std::list<BaseOpChecker *> input; + CheckPlan<TypeParam>(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<BaseOpChecker *> updates{&set_prop}; + std::list<BaseOpChecker *> input; + CheckPlan<TypeParam>(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<BaseOpChecker *> input; + std::list<BaseOpChecker *> nested_updates{{&create, &del}}; + auto nested_foreach = ExpectForeach(input, nested_updates); + std::list<BaseOpChecker *> updates{&nested_foreach}; + CheckPlan<TypeParam>(query, storage, ExpectForeach(input, updates)); + } + { + auto *i = NEXPR("i", IDENT("i")); + auto *j = NEXPR("j", IDENT("j")); + auto create = ExpectCreateNode(); + std::list<BaseOpChecker *> empty; + std::list<BaseOpChecker *> updates{&create}; + auto input_op = ExpectForeach(empty, updates); + std::list<BaseOpChecker *> input{&input_op}; + auto *query = + QUERY(SINGLE_QUERY(FOREACH(i, {CREATE(PATTERN(NODE("n")))}), FOREACH(j, {CREATE(PATTERN(NODE("n")))}))); + CheckPlan<TypeParam>(query, storage, ExpectForeach(input, updates)); + } +} +*/ + +} // namespace From 7e8b4921b40c03e5d91e3756e167f7408dcf6088 Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Wed, 30 Nov 2022 13:16:04 +0100 Subject: [PATCH 05/85] Make query_v2_plan unit tests available again. The query_v2_plan unit tests were needed in order to properly test if the created logical plan of the new operator work properly. In order to achieve this v2 versions of the several files were created, where the old utilities were replaced with new ones, like query::v2 and storage::v3. A new fake db accessor was also created in order to be able to test the ScanAllByPrimaryKey operator. --- src/query/v2/plan/pretty_print.cpp | 21 + src/query/v2/plan/pretty_print.hpp | 2 + src/query/v2/plan/rewrite/index_lookup.hpp | 39 +- src/query/v2/plan/vertex_count_cache.hpp | 12 +- tests/unit/CMakeLists.txt | 4 +- tests/unit/query_common.hpp | 247 ------- tests/unit/query_plan_checker_v2.hpp | 86 ++- tests/unit/query_v2_common.hpp | 603 ++++++++++++++++++ .../{query_plan_v2.cpp => query_v2_plan.cpp} | 123 ++-- 9 files changed, 815 insertions(+), 322 deletions(-) create mode 100644 tests/unit/query_v2_common.hpp rename tests/unit/{query_plan_v2.cpp => query_v2_plan.cpp} (95%) diff --git a/src/query/v2/plan/pretty_print.cpp b/src/query/v2/plan/pretty_print.cpp index 76cc6ac26..4be8f4884 100644 --- a/src/query/v2/plan/pretty_print.cpp +++ b/src/query/v2/plan/pretty_print.cpp @@ -86,6 +86,14 @@ bool PlanPrinter::PreVisit(query::v2::plan::ScanAllByLabelProperty &op) { return true; } +bool PlanPrinter::PreVisit(query::v2::plan::ScanAllByPrimaryKey &op) { + WithPrintLn([&](auto &out) { + out << "* ScanAllByPrimaryKey" + << " (" << op.output_symbol_.name() << " :" << request_manager_->LabelToName(op.label_) << ")"; + }); + return true; +} + bool PlanPrinter::PreVisit(query::v2::plan::Expand &op) { WithPrintLn([&](auto &out) { *out_ << "* Expand (" << op.input_symbol_.name() << ")" @@ -480,6 +488,19 @@ bool PlanToJsonVisitor::PreVisit(ScanAllByLabelProperty &op) { return false; } +bool PlanToJsonVisitor::PreVisit(ScanAllByPrimaryKey &op) { + json self; + self["name"] = "ScanAllByPrimaryKey"; + self["label"] = ToJson(op.label_, *request_manager_); + self["output_symbol"] = ToJson(op.output_symbol_); + + op.input_->Accept(*this); + self["input"] = PopOutput(); + + output_ = std::move(self); + return false; +} + bool PlanToJsonVisitor::PreVisit(CreateNode &op) { json self; self["name"] = "CreateNode"; diff --git a/src/query/v2/plan/pretty_print.hpp b/src/query/v2/plan/pretty_print.hpp index 66fa31556..c1c84db40 100644 --- a/src/query/v2/plan/pretty_print.hpp +++ b/src/query/v2/plan/pretty_print.hpp @@ -68,6 +68,7 @@ class PlanPrinter : public virtual HierarchicalLogicalOperatorVisitor { bool PreVisit(ScanAllByLabelPropertyValue &) override; bool PreVisit(ScanAllByLabelPropertyRange &) override; bool PreVisit(ScanAllByLabelProperty &) override; + bool PreVisit(query::v2::plan::ScanAllByPrimaryKey &) override; bool PreVisit(Expand &) override; bool PreVisit(ExpandVariable &) override; @@ -195,6 +196,7 @@ class PlanToJsonVisitor : public virtual HierarchicalLogicalOperatorVisitor { bool PreVisit(ScanAllByLabelPropertyRange &) override; bool PreVisit(ScanAllByLabelPropertyValue &) override; bool PreVisit(ScanAllByLabelProperty &) override; + bool PreVisit(ScanAllByPrimaryKey &) override; bool PreVisit(Produce &) override; bool PreVisit(Accumulate &) override; diff --git a/src/query/v2/plan/rewrite/index_lookup.hpp b/src/query/v2/plan/rewrite/index_lookup.hpp index 41e24d2b6..1a4a4160c 100644 --- a/src/query/v2/plan/rewrite/index_lookup.hpp +++ b/src/query/v2/plan/rewrite/index_lookup.hpp @@ -273,6 +273,16 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor { return true; } + bool PreVisit(ScanAllByPrimaryKey &op) override { + prev_ops_.push_back(&op); + return true; + } + + bool PostVisit(ScanAllByPrimaryKey &) override { + prev_ops_.pop_back(); + return true; + } + bool PreVisit(ConstructNamedPath &op) override { prev_ops_.push_back(&op); return true; @@ -480,6 +490,12 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor { storage::v3::PropertyId GetProperty(PropertyIx prop) { return db_->NameToProperty(prop.name); } + void EraseLabelFilters(const memgraph::query::v2::Symbol &node_symbol, memgraph::query::v2::LabelIx prim_label) { + std::vector<query::v2::Expression *> removed_expressions; + filters_.EraseLabelFilter(node_symbol, prim_label, &removed_expressions); + filter_exprs_for_removal_.insert(removed_expressions.begin(), removed_expressions.end()); + } + std::optional<LabelIx> FindBestLabelIndex(const std::unordered_set<LabelIx> &labels) { MG_ASSERT(!labels.empty(), "Trying to find the best label without any labels."); std::optional<LabelIx> best_label; @@ -564,19 +580,28 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor { // First, try to see if we can find a vertex based on the possibly // supplied primary key. auto property_filters = filters_.PropertyFilters(node_symbol); - storage::v3::LabelId prim_label; - std::vector<query::v2::Expression *> primary_key; + query::v2::LabelIx prim_label; + std::vector<std::pair<query::v2::Expression *, query::v2::plan::FilterInfo>> primary_key; if (!property_filters.empty()) { for (const auto &label : labels) { if (db_->LabelIndexExists(GetLabel(label))) { - prim_label = GetLabel(label); - primary_key = db_->ExtractPrimaryKey(prim_label, property_filters); + prim_label = label; + primary_key = db_->ExtractPrimaryKey(GetLabel(prim_label), property_filters); break; } } if (!primary_key.empty()) { - return std::make_unique<ScanAllByPrimaryKey>(input, node_symbol, prim_label, primary_key); + // Mark the expressions so they won't be used for an additional, unnecessary filter. + for (const auto &pk : primary_key) { + filter_exprs_for_removal_.insert(pk.first); + filters_.EraseFilter(pk.second); + } + EraseLabelFilters(node_symbol, prim_label); + std::vector<query::v2::Expression *> pk_expressions; + std::transform(primary_key.begin(), primary_key.end(), std::back_inserter(pk_expressions), + [](const auto &exp) { return exp.first; }); + return std::make_unique<ScanAllByPrimaryKey>(input, node_symbol, GetLabel(prim_label), pk_expressions); } } @@ -593,9 +618,7 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor { filter_exprs_for_removal_.insert(found_index->filter.expression); } filters_.EraseFilter(found_index->filter); - std::vector<Expression *> removed_expressions; - filters_.EraseLabelFilter(node_symbol, found_index->label, &removed_expressions); - filter_exprs_for_removal_.insert(removed_expressions.begin(), removed_expressions.end()); + EraseLabelFilters(node_symbol, found_index->label); if (prop_filter.lower_bound_ || prop_filter.upper_bound_) { return std::make_unique<ScanAllByLabelPropertyRange>( input, node_symbol, GetLabel(found_index->label), GetProperty(prop_filter.property_), diff --git a/src/query/v2/plan/vertex_count_cache.hpp b/src/query/v2/plan/vertex_count_cache.hpp index 70b8d324b..120e434ea 100644 --- a/src/query/v2/plan/vertex_count_cache.hpp +++ b/src/query/v2/plan/vertex_count_cache.hpp @@ -58,9 +58,9 @@ class VertexCountCache { bool LabelPropertyIndexExists(storage::v3::LabelId /*label*/, storage::v3::PropertyId /*property*/) { return false; } - std::vector<query::v2::Expression *> ExtractPrimaryKey(storage::v3::LabelId label, - std::vector<query::v2::plan::FilterInfo> property_filters) { - std::vector<query::v2::Expression *> pk; + std::vector<std::pair<query::v2::Expression *, query::v2::plan::FilterInfo>> ExtractPrimaryKey( + storage::v3::LabelId label, std::vector<query::v2::plan::FilterInfo> property_filters) { + std::vector<std::pair<query::v2::Expression *, query::v2::plan::FilterInfo>> pk; const auto schema = shard_request_manager_->GetSchemaForLabel(label); std::vector<storage::v3::PropertyId> schema_properties; @@ -72,11 +72,13 @@ class VertexCountCache { for (const auto &property_filter : property_filters) { const auto &property_id = NameToProperty(property_filter.property_filter->property_.name); if (std::find(schema_properties.begin(), schema_properties.end(), property_id) != schema_properties.end()) { - pk.push_back(property_filter.expression); + pk.emplace_back(std::make_pair(property_filter.expression, property_filter)); } } - return pk.size() == schema_properties.size() ? pk : std::vector<query::v2::Expression *>{}; + return pk.size() == schema_properties.size() + ? pk + : std::vector<std::pair<query::v2::Expression *, query::v2::plan::FilterInfo>>{}; } msgs::ShardRequestManagerInterface *shard_request_manager_; diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index d9f9df875..123a448f5 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -93,8 +93,8 @@ 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_v2_plan.cpp) +target_link_libraries(${test_prefix}query_v2_plan 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 4697124ef..f7fe6e805 100644 --- a/tests/unit/query_common.hpp +++ b/tests/unit/query_common.hpp @@ -84,38 +84,14 @@ struct OrderBy { std::vector<SortItem> expressions; }; -// new stuff begin - -struct OrderByv2 { - std::vector<query::v2::SortItem> 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<Clause *> set; }; @@ -182,42 +158,6 @@ auto GetPropertyLookup(AstStorage &storage, TDbAccessor &, Expression *expr, return storage.Create<PropertyLookup>(expr, storage.GetPropertyIx(prop_pair.first)); } -// new stuff begin - -template <class TDbAccessor> -auto GetPropertyLookup(memgraph::query::v2::AstStorage &storage, TDbAccessor &dba, const std::string &name, - memgraph::storage::v3::PropertyId property) { - return storage.Create<query::v2::PropertyLookup>(storage.Create<query::v2::Identifier>(name), - storage.GetPropertyIx(dba.PropertyToName(property))); -} - -template <class TDbAccessor> -auto GetPropertyLookup(memgraph::query::v2::AstStorage &storage, TDbAccessor &dba, - memgraph::query::v2::Expression *expr, memgraph::storage::v3::PropertyId property) { - return storage.Create<query::v2::PropertyLookup>(expr, storage.GetPropertyIx(dba.PropertyToName(property))); -} - -template <class TDbAccessor> -auto GetPropertyLookup(memgraph::query::v2::AstStorage &storage, TDbAccessor &dba, - memgraph::query::v2::Expression *expr, const std::string &property) { - return storage.Create<query::v2::PropertyLookup>(expr, storage.GetPropertyIx(property)); -} - -template <class TDbAccessor> -auto GetPropertyLookup(memgraph::query::v2::AstStorage &storage, TDbAccessor &, const std::string &name, - const std::pair<std::string, memgraph::storage::v3::PropertyId> &prop_pair) { - return storage.Create<query::v2::PropertyLookup>(storage.Create<query::v2::Identifier>(name), - storage.GetPropertyIx(prop_pair.first)); -} - -template <class TDbAccessor> -auto GetPropertyLookup(memgraph::query::v2::AstStorage &storage, TDbAccessor &, memgraph::query::v2::Expression *expr, - const std::pair<std::string, memgraph::storage::v3::PropertyId> &prop_pair) { - return storage.Create<query::v2::PropertyLookup>(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. @@ -276,15 +216,6 @@ auto GetNode(AstStorage &storage, const std::string &name, std::optional<std::st return node; } -// new stuff begin -auto GetNode(memgraph::query::v2::AstStorage &storage, const std::string &name, - std::optional<std::string> label = std::nullopt) { - auto node = storage.Create<query::v2::NodeAtom>(storage.Create<query::v2::Identifier>(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<PatternAtom *> atoms) { auto pattern = storage.Create<Pattern>(); @@ -301,26 +232,6 @@ auto GetPattern(AstStorage &storage, const std::string &name, std::vector<Patter return pattern; } -// new stuff begin - -auto GetPattern(memgraph::query::v2::AstStorage &storage, std::vector<query::v2::PatternAtom *> atoms) { - auto pattern = storage.Create<query::v2::Pattern>(); - pattern->identifier_ = storage.Create<query::v2::Identifier>(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<query::v2::PatternAtom *> atoms) { - auto pattern = storage.Create<query::v2::Pattern>(); - pattern->identifier_ = storage.Create<query::v2::Identifier>(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. @@ -330,16 +241,6 @@ auto GetWithPatterns(TWithPatterns *with_patterns, std::vector<Pattern *> patter return with_patterns; } -// new stuff begin - -template <class TWithPatterns> -auto GetWithPatterns(TWithPatterns *with_patterns, std::vector<query::v2::Pattern *> 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) { @@ -375,45 +276,6 @@ 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 <class... T> -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 <class... T> -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 <class... T> -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; @@ -433,24 +295,6 @@ 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::v2::CypherQuery>(); - query->single_query_ = single_query; - return query; -} - -template <class... T> -auto GetQuery(query::v2::AstStorage &storage, query::v2::SingleQuery *single_query, T *...cypher_unions) { - auto *query = storage.Create<query::v2::CypherQuery>(); - query->single_query_ = single_query; - query->cypher_unions_ = std::vector<query::v2::CypherUnion *>{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); @@ -514,80 +358,6 @@ 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<memgraph::query::v2::Identifier>(name); - auto *named_expr = storage.Create<memgraph::query::v2::NamedExpression>(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<memgraph::query::v2::Identifier>(name); - body.named_expressions.emplace_back(named_expr); -} -template <class... T> -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 <class... T> -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 <class... T> -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<memgraph::query::v2::Identifier>(name); - body.named_expressions.emplace_back(named_expr); - FillReturnBody(storage, body, rest...); -} -template <class... T> -void FillReturnBody(query::v2::AstStorage &storage, query::v2::ReturnBody &body, const std::string &name, T... rest) { - auto *ident = storage.Create<memgraph::query::v2::Identifier>(name); - auto *named_expr = storage.Create<memgraph::query::v2::NamedExpression>(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: @@ -609,18 +379,6 @@ auto GetReturn(AstStorage &storage, bool distinct, T... exprs) { return ret; } -// new stuff begin - -template <class... T> -auto GetReturn(query::v2::AstStorage &storage, bool distinct, T... exprs) { - auto ret = storage.Create<query::v2::Return>(); - 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. @@ -739,10 +497,7 @@ auto GetForeach(AstStorage &storage, NamedExpression *named_expr, const std::vec memgraph::query::test_common::GetWithPatterns(storage.Create<memgraph::query::Match>(true), {__VA_ARGS__}) #define MATCH(...) \ memgraph::query::test_common::GetWithPatterns(storage.Create<memgraph::query::Match>(), {__VA_ARGS__}) -#define MATCH_V2(...) \ - memgraph::query::test_common::GetWithPatterns(storage.Create<memgraph::query::v2::Match>(), {__VA_ARGS__}) #define WHERE(expr) storage.Create<memgraph::query::Where>((expr)) -#define WHERE_V2(expr) storage.Create<memgraph::query::v2::Where>((expr)) #define CREATE(...) \ memgraph::query::test_common::GetWithPatterns(storage.Create<memgraph::query::Create>(), {__VA_ARGS__}) #define IDENT(...) storage.Create<memgraph::query::Identifier>(__VA_ARGS__) @@ -789,8 +544,6 @@ auto GetForeach(AstStorage &storage, NamedExpression *named_expr, const std::vec std::vector<memgraph::query::PropertyIx>{(property)}) #define QUERY(...) memgraph::query::test_common::GetQuery(storage, __VA_ARGS__) #define SINGLE_QUERY(...) memgraph::query::test_common::GetSingleQuery(storage.Create<SingleQuery>(), __VA_ARGS__) -#define SINGLE_QUERY_V2(...) \ - memgraph::query::test_common::GetSingleQuery(storage.Create<memgraph::query::v2::SingleQuery>(), __VA_ARGS__) #define UNION(...) memgraph::query::test_common::GetCypherUnion(storage.Create<CypherUnion>(true), __VA_ARGS__) #define UNION_ALL(...) memgraph::query::test_common::GetCypherUnion(storage.Create<CypherUnion>(false), __VA_ARGS__) #define FOREACH(...) memgraph::query::test_common::GetForeach(storage, __VA_ARGS__) diff --git a/tests/unit/query_plan_checker_v2.hpp b/tests/unit/query_plan_checker_v2.hpp index 6a6a12680..1ad1e712a 100644 --- a/tests/unit/query_plan_checker_v2.hpp +++ b/tests/unit/query_plan_checker_v2.hpp @@ -61,6 +61,7 @@ class PlanChecker : public virtual HierarchicalLogicalOperatorVisitor { PRE_VISIT(ScanAllByLabelPropertyValue); PRE_VISIT(ScanAllByLabelPropertyRange); PRE_VISIT(ScanAllByLabelProperty); + PRE_VISIT(ScanAllByPrimaryKey); PRE_VISIT(Expand); PRE_VISIT(ExpandVariable); PRE_VISIT(Filter); @@ -270,25 +271,25 @@ using ExpectDistinct = OpChecker<Distinct>; // const std::list<BaseOpChecker *> &optional_; // }; -// class ExpectScanAllByLabelPropertyValue : public OpChecker<ScanAllByLabelPropertyValue> { -// public: -// ExpectScanAllByLabelPropertyValue(memgraph::storage::LabelId label, -// const std::pair<std::string, memgraph::storage::PropertyId> &prop_pair, -// memgraph::query::Expression *expression) -// : label_(label), property_(prop_pair.second), expression_(expression) {} +class ExpectScanAllByLabelPropertyValue : public OpChecker<ScanAllByLabelPropertyValue> { + public: + ExpectScanAllByLabelPropertyValue(memgraph::storage::v3::LabelId label, + const std::pair<std::string, memgraph::storage::v3::PropertyId> &prop_pair, + memgraph::query::v2::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()); -// } + 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_; -// }; + private: + memgraph::storage::v3::LabelId label_; + memgraph::storage::v3::PropertyId property_; + memgraph::query::v2::Expression *expression_; +}; // class ExpectScanAllByLabelPropertyRange : public OpChecker<ScanAllByLabelPropertyRange> { // public: @@ -536,12 +537,53 @@ class FakeDistributedDbAccessor { } memgraph::storage::v3::PropertyId NameToProperty(const std::string &name) { - return storage::v3::PropertyId::FromUint(0); + auto find_in_prim_properties = primary_properties_.find(name); + if (find_in_prim_properties != primary_properties_.end()) { + return find_in_prim_properties->second; + } + auto find_in_secondary_properties = secondary_properties_.find(name); + if (find_in_secondary_properties != secondary_properties_.end()) { + return find_in_secondary_properties->second; + } + + MG_ASSERT(false, "The property does not exist as a primary or a secondary property."); + return memgraph::storage::v3::PropertyId::FromUint(0); } - std::vector<query::v2::Expression *> ExtractPrimaryKey(storage::v3::LabelId label, - std::vector<query::v2::plan::FilterInfo> property_filters) { - return std::vector<query::v2::Expression *>{}; + std::vector<std::pair<query::v2::Expression *, query::v2::plan::FilterInfo>> ExtractPrimaryKey( + storage::v3::LabelId label, std::vector<query::v2::plan::FilterInfo> property_filters) { + MG_ASSERT(schemas_.contains(label), + "You did not specify the Schema for this label! Use FakeDistributedDbAccessor::CreateSchema(...)."); + + std::vector<std::pair<query::v2::Expression *, query::v2::plan::FilterInfo>> pk; + const auto schema = GetSchemaForLabel(label); + + std::vector<storage::v3::PropertyId> schema_properties; + schema_properties.reserve(schema.size()); + + std::transform(schema.begin(), schema.end(), std::back_inserter(schema_properties), + [](const auto &schema_elem) { return schema_elem; }); + + for (const auto &property_filter : property_filters) { + const auto &property_id = NameToProperty(property_filter.property_filter->property_.name); + if (std::find(schema_properties.begin(), schema_properties.end(), property_id) != schema_properties.end()) { + pk.emplace_back(std::make_pair(property_filter.expression, property_filter)); + } + } + + return pk.size() == schema_properties.size() + ? pk + : std::vector<std::pair<query::v2::Expression *, query::v2::plan::FilterInfo>>{}; + } + + std::vector<memgraph::storage::v3::PropertyId> GetSchemaForLabel(storage::v3::LabelId label) { + return schemas_.at(label); + } + + void CreateSchema(const memgraph::storage::v3::LabelId primary_label, + const std::vector<memgraph::storage::v3::PropertyId> &schemas_types) { + MG_ASSERT(!schemas_.contains(primary_label), "You already created the schema for this label!"); + schemas_.emplace(primary_label, schemas_types); } private: @@ -554,6 +596,8 @@ class FakeDistributedDbAccessor { std::unordered_map<memgraph::storage::v3::LabelId, int64_t> label_index_; std::vector<std::tuple<memgraph::storage::v3::LabelId, memgraph::storage::v3::PropertyId, int64_t>> label_property_index_; + + std::unordered_map<memgraph::storage::v3::LabelId, std::vector<memgraph::storage::v3::PropertyId>> schemas_; }; } // namespace memgraph::query::v2::plan diff --git a/tests/unit/query_v2_common.hpp b/tests/unit/query_v2_common.hpp new file mode 100644 index 000000000..85905cb22 --- /dev/null +++ b/tests/unit/query_v2_common.hpp @@ -0,0 +1,603 @@ +// 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. + +/// @file +/// This file provides macros for easier construction of openCypher query AST. +/// The usage of macros is very similar to how one would write openCypher. For +/// example: +/// +/// AstStorage storage; // Macros rely on storage being in scope. +/// // PROPERTY_LOOKUP and PROPERTY_PAIR macros +/// // rely on a DbAccessor *reference* named dba. +/// database::GraphDb db; +/// auto dba_ptr = db.Access(); +/// auto &dba = *dba_ptr; +/// +/// QUERY(MATCH(PATTERN(NODE("n"), EDGE("e"), NODE("m"))), +/// WHERE(LESS(PROPERTY_LOOKUP("e", edge_prop), LITERAL(3))), +/// RETURN(SUM(PROPERTY_LOOKUP("m", prop)), AS("sum"), +/// ORDER_BY(IDENT("sum")), +/// SKIP(ADD(LITERAL(1), LITERAL(2))))); +/// +/// Each of the macros is accompanied by a function. The functions use overload +/// resolution and template magic to provide a type safe way of constructing +/// queries. Although the functions can be used by themselves, it is more +/// convenient to use the macros. + +#pragma once + +#include <map> +#include <sstream> +#include <string> +#include <unordered_map> +#include <utility> +#include <vector> + +#include "query/frontend/ast/pretty_print.hpp" // not sure if that is ok... +#include "query/v2/frontend/ast/ast.hpp" +#include "storage/v3/id_types.hpp" +#include "utils/string.hpp" + +#include "query/v2/frontend/ast/ast.hpp" + +namespace memgraph::query::v2 { + +namespace test_common { + +auto ToIntList(const TypedValue &t) { + std::vector<int64_t> list; + for (auto x : t.ValueList()) { + list.push_back(x.ValueInt()); + } + return list; +}; + +auto ToIntMap(const TypedValue &t) { + std::map<std::string, int64_t> map; + for (const auto &kv : t.ValueMap()) map.emplace(kv.first, kv.second.ValueInt()); + return map; +}; + +std::string ToString(Expression *expr) { + std::ostringstream ss; + // PrintExpression(expr, &ss); + return ss.str(); +} + +std::string ToString(NamedExpression *expr) { + std::ostringstream ss; + // PrintExpression(expr, &ss); + return ss.str(); +} + +// Custom types for ORDER BY, SKIP, LIMIT, ON MATCH and ON CREATE expressions, +// so that they can be used to resolve function calls. +struct OrderBy { + std::vector<SortItem> expressions; +}; + +struct Skip { + Expression *expression = nullptr; +}; + +struct Limit { + Expression *expression = nullptr; +}; + +struct OnMatch { + std::vector<Clause *> set; +}; +struct OnCreate { + std::vector<Clause *> set; +}; + +// Helper functions for filling the OrderBy with expressions. +auto FillOrderBy(OrderBy &order_by, Expression *expression, Ordering ordering = Ordering::ASC) { + order_by.expressions.push_back({ordering, expression}); +} +template <class... T> +auto FillOrderBy(OrderBy &order_by, Expression *expression, Ordering ordering, T... rest) { + FillOrderBy(order_by, expression, ordering); + FillOrderBy(order_by, rest...); +} +template <class... T> +auto FillOrderBy(OrderBy &order_by, Expression *expression, T... rest) { + FillOrderBy(order_by, expression); + FillOrderBy(order_by, rest...); +} + +/// Create OrderBy expressions. +/// +/// The supported combination of arguments is: (Expression, [Ordering])+ +/// Since the Ordering is optional, by default it is ascending. +template <class... T> +auto GetOrderBy(T... exprs) { + OrderBy order_by; + FillOrderBy(order_by, exprs...); + return order_by; +} + +/// Create PropertyLookup with given name and property. +/// +/// Name is used to create the Identifier which is used for property lookup. +template <class TDbAccessor> +auto GetPropertyLookup(AstStorage &storage, TDbAccessor &dba, const std::string &name, + memgraph::storage::v3::PropertyId property) { + return storage.Create<PropertyLookup>(storage.Create<Identifier>(name), + storage.GetPropertyIx(dba.PropertyToName(property))); +} + +template <class TDbAccessor> +auto GetPropertyLookup(AstStorage &storage, TDbAccessor &dba, Expression *expr, + memgraph::storage::v3::PropertyId property) { + return storage.Create<PropertyLookup>(expr, storage.GetPropertyIx(dba.PropertyToName(property))); +} + +template <class TDbAccessor> +auto GetPropertyLookup(AstStorage &storage, TDbAccessor &dba, Expression *expr, const std::string &property) { + return storage.Create<PropertyLookup>(expr, storage.GetPropertyIx(property)); +} + +template <class TDbAccessor> +auto GetPropertyLookup(AstStorage &storage, TDbAccessor &, const std::string &name, + const std::pair<std::string, memgraph::storage::v3::PropertyId> &prop_pair) { + return storage.Create<PropertyLookup>(storage.Create<Identifier>(name), storage.GetPropertyIx(prop_pair.first)); +} + +template <class TDbAccessor> +auto GetPropertyLookup(AstStorage &storage, TDbAccessor &, Expression *expr, + const std::pair<std::string, memgraph::storage::v3::PropertyId> &prop_pair) { + return storage.Create<PropertyLookup>(expr, storage.GetPropertyIx(prop_pair.first)); +} + +/// Create an EdgeAtom with given name, direction and edge_type. +/// +/// Name is used to create the Identifier which is assigned to the edge. +auto GetEdge(AstStorage &storage, const std::string &name, EdgeAtom::Direction dir = EdgeAtom::Direction::BOTH, + const std::vector<std::string> &edge_types = {}) { + std::vector<EdgeTypeIx> types; + types.reserve(edge_types.size()); + for (const auto &type : edge_types) { + types.push_back(storage.GetEdgeTypeIx(type)); + } + return storage.Create<EdgeAtom>(storage.Create<Identifier>(name), EdgeAtom::Type::SINGLE, dir, types); +} + +/// Create a variable length expansion EdgeAtom with given name, direction and +/// edge_type. +/// +/// Name is used to create the Identifier which is assigned to the edge. +auto GetEdgeVariable(AstStorage &storage, const std::string &name, EdgeAtom::Type type = EdgeAtom::Type::DEPTH_FIRST, + EdgeAtom::Direction dir = EdgeAtom::Direction::BOTH, + const std::vector<std::string> &edge_types = {}, Identifier *flambda_inner_edge = nullptr, + Identifier *flambda_inner_node = nullptr, Identifier *wlambda_inner_edge = nullptr, + Identifier *wlambda_inner_node = nullptr, Expression *wlambda_expression = nullptr, + Identifier *total_weight = nullptr) { + std::vector<EdgeTypeIx> types; + types.reserve(edge_types.size()); + for (const auto &type : edge_types) { + types.push_back(storage.GetEdgeTypeIx(type)); + } + auto r_val = storage.Create<EdgeAtom>(storage.Create<Identifier>(name), type, dir, types); + + r_val->filter_lambda_.inner_edge = + flambda_inner_edge ? flambda_inner_edge : storage.Create<Identifier>(memgraph::utils::RandomString(20)); + r_val->filter_lambda_.inner_node = + flambda_inner_node ? flambda_inner_node : storage.Create<Identifier>(memgraph::utils::RandomString(20)); + + if (type == EdgeAtom::Type::WEIGHTED_SHORTEST_PATH) { + r_val->weight_lambda_.inner_edge = + wlambda_inner_edge ? wlambda_inner_edge : storage.Create<Identifier>(memgraph::utils::RandomString(20)); + r_val->weight_lambda_.inner_node = + wlambda_inner_node ? wlambda_inner_node : storage.Create<Identifier>(memgraph::utils::RandomString(20)); + r_val->weight_lambda_.expression = + wlambda_expression ? wlambda_expression : storage.Create<memgraph::query::v2::PrimitiveLiteral>(1); + + r_val->total_weight_ = total_weight; + } + + return r_val; +} + +/// Create a NodeAtom with given name and label. +/// +/// Name is used to create the Identifier which is assigned to the node. +auto GetNode(AstStorage &storage, const std::string &name, std::optional<std::string> label = std::nullopt) { + auto node = storage.Create<NodeAtom>(storage.Create<Identifier>(name)); + if (label) node->labels_.emplace_back(storage.GetLabelIx(*label)); + return node; +} + +/// Create a Pattern with given atoms. +auto GetPattern(AstStorage &storage, std::vector<PatternAtom *> atoms) { + auto pattern = storage.Create<Pattern>(); + pattern->identifier_ = storage.Create<Identifier>(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(AstStorage &storage, const std::string &name, std::vector<PatternAtom *> atoms) { + auto pattern = storage.Create<Pattern>(); + pattern->identifier_ = storage.Create<Identifier>(name, true); + pattern->atoms_.insert(pattern->atoms_.begin(), atoms.begin(), atoms.end()); + return pattern; +} + +/// This function fills an AST node which with given patterns. +/// +/// The function is most commonly used to create Match and Create clauses. +template <class TWithPatterns> +auto GetWithPatterns(TWithPatterns *with_patterns, std::vector<Pattern *> patterns) { + with_patterns->patterns_.insert(with_patterns->patterns_.begin(), patterns.begin(), patterns.end()); + return with_patterns; +} + +/// Create a query with given clauses. + +auto GetSingleQuery(SingleQuery *single_query, Clause *clause) { + single_query->clauses_.emplace_back(clause); + return single_query; +} +auto GetSingleQuery(SingleQuery *single_query, Match *match, Where *where) { + match->where_ = where; + single_query->clauses_.emplace_back(match); + return single_query; +} +auto GetSingleQuery(SingleQuery *single_query, With *with, Where *where) { + with->where_ = where; + single_query->clauses_.emplace_back(with); + return single_query; +} +template <class... T> +auto GetSingleQuery(SingleQuery *single_query, Match *match, Where *where, T *...clauses) { + match->where_ = where; + single_query->clauses_.emplace_back(match); + return GetSingleQuery(single_query, clauses...); +} +template <class... T> +auto GetSingleQuery(SingleQuery *single_query, With *with, Where *where, T *...clauses) { + with->where_ = where; + single_query->clauses_.emplace_back(with); + return GetSingleQuery(single_query, clauses...); +} + +template <class... T> +auto GetSingleQuery(SingleQuery *single_query, Clause *clause, T *...clauses) { + single_query->clauses_.emplace_back(clause); + return GetSingleQuery(single_query, clauses...); +} + +auto GetCypherUnion(CypherUnion *cypher_union, SingleQuery *single_query) { + cypher_union->single_query_ = single_query; + return cypher_union; +} + +auto GetQuery(AstStorage &storage, SingleQuery *single_query) { + auto *query = storage.Create<CypherQuery>(); + query->single_query_ = single_query; + return query; +} + +template <class... T> +auto GetQuery(AstStorage &storage, SingleQuery *single_query, T *...cypher_unions) { + auto *query = storage.Create<CypherQuery>(); + query->single_query_ = single_query; + query->cypher_unions_ = std::vector<CypherUnion *>{cypher_unions...}; + return query; +} + +// Helper functions for constructing RETURN and WITH clauses. +void FillReturnBody(AstStorage &, ReturnBody &body, NamedExpression *named_expr) { + body.named_expressions.emplace_back(named_expr); +} +void FillReturnBody(AstStorage &storage, ReturnBody &body, const std::string &name) { + if (name == "*") { + body.all_identifiers = true; + } else { + auto *ident = storage.Create<memgraph::query::v2::Identifier>(name); + auto *named_expr = storage.Create<memgraph::query::v2::NamedExpression>(name, ident); + body.named_expressions.emplace_back(named_expr); + } +} +void FillReturnBody(AstStorage &, ReturnBody &body, Limit limit) { body.limit = limit.expression; } +void FillReturnBody(AstStorage &, ReturnBody &body, Skip skip, Limit limit = Limit{}) { + body.skip = skip.expression; + body.limit = limit.expression; +} +void FillReturnBody(AstStorage &, ReturnBody &body, OrderBy order_by, Limit limit = Limit{}) { + body.order_by = order_by.expressions; + body.limit = limit.expression; +} +void FillReturnBody(AstStorage &, ReturnBody &body, OrderBy order_by, Skip skip, Limit limit = Limit{}) { + body.order_by = order_by.expressions; + body.skip = skip.expression; + body.limit = limit.expression; +} +void FillReturnBody(AstStorage &, ReturnBody &body, Expression *expr, 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(AstStorage &storage, ReturnBody &body, const std::string &name, NamedExpression *named_expr) { + named_expr->expression_ = storage.Create<memgraph::query::v2::Identifier>(name); + body.named_expressions.emplace_back(named_expr); +} +template <class... T> +void FillReturnBody(AstStorage &storage, ReturnBody &body, Expression *expr, NamedExpression *named_expr, T... rest) { + named_expr->expression_ = expr; + body.named_expressions.emplace_back(named_expr); + FillReturnBody(storage, body, rest...); +} +template <class... T> +void FillReturnBody(AstStorage &storage, ReturnBody &body, NamedExpression *named_expr, T... rest) { + body.named_expressions.emplace_back(named_expr); + FillReturnBody(storage, body, rest...); +} +template <class... T> +void FillReturnBody(AstStorage &storage, ReturnBody &body, const std::string &name, NamedExpression *named_expr, + T... rest) { + named_expr->expression_ = storage.Create<memgraph::query::v2::Identifier>(name); + body.named_expressions.emplace_back(named_expr); + FillReturnBody(storage, body, rest...); +} +template <class... T> +void FillReturnBody(AstStorage &storage, ReturnBody &body, const std::string &name, T... rest) { + auto *ident = storage.Create<memgraph::query::v2::Identifier>(name); + auto *named_expr = storage.Create<memgraph::query::v2::NamedExpression>(name, ident); + body.named_expressions.emplace_back(named_expr); + FillReturnBody(storage, body, rest...); +} + +/// Create the return clause with given expressions. +/// +/// The supported expression combination of arguments is: +/// +/// (String | NamedExpression | (Expression NamedExpression))+ +/// [OrderBy] [Skip] [Limit] +/// +/// When the pair (Expression NamedExpression) is given, the Expression will be +/// moved inside the NamedExpression. This is done, so that the constructs like +/// RETURN(expr, AS("name"), ...) are supported. Taking a String is a shorthand +/// for RETURN(IDENT(string), AS(string), ....). +/// +/// @sa GetWith +template <class... T> +auto GetReturn(AstStorage &storage, bool distinct, T... exprs) { + auto ret = storage.Create<Return>(); + ret->body_.distinct = distinct; + FillReturnBody(storage, ret->body_, exprs...); + return ret; +} + +/// Create the with clause with given expressions. +/// +/// The supported expression combination is the same as for @c GetReturn. +/// +/// @sa GetReturn +template <class... T> +auto GetWith(AstStorage &storage, bool distinct, T... exprs) { + auto with = storage.Create<With>(); + with->body_.distinct = distinct; + FillReturnBody(storage, with->body_, exprs...); + return with; +} + +/// Create the UNWIND clause with given named expression. +auto GetUnwind(AstStorage &storage, NamedExpression *named_expr) { + return storage.Create<memgraph::query::v2::Unwind>(named_expr); +} +auto GetUnwind(AstStorage &storage, Expression *expr, NamedExpression *as) { + as->expression_ = expr; + return GetUnwind(storage, as); +} + +/// Create the delete clause with given named expressions. +auto GetDelete(AstStorage &storage, std::vector<Expression *> exprs, bool detach = false) { + auto del = storage.Create<Delete>(); + del->expressions_.insert(del->expressions_.begin(), exprs.begin(), exprs.end()); + del->detach_ = detach; + return del; +} + +/// Create a set property clause for given property lookup and the right hand +/// side expression. +auto GetSet(AstStorage &storage, PropertyLookup *prop_lookup, Expression *expr) { + return storage.Create<SetProperty>(prop_lookup, expr); +} + +/// Create a set properties clause for given identifier name and the right hand +/// side expression. +auto GetSet(AstStorage &storage, const std::string &name, Expression *expr, bool update = false) { + return storage.Create<SetProperties>(storage.Create<Identifier>(name), expr, update); +} + +/// Create a set labels clause for given identifier name and labels. +auto GetSet(AstStorage &storage, const std::string &name, std::vector<std::string> label_names) { + std::vector<LabelIx> labels; + labels.reserve(label_names.size()); + for (const auto &label : label_names) { + labels.push_back(storage.GetLabelIx(label)); + } + return storage.Create<SetLabels>(storage.Create<Identifier>(name), labels); +} + +/// Create a remove property clause for given property lookup +auto GetRemove(AstStorage &storage, PropertyLookup *prop_lookup) { return storage.Create<RemoveProperty>(prop_lookup); } + +/// Create a remove labels clause for given identifier name and labels. +auto GetRemove(AstStorage &storage, const std::string &name, std::vector<std::string> label_names) { + std::vector<LabelIx> labels; + labels.reserve(label_names.size()); + for (const auto &label : label_names) { + labels.push_back(storage.GetLabelIx(label)); + } + return storage.Create<RemoveLabels>(storage.Create<Identifier>(name), labels); +} + +/// Create a Merge clause for given Pattern with optional OnMatch and OnCreate +/// parts. +auto GetMerge(AstStorage &storage, Pattern *pattern, OnCreate on_create = OnCreate{}) { + auto *merge = storage.Create<memgraph::query::v2::Merge>(); + merge->pattern_ = pattern; + merge->on_create_ = on_create.set; + return merge; +} +auto GetMerge(AstStorage &storage, Pattern *pattern, OnMatch on_match, OnCreate on_create = OnCreate{}) { + auto *merge = storage.Create<memgraph::query::v2::Merge>(); + merge->pattern_ = pattern; + merge->on_match_ = on_match.set; + merge->on_create_ = on_create.set; + return merge; +} + +auto GetCallProcedure(AstStorage &storage, std::string procedure_name, + std::vector<memgraph::query::v2::Expression *> arguments = {}) { + auto *call_procedure = storage.Create<memgraph::query::v2::CallProcedure>(); + call_procedure->procedure_name_ = std::move(procedure_name); + call_procedure->arguments_ = std::move(arguments); + return call_procedure; +} + +/// Create the FOREACH clause with given named expression. +auto GetForeach(AstStorage &storage, NamedExpression *named_expr, const std::vector<query::v2::Clause *> &clauses) { + return storage.Create<query::v2::Foreach>(named_expr, clauses); +} + +} // namespace test_common + +} // namespace memgraph::query::v2 + +/// All the following macros implicitly pass `storage` variable to functions. +/// You need to have `AstStorage storage;` somewhere in scope to use them. +/// Refer to function documentation to see what the macro does. +/// +/// Example usage: +/// +/// // Create MATCH (n) -[r]- (m) RETURN m AS new_name +/// AstStorage storage; +/// auto query = QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m"))), +/// RETURN(NEXPR("new_name"), IDENT("m"))); +#define NODE(...) memgraph::expr::test_common::GetNode(storage, __VA_ARGS__) +#define EDGE(...) memgraph::expr::test_common::GetEdge(storage, __VA_ARGS__) +#define EDGE_VARIABLE(...) memgraph::expr::test_common::GetEdgeVariable(storage, __VA_ARGS__) +#define PATTERN(...) memgraph::expr::test_common::GetPattern(storage, {__VA_ARGS__}) +#define PATTERN(...) memgraph::expr::test_common::GetPattern(storage, {__VA_ARGS__}) +#define NAMED_PATTERN(name, ...) memgraph::expr::test_common::GetPattern(storage, name, {__VA_ARGS__}) +#define OPTIONAL_MATCH(...) \ + memgraph::expr::test_common::GetWithPatterns(storage.Create<memgraph::query::v2::Match>(true), {__VA_ARGS__}) +#define MATCH(...) \ + memgraph::expr::test_common::GetWithPatterns(storage.Create<memgraph::query::v2::Match>(), {__VA_ARGS__}) +#define WHERE(expr) storage.Create<memgraph::query::v2::Where>((expr)) +#define CREATE(...) \ + memgraph::expr::test_common::GetWithPatterns(storage.Create<memgraph::query::v2::Create>(), {__VA_ARGS__}) +#define IDENT(...) storage.Create<memgraph::query::v2::Identifier>(__VA_ARGS__) +#define LITERAL(val) storage.Create<memgraph::query::v2::PrimitiveLiteral>((val)) +#define LIST(...) \ + storage.Create<memgraph::query::v2::ListLiteral>(std::vector<memgraph::query::v2::Expression *>{__VA_ARGS__}) +#define MAP(...) \ + storage.Create<memgraph::query::v2::MapLiteral>( \ + std::unordered_map<memgraph::query::v2::PropertyIx, memgraph::query::v2::Expression *>{__VA_ARGS__}) +#define PROPERTY_PAIR(property_name) \ + std::make_pair(property_name, dba.NameToProperty(property_name)) // This one might not be needed at all +#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::expr::test_common::GetPropertyLookup(storage, dba, __VA_ARGS__) +#define PARAMETER_LOOKUP(token_position) storage.Create<memgraph::query::v2::ParameterLookup>((token_position)) +#define NEXPR(name, expr) storage.Create<memgraph::query::v2::NamedExpression>((name), (expr)) +// AS is alternative to NEXPR which does not initialize NamedExpression with +// Expression. It should be used with RETURN or WITH. For example: +// RETURN(IDENT("n"), AS("n")) vs. RETURN(NEXPR("n", IDENT("n"))). +#define AS(name) storage.Create<memgraph::query::v2::NamedExpression>((name)) +#define RETURN(...) memgraph::expr::test_common::GetReturn(storage, false, __VA_ARGS__) +#define WITH(...) memgraph::expr::test_common::GetWith(storage, false, __VA_ARGS__) +#define RETURN_DISTINCT(...) memgraph::expr::test_common::GetReturn(storage, true, __VA_ARGS__) +#define WITH_DISTINCT(...) memgraph::expr::test_common::GetWith(storage, true, __VA_ARGS__) +#define UNWIND(...) memgraph::expr::test_common::GetUnwind(storage, __VA_ARGS__) +#define ORDER_BY(...) memgraph::expr::test_common::GetOrderBy(__VA_ARGS__) +#define SKIP(expr) \ + memgraph::expr::test_common::Skip { (expr) } +#define LIMIT(expr) \ + memgraph::expr::test_common::Limit { (expr) } +#define DELETE(...) memgraph::expr::test_common::GetDelete(storage, {__VA_ARGS__}) +#define DETACH_DELETE(...) memgraph::expr::test_common::GetDelete(storage, {__VA_ARGS__}, true) +#define SET(...) memgraph::expr::test_common::GetSet(storage, __VA_ARGS__) +#define REMOVE(...) memgraph::expr::test_common::GetRemove(storage, __VA_ARGS__) +#define MERGE(...) memgraph::expr::test_common::GetMerge(storage, __VA_ARGS__) +#define ON_MATCH(...) \ + memgraph::expr::test_common::OnMatch { \ + std::vector<memgraph::query::v2::Clause *> { __VA_ARGS__ } \ + } +#define ON_CREATE(...) \ + memgraph::expr::test_common::OnCreate { \ + std::vector<memgraph::query::v2::Clause *> { __VA_ARGS__ } \ + } +#define CREATE_INDEX_ON(label, property) \ + storage.Create<memgraph::query::v2::IndexQuery>(memgraph::query::v2::IndexQuery::Action::CREATE, (label), \ + std::vector<memgraph::query::v2::PropertyIx>{(property)}) +#define QUERY(...) memgraph::expr::test_common::GetQuery(storage, __VA_ARGS__) +#define SINGLE_QUERY(...) memgraph::expr::test_common::GetSingleQuery(storage.Create<SingleQuery>(), __VA_ARGS__) +#define UNION(...) memgraph::expr::test_common::GetCypherUnion(storage.Create<CypherUnion>(true), __VA_ARGS__) +#define UNION_ALL(...) memgraph::expr::test_common::GetCypherUnion(storage.Create<CypherUnion>(false), __VA_ARGS__) +#define FOREACH(...) memgraph::expr::test_common::GetForeach(storage, __VA_ARGS__) +// Various operators +#define NOT(expr) storage.Create<memgraph::query::v2::NotOperator>((expr)) +#define UPLUS(expr) storage.Create<memgraph::query::v2::UnaryPlusOperator>((expr)) +#define UMINUS(expr) storage.Create<memgraph::query::v2::UnaryMinusOperator>((expr)) +#define IS_NULL(expr) storage.Create<memgraph::query::v2::IsNullOperator>((expr)) +#define ADD(expr1, expr2) storage.Create<memgraph::query::v2::AdditionOperator>((expr1), (expr2)) +#define LESS(expr1, expr2) storage.Create<memgraph::query::v2::LessOperator>((expr1), (expr2)) +#define LESS_EQ(expr1, expr2) storage.Create<memgraph::query::v2::LessEqualOperator>((expr1), (expr2)) +#define GREATER(expr1, expr2) storage.Create<memgraph::query::v2::GreaterOperator>((expr1), (expr2)) +#define GREATER_EQ(expr1, expr2) storage.Create<memgraph::query::v2::GreaterEqualOperator>((expr1), (expr2)) +#define SUM(expr) \ + storage.Create<memgraph::query::v2::Aggregation>((expr), nullptr, memgraph::query::v2::Aggregation::Op::SUM) +#define COUNT(expr) \ + storage.Create<memgraph::query::v2::Aggregation>((expr), nullptr, memgraph::query::v2::Aggregation::Op::COUNT) +#define AVG(expr) \ + storage.Create<memgraph::query::v2::Aggregation>((expr), nullptr, memgraph::query::v2::Aggregation::Op::AVG) +#define COLLECT_LIST(expr) \ + storage.Create<memgraph::query::v2::Aggregation>((expr), nullptr, memgraph::query::v2::Aggregation::Op::COLLECT_LIST) +#define EQ(expr1, expr2) storage.Create<memgraph::query::v2::EqualOperator>((expr1), (expr2)) +#define NEQ(expr1, expr2) storage.Create<memgraph::query::v2::NotEqualOperator>((expr1), (expr2)) +#define AND(expr1, expr2) storage.Create<memgraph::query::v2::AndOperator>((expr1), (expr2)) +#define OR(expr1, expr2) storage.Create<memgraph::query::v2::OrOperator>((expr1), (expr2)) +#define IN_LIST(expr1, expr2) storage.Create<memgraph::query::v2::InListOperator>((expr1), (expr2)) +#define IF(cond, then, else) storage.Create<memgraph::query::v2::IfOperator>((cond), (then), (else)) +// Function call +#define FN(function_name, ...) \ + storage.Create<memgraph::query::v2::Function>(memgraph::utils::ToUpperCase(function_name), \ + std::vector<memgraph::query::v2::Expression *>{__VA_ARGS__}) +// List slicing +#define SLICE(list, lower_bound, upper_bound) \ + storage.Create<memgraph::query::v2::ListSlicingOperator>(list, lower_bound, upper_bound) +// all(variable IN list WHERE predicate) +#define ALL(variable, list, where) \ + storage.Create<memgraph::query::v2::All>(storage.Create<memgraph::query::v2::Identifier>(variable), list, where) +#define SINGLE(variable, list, where) \ + storage.Create<memgraph::query::v2::Single>(storage.Create<memgraph::query::v2::Identifier>(variable), list, where) +#define ANY(variable, list, where) \ + storage.Create<memgraph::query::v2::Any>(storage.Create<memgraph::query::v2::Identifier>(variable), list, where) +#define NONE(variable, list, where) \ + storage.Create<memgraph::query::v2::None>(storage.Create<memgraph::query::v2::Identifier>(variable), list, where) +#define REDUCE(accumulator, initializer, variable, list, expr) \ + storage.Create<memgraph::query::v2::Reduce>(storage.Create<memgraph::query::v2::Identifier>(accumulator), \ + initializer, storage.Create<memgraph::query::v2::Identifier>(variable), \ + list, expr) +#define COALESCE(...) \ + storage.Create<memgraph::query::v2::Coalesce>(std::vector<memgraph::query::v2::Expression *>{__VA_ARGS__}) +#define EXTRACT(variable, list, expr) \ + storage.Create<memgraph::query::v2::Extract>(storage.Create<memgraph::query::v2::Identifier>(variable), list, expr) +#define AUTH_QUERY(action, user, role, user_or_role, password, privileges) \ + storage.Create<memgraph::query::v2::AuthQuery>((action), (user), (role), (user_or_role), password, (privileges)) +#define DROP_USER(usernames) storage.Create<memgraph::query::v2::DropUser>((usernames)) +#define CALL_PROCEDURE(...) memgraph::query::v2::test_common::GetCallProcedure(storage, __VA_ARGS__) diff --git a/tests/unit/query_plan_v2.cpp b/tests/unit/query_v2_plan.cpp similarity index 95% rename from tests/unit/query_plan_v2.cpp rename to tests/unit/query_v2_plan.cpp index 009d0ca56..2eb8295db 100644 --- a/tests/unit/query_plan_v2.cpp +++ b/tests/unit/query_v2_plan.cpp @@ -22,14 +22,13 @@ #include <gmock/gmock.h> #include <gtest/gtest.h> -#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/frontend/ast/ast.hpp" #include "query/v2/plan/operator.hpp" #include "query/v2/plan/planner.hpp" -#include "query_common.hpp" +#include "query_v2_common.hpp" namespace memgraph::query { ::std::ostream &operator<<(::std::ostream &os, const Symbol &sym) { @@ -39,10 +38,10 @@ 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::AstStorage; +using memgraph::query::v2::SingleQuery; using memgraph::query::v2::SymbolTable; using Type = memgraph::query::v2::EdgeAtom::Type; using Direction = memgraph::query::v2::EdgeAtom::Direction; @@ -75,8 +74,8 @@ auto CheckPlan(LogicalOperator &plan, const SymbolTable &symbol_table, TChecker. } template <class TPlanner, class... TChecker> -auto CheckPlan(memgraph::query::CypherQuery *query, AstStorage &storage, TChecker... checker) { - auto symbol_table = memgraph::query::MakeSymbolTable(query); +auto CheckPlan(memgraph::query::v2::CypherQuery *query, AstStorage &storage, TChecker... checker) { + auto symbol_table = memgraph::expr::MakeSymbolTable(query); FakeDistributedDbAccessor dba; auto planner = MakePlanner<TPlanner>(&dba, storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, checker...); @@ -95,45 +94,91 @@ void DeleteListContent(std::list<BaseOpChecker *> *list) { 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; + const char *prim_label_name = "prim_label_one"; + // Exact primary key match, one elem as PK. { + FakeDistributedDbAccessor dba; + auto label = dba.Label(prim_label_name); + auto prim_prop_one = PRIMARY_PROPERTY_PAIR("prim_prop_one"); + + dba.SetIndexCount(label, 1); + dba.SetIndexCount(label, prim_prop_one.second, 1); + + dba.CreateSchema(label, {prim_prop_one.second}); + + 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 *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", prim_label_name))), + WHERE(EQ(PROPERTY_LOOKUP("n", prim_prop_one), LITERAL(1))), RETURN("n"))); auto symbol_table = (memgraph::expr::MakeSymbolTable(query)); auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAllByPrimaryKey(label, {expected_primary_key}), ExpectProduce()); + } + // Exact primary key match, two elem as PK. + { + FakeDistributedDbAccessor dba; + auto label = dba.Label(prim_label_name); + auto prim_prop_one = PRIMARY_PROPERTY_PAIR("prim_prop_one"); - // // 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<TypeParam>(&dba, storage, symbol_table, query); - // // We expect ScanAllByLabelProperty to come instead of ScanAll > Filter. - // CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelProperty(label, prop), ExpectExpand(), - // ExpectProduce()); + 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.CreateSchema(label, {prim_prop_one.second, prim_prop_two.second}); + + 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; + expected_primary_key = PROPERTY_LOOKUP("n", prim_prop_one); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", prim_label_name))), + WHERE(AND(EQ(PROPERTY_LOOKUP("n", prim_prop_one), LITERAL(1)), + EQ(PROPERTY_LOOKUP("n", prim_prop_two), LITERAL(1)))), + RETURN("n"))); + auto symbol_table = (memgraph::expr::MakeSymbolTable(query)); + auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAllByPrimaryKey(label, {expected_primary_key}), ExpectProduce()); + } + // One elem is missing from PK, default to ScanAllByLabelPropertyValue. + { + FakeDistributedDbAccessor dba; + auto label = dba.Label(prim_label_name); + + 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.CreateSchema(label, {prim_prop_one.second, prim_prop_two.second}); + + 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; + expected_primary_key = PROPERTY_LOOKUP("n", prim_prop_one); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", prim_label_name))), + WHERE(EQ(PROPERTY_LOOKUP("n", prim_prop_one), LITERAL(1))), RETURN("n"))); + auto symbol_table = (memgraph::expr::MakeSymbolTable(query)); + auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelPropertyValue(label, prim_prop_one, IDENT("n")), + ExpectProduce()); } } From 3604046f686fa112f8bc8320078468f5fca51cf3 Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Mon, 12 Dec 2022 10:53:07 +0100 Subject: [PATCH 06/85] Implement cypher query based simulation testing Make the Interpreter be able to handle SimulatorTransport as well. This includes introducing changes that make it possible to use the different transport types in a semi-polymorphic way with the introduction of factory methods in the RequestRouter. The reason for this solution is that the classes that represent the different transport types have member function templates, that we can not make virtual. This solution seemed to be the least convoluted. In the testing itself now it is possible to pass a set of cypher queried to the interpreter which would run these queries against the interpreter and the individual shards that are managed and started up by the MachineManager with the different entities communicating over the simulated network. --- src/io/local_transport/local_system.hpp | 2 + src/io/simulator/simulator.hpp | 4 +- src/io/simulator/simulator_transport.hpp | 2 +- src/memgraph.cpp | 14 ++- src/query/v2/interpreter.cpp | 32 ++--- src/query/v2/interpreter.hpp | 19 +-- src/query/v2/request_router.hpp | 109 ++++++++++++++++++ tests/simulation/CMakeLists.txt | 3 +- tests/simulation/cluster_property_test_v2.cpp | 65 +++++++++++ tests/simulation/simulation_interpreter.hpp | 95 +++++++++++++++ tests/simulation/test_cluster.hpp | 62 ++++++++++ 11 files changed, 368 insertions(+), 39 deletions(-) create mode 100644 tests/simulation/cluster_property_test_v2.cpp create mode 100644 tests/simulation/simulation_interpreter.hpp diff --git a/src/io/local_transport/local_system.hpp b/src/io/local_transport/local_system.hpp index 2e54f8d75..ec148b50b 100644 --- a/src/io/local_transport/local_system.hpp +++ b/src/io/local_transport/local_system.hpp @@ -29,6 +29,8 @@ class LocalSystem { return Io{local_transport, address}; } + std::shared_ptr<LocalTransportHandle> &GetTransportHandle() { return local_transport_handle_; } + void ShutDown() { local_transport_handle_->ShutDown(); } }; diff --git a/src/io/simulator/simulator.hpp b/src/io/simulator/simulator.hpp index 622c264b4..5095ab58b 100644 --- a/src/io/simulator/simulator.hpp +++ b/src/io/simulator/simulator.hpp @@ -41,7 +41,7 @@ class Simulator { Io<SimulatorTransport> Register(Address address) { std::uniform_int_distribution<uint64_t> seed_distrib; uint64_t seed = seed_distrib(rng_); - return Io{SimulatorTransport{simulator_handle_, address, seed}, address}; + return Io{SimulatorTransport(simulator_handle_, address, seed), address}; } void IncrementServerCountAndWaitForQuiescentState(Address address) { @@ -49,5 +49,7 @@ class Simulator { } SimulatorStats Stats() { return simulator_handle_->Stats(); } + + std::shared_ptr<SimulatorHandle> GetSimulatorHandle() { return simulator_handle_; } }; }; // namespace memgraph::io::simulator diff --git a/src/io/simulator/simulator_transport.hpp b/src/io/simulator/simulator_transport.hpp index 5e5a24aa9..492b59d3a 100644 --- a/src/io/simulator/simulator_transport.hpp +++ b/src/io/simulator/simulator_transport.hpp @@ -25,7 +25,7 @@ using memgraph::io::Time; class SimulatorTransport { std::shared_ptr<SimulatorHandle> simulator_handle_; - const Address address_; + Address address_; std::mt19937 rng_; public: diff --git a/src/memgraph.cpp b/src/memgraph.cpp index d825cc0e7..ca03024fe 100644 --- a/src/memgraph.cpp +++ b/src/memgraph.cpp @@ -607,15 +607,15 @@ int main(int argc, char **argv) { // to minimize the impact of their failure on the main storage. memgraph::io::local_transport::LocalSystem ls; - auto unique_local_addr_query = memgraph::coordinator::Address::UniqueLocalAddress(); - auto io = ls.Register(unique_local_addr_query); + auto unique_local_coord_addr_query = memgraph::coordinator::Address::UniqueLocalAddress(); + auto io = ls.Register(unique_local_coord_addr_query); memgraph::machine_manager::MachineConfig config{ - .coordinator_addresses = std::vector<memgraph::io::Address>{unique_local_addr_query}, + .coordinator_addresses = std::vector<memgraph::io::Address>{unique_local_coord_addr_query}, .is_storage = true, .is_coordinator = true, - .listen_ip = unique_local_addr_query.last_known_ip, - .listen_port = unique_local_addr_query.last_known_port, + .listen_ip = unique_local_coord_addr_query.last_known_ip, + .listen_port = unique_local_coord_addr_query.last_known_port, }; memgraph::coordinator::ShardMap sm; @@ -640,6 +640,8 @@ int main(int argc, char **argv) { memgraph::machine_manager::MachineManager<memgraph::io::local_transport::LocalTransport> mm{io, config, coordinator}; std::jthread mm_thread([&mm] { mm.Run(); }); + auto rr_factory = std::make_unique<memgraph::query::v2::LocalRequestRouterFactory>(ls.GetTransportHandle()); + memgraph::query::v2::InterpreterContext interpreter_context{ (memgraph::storage::v3::Shard *)(nullptr), {.query = {.allow_load_csv = FLAGS_allow_load_csv}, @@ -650,7 +652,7 @@ int main(int argc, char **argv) { .stream_transaction_conflict_retries = FLAGS_stream_transaction_conflict_retries, .stream_transaction_retry_interval = std::chrono::milliseconds(FLAGS_stream_transaction_retry_interval)}, FLAGS_data_directory, - std::move(io), + std::move(rr_factory), mm.CoordinatorAddress()}; SessionData session_data{&interpreter_context}; diff --git a/src/query/v2/interpreter.cpp b/src/query/v2/interpreter.cpp index 1c2d6dadf..c428d1a39 100644 --- a/src/query/v2/interpreter.cpp +++ b/src/query/v2/interpreter.cpp @@ -793,34 +793,24 @@ using RWType = plan::ReadWriteTypeChecker::RWType; InterpreterContext::InterpreterContext(storage::v3::Shard *db, const InterpreterConfig config, const std::filesystem::path & /*data_directory*/, - io::Io<io::local_transport::LocalTransport> io, + std::unique_ptr<RequestRouterFactory> &&request_router_factory, coordinator::Address coordinator_addr) - : db(db), config(config), io{std::move(io)}, coordinator_address{coordinator_addr} {} + : db(db), + config(config), + coordinator_address{coordinator_addr}, + request_router_factory_{std::move(request_router_factory)} {} Interpreter::Interpreter(InterpreterContext *interpreter_context) : interpreter_context_(interpreter_context) { MG_ASSERT(interpreter_context_, "Interpreter context must not be NULL"); - // TODO(tyler) make this deterministic so that it can be tested. - auto random_uuid = boost::uuids::uuid{boost::uuids::random_generator()()}; - auto query_io = interpreter_context_->io.ForkLocal(random_uuid); + request_router_ = + interpreter_context_->request_router_factory_->CreateRequestRouter(interpreter_context_->coordinator_address); - request_router_ = std::make_unique<RequestRouter<io::local_transport::LocalTransport>>( - coordinator::CoordinatorClient<io::local_transport::LocalTransport>( - query_io, interpreter_context_->coordinator_address, std::vector{interpreter_context_->coordinator_address}), - std::move(query_io)); // Get edge ids - coordinator::CoordinatorWriteRequests requests{coordinator::AllocateEdgeIdBatchRequest{.batch_size = 1000000}}; - io::rsm::WriteRequest<coordinator::CoordinatorWriteRequests> ww; - ww.operation = requests; - auto resp = interpreter_context_->io - .Request<io::rsm::WriteRequest<coordinator::CoordinatorWriteRequests>, - io::rsm::WriteResponse<coordinator::CoordinatorWriteResponses>>( - interpreter_context_->coordinator_address, ww) - .Wait(); - if (resp.HasValue()) { - const auto alloc_edge_id_reps = - std::get<coordinator::AllocateEdgeIdBatchResponse>(resp.GetValue().message.write_return); - interpreter_context_->edge_ids_alloc = {alloc_edge_id_reps.low, alloc_edge_id_reps.high}; + const auto edge_ids_alloc_min_max_pair = + request_router_->AllocateInitialEdgeIds(interpreter_context_->coordinator_address); + if (edge_ids_alloc_min_max_pair) { + interpreter_context_->edge_ids_alloc = {edge_ids_alloc_min_max_pair->first, edge_ids_alloc_min_max_pair->second}; } } diff --git a/src/query/v2/interpreter.hpp b/src/query/v2/interpreter.hpp index 985c9a90c..a83a26f11 100644 --- a/src/query/v2/interpreter.hpp +++ b/src/query/v2/interpreter.hpp @@ -17,6 +17,7 @@ #include "coordinator/coordinator.hpp" #include "coordinator/coordinator_client.hpp" #include "io/local_transport/local_transport.hpp" +#include "io/simulator/simulator_transport.hpp" #include "io/transport.hpp" #include "query/v2/auth_checker.hpp" #include "query/v2/bindings/cypher_main_visitor.hpp" @@ -172,7 +173,8 @@ struct PreparedQuery { struct InterpreterContext { explicit InterpreterContext(storage::v3::Shard *db, InterpreterConfig config, const std::filesystem::path &data_directory, - io::Io<io::local_transport::LocalTransport> io, coordinator::Address coordinator_addr); + std::unique_ptr<RequestRouterFactory> &&request_router_factory, + coordinator::Address coordinator_addr); storage::v3::Shard *db; @@ -188,26 +190,25 @@ struct InterpreterContext { const InterpreterConfig config; IdAllocator edge_ids_alloc; - // TODO (antaljanosbenjamin) Figure out an abstraction for io::Io to make it possible to construct an interpreter - // context with a simulator transport without templatizing it. - io::Io<io::local_transport::LocalTransport> io; coordinator::Address coordinator_address; storage::v3::LabelId NameToLabelId(std::string_view label_name) { - return storage::v3::LabelId::FromUint(query_id_mapper.NameToId(label_name)); + return storage::v3::LabelId::FromUint(query_id_mapper_.NameToId(label_name)); } storage::v3::PropertyId NameToPropertyId(std::string_view property_name) { - return storage::v3::PropertyId::FromUint(query_id_mapper.NameToId(property_name)); + return storage::v3::PropertyId::FromUint(query_id_mapper_.NameToId(property_name)); } storage::v3::EdgeTypeId NameToEdgeTypeId(std::string_view edge_type_name) { - return storage::v3::EdgeTypeId::FromUint(query_id_mapper.NameToId(edge_type_name)); + return storage::v3::EdgeTypeId::FromUint(query_id_mapper_.NameToId(edge_type_name)); } + std::unique_ptr<RequestRouterFactory> request_router_factory_; + private: // TODO Replace with local map of labels, properties and edge type ids - storage::v3::NameIdMapper query_id_mapper; + storage::v3::NameIdMapper query_id_mapper_; }; /// Function that is used to tell all active interpreters that they should stop @@ -296,7 +297,7 @@ class Interpreter final { */ void Abort(); - const RequestRouterInterface *GetRequestRouter() const { return request_router_.get(); } + RequestRouterInterface *GetRequestRouter() { return request_router_.get(); } private: struct QueryExecution { diff --git a/src/query/v2/request_router.hpp b/src/query/v2/request_router.hpp index 996272fdc..de63cd76e 100644 --- a/src/query/v2/request_router.hpp +++ b/src/query/v2/request_router.hpp @@ -11,6 +11,7 @@ #pragma once +#include <boost/uuid/uuid.hpp> #include <chrono> #include <deque> #include <iostream> @@ -23,6 +24,7 @@ #include <stdexcept> #include <thread> #include <unordered_map> +#include <variant> #include <vector> #include "coordinator/coordinator.hpp" @@ -31,6 +33,7 @@ #include "coordinator/shard_map.hpp" #include "io/address.hpp" #include "io/errors.hpp" +#include "io/local_transport/local_transport.hpp" #include "io/rsm/raft.hpp" #include "io/rsm/rsm_client.hpp" #include "io/rsm/shard_rsm.hpp" @@ -124,6 +127,10 @@ class RequestRouterInterface { virtual std::optional<storage::v3::LabelId> MaybeNameToLabel(const std::string &name) const = 0; virtual bool IsPrimaryLabel(storage::v3::LabelId label) const = 0; virtual bool IsPrimaryKey(storage::v3::LabelId primary_label, storage::v3::PropertyId property) const = 0; + + virtual std::optional<std::pair<uint64_t, uint64_t>> AllocateInitialEdgeIds(io::Address coordinator_address) { + return {}; + } }; // TODO(kostasrim)rename this class template @@ -595,6 +602,23 @@ class RequestRouter : public RequestRouterInterface { edge_types_.StoreMapping(std::move(id_to_name)); } + std::optional<std::pair<uint64_t, uint64_t>> AllocateInitialEdgeIds(io::Address coordinator_address) override { + coordinator::CoordinatorWriteRequests requests{coordinator::AllocateEdgeIdBatchRequest{.batch_size = 1000000}}; + + io::rsm::WriteRequest<coordinator::CoordinatorWriteRequests> ww; + ww.operation = requests; + auto resp = + io_.template Request<io::rsm::WriteRequest<coordinator::CoordinatorWriteRequests>, + io::rsm::WriteResponse<coordinator::CoordinatorWriteResponses>>(coordinator_address, ww) + .Wait(); + if (resp.HasValue()) { + const auto alloc_edge_id_reps = + std::get<coordinator::AllocateEdgeIdBatchResponse>(resp.GetValue().message.write_return); + return std::make_pair(alloc_edge_id_reps.low, alloc_edge_id_reps.high); + } + return {}; + } + ShardMap shards_map_; storage::v3::NameIdMapper properties_; storage::v3::NameIdMapper edge_types_; @@ -605,4 +629,89 @@ class RequestRouter : public RequestRouterInterface { coordinator::Hlc transaction_id_; // TODO(kostasrim) Add batch prefetching }; + +class RequestRouterFactory { + protected: + using LocalTransport = io::Io<io::local_transport::LocalTransport>; + using SimulatorTransport = io::Io<io::simulator::SimulatorTransport>; + + using LocalTransportHandlePtr = std::shared_ptr<io::local_transport::LocalTransportHandle>; + using SimulatorTransportHandlePtr = std::shared_ptr<io::simulator::SimulatorHandle>; + + using TransportHandleVariant = std::variant<LocalTransportHandlePtr, SimulatorTransportHandlePtr>; + + TransportHandleVariant transport_handle_; + + public: + explicit RequestRouterFactory(const TransportHandleVariant &transport_handle) : transport_handle_(transport_handle) {} + + RequestRouterFactory(const RequestRouterFactory &) = delete; + RequestRouterFactory &operator=(const RequestRouterFactory &) = delete; + RequestRouterFactory(RequestRouterFactory &&) = delete; + RequestRouterFactory &operator=(RequestRouterFactory &&) = delete; + + virtual ~RequestRouterFactory() = default; + + virtual TransportHandleVariant GetTransportHandle() { return transport_handle_; } + + virtual std::unique_ptr<RequestRouterInterface> CreateRequestRouter( + const coordinator::Address &coordinator_address) const noexcept = 0; +}; + +class LocalRequestRouterFactory : public RequestRouterFactory { + public: + explicit LocalRequestRouterFactory(const TransportHandleVariant &transport_handle) + : RequestRouterFactory(transport_handle) {} + + std::unique_ptr<RequestRouterInterface> CreateRequestRouter( + const coordinator::Address &coordinator_address) const noexcept override { + using TransportType = io::local_transport::LocalTransport; + auto actual_transport_handle = std::get<LocalTransportHandlePtr>(transport_handle_); + + boost::uuids::uuid random_uuid; + io::Address unique_local_addr_query; + + random_uuid = boost::uuids::uuid{boost::uuids::random_generator()()}; + unique_local_addr_query = memgraph::coordinator::Address::UniqueLocalAddress(); + + TransportType local_transport(actual_transport_handle); + auto local_transport_io = io::Io<TransportType>(local_transport, unique_local_addr_query); + + auto query_io = local_transport_io.ForkLocal(random_uuid); + + return std::make_unique<RequestRouter<TransportType>>( + coordinator::CoordinatorClient<TransportType>(query_io, coordinator_address, {coordinator_address}), + std::move(local_transport_io)); + } +}; + +class SimulatedRequestRouterFactory : public RequestRouterFactory { + mutable io::simulator::Simulator *simulator_; + coordinator::Address address_; + + public: + explicit SimulatedRequestRouterFactory(io::simulator::Simulator &simulator, coordinator::Address address) + : RequestRouterFactory(simulator.GetSimulatorHandle()), simulator_(&simulator), address_(address) {} + + std::unique_ptr<RequestRouterInterface> CreateRequestRouter( + const coordinator::Address &coordinator_address) const noexcept override { + using TransportType = io::simulator::SimulatorTransport; + auto actual_transport_handle = std::get<SimulatorTransportHandlePtr>(transport_handle_); + + boost::uuids::uuid random_uuid; + io::Address unique_local_addr_query; + + // The simulated RR should not introduce stochastic behavior. + random_uuid = boost::uuids::uuid{3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unique_local_addr_query = {.unique_id = boost::uuids::uuid{4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}; + + auto io = simulator_->Register(unique_local_addr_query); + auto query_io = io.ForkLocal(random_uuid); + + return std::make_unique<RequestRouter<TransportType>>( + coordinator::CoordinatorClient<TransportType>(query_io, coordinator_address, {coordinator_address}), + std::move(io)); + } +}; + } // namespace memgraph::query::v2 diff --git a/tests/simulation/CMakeLists.txt b/tests/simulation/CMakeLists.txt index 9e1a4c71e..f81f89798 100644 --- a/tests/simulation/CMakeLists.txt +++ b/tests/simulation/CMakeLists.txt @@ -17,7 +17,7 @@ function(add_simulation_test test_cpp) # requires unique logical target names set_target_properties(${target_name} PROPERTIES OUTPUT_NAME ${exec_name}) - target_link_libraries(${target_name} mg-storage-v3 mg-communication mg-utils mg-io mg-io-simulator mg-coordinator mg-query-v2) + target_link_libraries(${target_name} mg-communication mg-utils mg-io mg-io-simulator mg-coordinator mg-query-v2 mg-storage-v3) target_link_libraries(${target_name} Boost::headers) target_link_libraries(${target_name} gtest gtest_main gmock rapidcheck rapidcheck_gtest) @@ -32,3 +32,4 @@ add_simulation_test(trial_query_storage/query_storage_test.cpp) add_simulation_test(sharded_map.cpp) add_simulation_test(shard_rsm.cpp) add_simulation_test(cluster_property_test.cpp) +add_simulation_test(cluster_property_test_v2.cpp) diff --git a/tests/simulation/cluster_property_test_v2.cpp b/tests/simulation/cluster_property_test_v2.cpp new file mode 100644 index 000000000..2f121647b --- /dev/null +++ b/tests/simulation/cluster_property_test_v2.cpp @@ -0,0 +1,65 @@ +// 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. + +// This test serves as an example of a property-based model test. +// It generates a cluster configuration and a set of operations to +// apply against both the real system and a greatly simplified model. + +#include <chrono> + +#include <gtest/gtest.h> +#include <rapidcheck.h> +#include <rapidcheck/gtest.h> +#include <spdlog/cfg/env.h> + +#include "generated_operations.hpp" +#include "io/simulator/simulator_config.hpp" +#include "io/time.hpp" +#include "storage/v3/shard_manager.hpp" +#include "test_cluster.hpp" + +namespace memgraph::tests::simulation { + +using io::Duration; +using io::Time; +using io::simulator::SimulatorConfig; +using storage::v3::kMaximumCronInterval; + +RC_GTEST_PROP(RandomClusterConfig, HappyPath, (ClusterConfig cluster_config, NonEmptyOpVec ops, uint64_t rng_seed)) { + spdlog::cfg::load_env_levels(); + + SimulatorConfig sim_config{ + .drop_percent = 0, + .perform_timeouts = false, + .scramble_messages = true, + .rng_seed = rng_seed, + .start_time = Time::min(), + // TODO(tyler) set abort_time to something more restrictive than Time::max() + .abort_time = Time::max(), + }; + + std::vector<std::string> queries = {"CREATE (n:test_label{property_1: 0, property_2: 0});", "MATCH (n) RETURN n;"}; + + auto [sim_stats_1, latency_stats_1] = RunClusterSimulationWithQueries(sim_config, cluster_config, queries); + auto [sim_stats_2, latency_stats_2] = RunClusterSimulationWithQueries(sim_config, cluster_config, queries); + + if (latency_stats_1 != latency_stats_2) { + spdlog::error("simulator stats diverged across runs"); + spdlog::error("run 1 simulator stats: {}", sim_stats_1); + spdlog::error("run 2 simulator stats: {}", sim_stats_2); + spdlog::error("run 1 latency:\n{}", latency_stats_1.SummaryTable()); + spdlog::error("run 2 latency:\n{}", latency_stats_2.SummaryTable()); + RC_ASSERT(latency_stats_1 == latency_stats_2); + RC_ASSERT(sim_stats_1 == sim_stats_2); + } +} + +} // namespace memgraph::tests::simulation diff --git a/tests/simulation/simulation_interpreter.hpp b/tests/simulation/simulation_interpreter.hpp new file mode 100644 index 000000000..b59f335be --- /dev/null +++ b/tests/simulation/simulation_interpreter.hpp @@ -0,0 +1,95 @@ +// 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 <machine_manager/machine_config.hpp> +#include <machine_manager/machine_manager.hpp> +#include "io/simulator/simulator_handle.hpp" +#include "query/v2/config.hpp" +#include "query/v2/discard_value_stream.hpp" +#include "query/v2/frontend/ast/ast.hpp" +#include "query/v2/interpreter.hpp" +#include "query/v2/request_router.hpp" + +#include <memory> + +// TODO(gvolfing) +// -How to set up the entire raft cluster with the QE. Also provide abrstraction for that. +// -Pass an argument to the setup to determine, how many times the retry of a query should happen. + +namespace memgraph::io::simulator { + +class SimulatedInterpreter { + using ResultStream = query::v2::DiscardValueResultStream; + + public: + explicit SimulatedInterpreter(std::unique_ptr<query::v2::InterpreterContext> &&interpreter_context) + : interpreter_context_(std::move(interpreter_context)) { + interpreter_ = std::make_unique<memgraph::query::v2::Interpreter>(&(*interpreter_context_)); + } + + SimulatedInterpreter(const SimulatedInterpreter &) = delete; + SimulatedInterpreter &operator=(const SimulatedInterpreter &) = delete; + SimulatedInterpreter(SimulatedInterpreter &&) = delete; + SimulatedInterpreter &operator=(SimulatedInterpreter &&) = delete; + ~SimulatedInterpreter() = default; + + std::vector<ResultStream> RunQueries(const std::vector<std::string> &queries) { + std::vector<ResultStream> results; + results.reserve(queries.size()); + + for (const auto &query : queries) { + results.emplace_back(RunQuery(query)); + } + return results; + } + + private: + ResultStream RunQuery(const std::string &query) { + ResultStream stream; + + std::map<std::string, memgraph::storage::v3::PropertyValue> params; + const std::string *username = nullptr; + + interpreter_->BeginTransaction(); + + auto *rr = interpreter_->GetRequestRouter(); + rr->StartTransaction(); + + interpreter_->Prepare(query, params, username); + interpreter_->PullAll(&stream); + interpreter_->CommitTransaction(); + + return stream; + } + + std::unique_ptr<query::v2::InterpreterContext> interpreter_context_; + std::unique_ptr<query::v2::Interpreter> interpreter_; +}; + +SimulatedInterpreter SetUpInterpreter(Address coordinator_address, Simulator &simulator) { + auto rr_factory = + std::make_unique<memgraph::query::v2::SimulatedRequestRouterFactory>(simulator, coordinator_address); + + auto interpreter_context = std::make_unique<memgraph::query::v2::InterpreterContext>( + (memgraph::storage::v3::Shard *)(nullptr), + memgraph::query::v2::InterpreterConfig{.query = {.allow_load_csv = true}, + .execution_timeout_sec = 600, + .replication_replica_check_frequency = std::chrono::seconds(1), + .default_kafka_bootstrap_servers = "", + .default_pulsar_service_url = "", + .stream_transaction_conflict_retries = 30, + .stream_transaction_retry_interval = std::chrono::milliseconds(500)}, + std::filesystem::path("mg_data"), std::move(rr_factory), coordinator_address); + + return SimulatedInterpreter(std::move(interpreter_context)); +} + +} // namespace memgraph::io::simulator diff --git a/tests/simulation/test_cluster.hpp b/tests/simulation/test_cluster.hpp index 1392a0632..5aa792c16 100644 --- a/tests/simulation/test_cluster.hpp +++ b/tests/simulation/test_cluster.hpp @@ -36,6 +36,8 @@ #include "utils/print_helpers.hpp" #include "utils/variant_helpers.hpp" +#include "simulation_interpreter.hpp" + namespace memgraph::tests::simulation { using coordinator::Coordinator; @@ -277,4 +279,64 @@ std::pair<SimulatorStats, LatencyHistogramSummaries> RunClusterSimulation(const return std::make_pair(stats, histo); } +std::pair<SimulatorStats, LatencyHistogramSummaries> RunClusterSimulationWithQueries( + const SimulatorConfig &sim_config, const ClusterConfig &cluster_config, const std::vector<std::string> &queries) { + spdlog::info("========================== NEW SIMULATION =========================="); + + auto simulator = Simulator(sim_config); + + auto machine_1_addr = Address::TestAddress(1); + auto cli_addr = Address::TestAddress(2); + auto cli_addr_2 = Address::TestAddress(3); + + Io<SimulatorTransport> cli_io = simulator.Register(cli_addr); + Io<SimulatorTransport> cli_io_2 = simulator.Register(cli_addr_2); + + auto coordinator_addresses = std::vector{ + machine_1_addr, + }; + + ShardMap initialization_sm = TestShardMap(cluster_config.shards - 1, cluster_config.replication_factor); + + auto mm_1 = MkMm(simulator, coordinator_addresses, machine_1_addr, initialization_sm); + Address coordinator_address = mm_1.CoordinatorAddress(); + + auto mm_thread_1 = std::jthread(RunMachine, std::move(mm_1)); + simulator.IncrementServerCountAndWaitForQuiescentState(machine_1_addr); + + auto detach_on_error = DetachIfDropped{.handle = mm_thread_1}; + + // TODO(tyler) clarify addresses of coordinator etc... as it's a mess + + CoordinatorClient<SimulatorTransport> coordinator_client(cli_io, coordinator_address, {coordinator_address}); + WaitForShardsToInitialize(coordinator_client); + + auto simulated_interpreter = io::simulator::SetUpInterpreter(coordinator_address, simulator); + + auto query_results = simulated_interpreter.RunQueries(queries); + + // We have now completed our workload without failing any assertions, so we can + // disable detaching the worker thread, which will cause the mm_thread_1 jthread + // to be joined when this function returns. + detach_on_error.detach = false; + + simulator.ShutDown(); + + mm_thread_1.join(); + + SimulatorStats stats = simulator.Stats(); + + spdlog::info("total messages: {}", stats.total_messages); + spdlog::info("dropped messages: {}", stats.dropped_messages); + spdlog::info("timed out requests: {}", stats.timed_out_requests); + spdlog::info("total requests: {}", stats.total_requests); + spdlog::info("total responses: {}", stats.total_responses); + spdlog::info("simulator ticks: {}", stats.simulator_ticks); + + auto histo = cli_io_2.ResponseLatencies(); + + spdlog::info("========================== SUCCESS :) =========================="); + return std::make_pair(stats, histo); +} + } // namespace memgraph::tests::simulation From f36b96744c317cce627a62c3521f642dd57176f2 Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Thu, 15 Dec 2022 11:04:20 +0100 Subject: [PATCH 07/85] Apply post-merge fixes --- src/query/v2/plan/pretty_print.cpp | 4 ++-- src/query/v2/request_router.hpp | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/query/v2/plan/pretty_print.cpp b/src/query/v2/plan/pretty_print.cpp index adfab1a5f..762a1b19a 100644 --- a/src/query/v2/plan/pretty_print.cpp +++ b/src/query/v2/plan/pretty_print.cpp @@ -89,7 +89,7 @@ bool PlanPrinter::PreVisit(query::v2::plan::ScanAllByLabelProperty &op) { bool PlanPrinter::PreVisit(query::v2::plan::ScanAllByPrimaryKey &op) { WithPrintLn([&](auto &out) { out << "* ScanAllByPrimaryKey" - << " (" << op.output_symbol_.name() << " :" << request_manager_->LabelToName(op.label_) << ")"; + << " (" << op.output_symbol_.name() << " :" << request_router_->LabelToName(op.label_) << ")"; }); return true; } @@ -490,7 +490,7 @@ bool PlanToJsonVisitor::PreVisit(ScanAllByLabelProperty &op) { bool PlanToJsonVisitor::PreVisit(ScanAllByPrimaryKey &op) { json self; self["name"] = "ScanAllByPrimaryKey"; - self["label"] = ToJson(op.label_, *request_manager_); + self["label"] = ToJson(op.label_, *request_router_); self["output_symbol"] = ToJson(op.output_symbol_); op.input_->Accept(*this); diff --git a/src/query/v2/request_router.hpp b/src/query/v2/request_router.hpp index 3dd2f164b..b5878ff92 100644 --- a/src/query/v2/request_router.hpp +++ b/src/query/v2/request_router.hpp @@ -114,6 +114,10 @@ class RequestRouterInterface { virtual std::optional<storage::v3::LabelId> MaybeNameToLabel(const std::string &name) const = 0; virtual bool IsPrimaryLabel(storage::v3::LabelId label) const = 0; virtual bool IsPrimaryKey(storage::v3::LabelId primary_label, storage::v3::PropertyId property) const = 0; + // TODO - (gvolfing) Implement this function in the mocked class. + virtual std::vector<coordinator::SchemaProperty> GetSchemaForLabel(storage::v3::LabelId label) const { + return std::vector<coordinator::SchemaProperty>{}; + }; }; // TODO(kostasrim)rename this class template @@ -232,6 +236,10 @@ class RequestRouter : public RequestRouterInterface { }) != schema_it->second.end(); } + std::vector<coordinator::SchemaProperty> GetSchemaForLabel(storage::v3::LabelId label) const override { + return shards_map_.schemas.at(label); + } + bool IsPrimaryLabel(storage::v3::LabelId label) const override { return shards_map_.label_spaces.contains(label); } // TODO(kostasrim) Simplify return result From ae57fa31999a998676f750296ee7ec6c254eb334 Mon Sep 17 00:00:00 2001 From: gvolfing <107616712+gvolfing@users.noreply.github.com> Date: Thu, 15 Dec 2022 15:25:46 +0100 Subject: [PATCH 08/85] Apply suggestions from code review Co-authored-by: Kostas Kyrimis <kostaskyrim@gmail.com> --- src/io/local_transport/local_system.hpp | 2 +- src/io/simulator/simulator.hpp | 2 +- src/query/v2/interpreter.cpp | 6 +++--- src/query/v2/interpreter.hpp | 2 +- src/query/v2/request_router.hpp | 8 ++++---- tests/simulation/cluster_property_test_v2.cpp | 1 - tests/simulation/simulation_interpreter.hpp | 13 +++++++------ 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/io/local_transport/local_system.hpp b/src/io/local_transport/local_system.hpp index ec148b50b..feea44244 100644 --- a/src/io/local_transport/local_system.hpp +++ b/src/io/local_transport/local_system.hpp @@ -29,7 +29,7 @@ class LocalSystem { return Io{local_transport, address}; } - std::shared_ptr<LocalTransportHandle> &GetTransportHandle() { return local_transport_handle_; } + std::shared_ptr<LocalTransportHandle> GetTransportHandle() const { return local_transport_handle_; } void ShutDown() { local_transport_handle_->ShutDown(); } }; diff --git a/src/io/simulator/simulator.hpp b/src/io/simulator/simulator.hpp index 5095ab58b..87ced7180 100644 --- a/src/io/simulator/simulator.hpp +++ b/src/io/simulator/simulator.hpp @@ -50,6 +50,6 @@ class Simulator { SimulatorStats Stats() { return simulator_handle_->Stats(); } - std::shared_ptr<SimulatorHandle> GetSimulatorHandle() { return simulator_handle_; } + std::shared_ptr<SimulatorHandle> GetSimulatorHandle() const { return simulator_handle_; } }; }; // namespace memgraph::io::simulator diff --git a/src/query/v2/interpreter.cpp b/src/query/v2/interpreter.cpp index c428d1a39..8042a763c 100644 --- a/src/query/v2/interpreter.cpp +++ b/src/query/v2/interpreter.cpp @@ -793,7 +793,7 @@ using RWType = plan::ReadWriteTypeChecker::RWType; InterpreterContext::InterpreterContext(storage::v3::Shard *db, const InterpreterConfig config, const std::filesystem::path & /*data_directory*/, - std::unique_ptr<RequestRouterFactory> &&request_router_factory, + std::unique_ptr<RequestRouterFactory> request_router_factory, coordinator::Address coordinator_addr) : db(db), config(config), @@ -807,10 +807,10 @@ Interpreter::Interpreter(InterpreterContext *interpreter_context) : interpreter_ interpreter_context_->request_router_factory_->CreateRequestRouter(interpreter_context_->coordinator_address); // Get edge ids - const auto edge_ids_alloc_min_max_pair = + const auto [min, max] = request_router_->AllocateInitialEdgeIds(interpreter_context_->coordinator_address); if (edge_ids_alloc_min_max_pair) { - interpreter_context_->edge_ids_alloc = {edge_ids_alloc_min_max_pair->first, edge_ids_alloc_min_max_pair->second}; + interpreter_context_->edge_ids_alloc = {min, max} } } diff --git a/src/query/v2/interpreter.hpp b/src/query/v2/interpreter.hpp index a83a26f11..05b05dd8e 100644 --- a/src/query/v2/interpreter.hpp +++ b/src/query/v2/interpreter.hpp @@ -173,7 +173,7 @@ struct PreparedQuery { struct InterpreterContext { explicit InterpreterContext(storage::v3::Shard *db, InterpreterConfig config, const std::filesystem::path &data_directory, - std::unique_ptr<RequestRouterFactory> &&request_router_factory, + std::unique_ptr<RequestRouterFactory> request_router_factory, coordinator::Address coordinator_addr); storage::v3::Shard *db; diff --git a/src/query/v2/request_router.hpp b/src/query/v2/request_router.hpp index de63cd76e..7ac5d1816 100644 --- a/src/query/v2/request_router.hpp +++ b/src/query/v2/request_router.hpp @@ -655,7 +655,7 @@ class RequestRouterFactory { virtual TransportHandleVariant GetTransportHandle() { return transport_handle_; } virtual std::unique_ptr<RequestRouterInterface> CreateRequestRouter( - const coordinator::Address &coordinator_address) const noexcept = 0; + const coordinator::Address &coordinator_address) const = 0; }; class LocalRequestRouterFactory : public RequestRouterFactory { @@ -664,7 +664,7 @@ class LocalRequestRouterFactory : public RequestRouterFactory { : RequestRouterFactory(transport_handle) {} std::unique_ptr<RequestRouterInterface> CreateRequestRouter( - const coordinator::Address &coordinator_address) const noexcept override { + const coordinator::Address &coordinator_address) const override { using TransportType = io::local_transport::LocalTransport; auto actual_transport_handle = std::get<LocalTransportHandlePtr>(transport_handle_); @@ -686,7 +686,7 @@ class LocalRequestRouterFactory : public RequestRouterFactory { }; class SimulatedRequestRouterFactory : public RequestRouterFactory { - mutable io::simulator::Simulator *simulator_; + io::simulator::Simulator *simulator_; coordinator::Address address_; public: @@ -694,7 +694,7 @@ class SimulatedRequestRouterFactory : public RequestRouterFactory { : RequestRouterFactory(simulator.GetSimulatorHandle()), simulator_(&simulator), address_(address) {} std::unique_ptr<RequestRouterInterface> CreateRequestRouter( - const coordinator::Address &coordinator_address) const noexcept override { + const coordinator::Address &coordinator_address) const override { using TransportType = io::simulator::SimulatorTransport; auto actual_transport_handle = std::get<SimulatorTransportHandlePtr>(transport_handle_); diff --git a/tests/simulation/cluster_property_test_v2.cpp b/tests/simulation/cluster_property_test_v2.cpp index 2f121647b..4996b4c2f 100644 --- a/tests/simulation/cluster_property_test_v2.cpp +++ b/tests/simulation/cluster_property_test_v2.cpp @@ -42,7 +42,6 @@ RC_GTEST_PROP(RandomClusterConfig, HappyPath, (ClusterConfig cluster_config, Non .scramble_messages = true, .rng_seed = rng_seed, .start_time = Time::min(), - // TODO(tyler) set abort_time to something more restrictive than Time::max() .abort_time = Time::max(), }; diff --git a/tests/simulation/simulation_interpreter.hpp b/tests/simulation/simulation_interpreter.hpp index b59f335be..e25dfaa99 100644 --- a/tests/simulation/simulation_interpreter.hpp +++ b/tests/simulation/simulation_interpreter.hpp @@ -9,8 +9,8 @@ // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. -#include <machine_manager/machine_config.hpp> -#include <machine_manager/machine_manager.hpp> +#include "machine_manager/machine_config.hpp" +#include "machine_manager/machine_manager.hpp" #include "io/simulator/simulator_handle.hpp" #include "query/v2/config.hpp" #include "query/v2/discard_value_stream.hpp" @@ -18,7 +18,8 @@ #include "query/v2/interpreter.hpp" #include "query/v2/request_router.hpp" -#include <memory> +#include <vector> +#include <string> // TODO(gvolfing) // -How to set up the entire raft cluster with the QE. Also provide abrstraction for that. @@ -30,9 +31,9 @@ class SimulatedInterpreter { using ResultStream = query::v2::DiscardValueResultStream; public: - explicit SimulatedInterpreter(std::unique_ptr<query::v2::InterpreterContext> &&interpreter_context) + explicit SimulatedInterpreter(std::unique_ptr<query::v2::InterpreterContext> interpreter_context) : interpreter_context_(std::move(interpreter_context)) { - interpreter_ = std::make_unique<memgraph::query::v2::Interpreter>(&(*interpreter_context_)); + interpreter_ = std::make_unique<memgraph::query::v2::Interpreter>(interpreter_context_); } SimulatedInterpreter(const SimulatedInterpreter &) = delete; @@ -79,7 +80,7 @@ SimulatedInterpreter SetUpInterpreter(Address coordinator_address, Simulator &si std::make_unique<memgraph::query::v2::SimulatedRequestRouterFactory>(simulator, coordinator_address); auto interpreter_context = std::make_unique<memgraph::query::v2::InterpreterContext>( - (memgraph::storage::v3::Shard *)(nullptr), + nullptr memgraph::query::v2::InterpreterConfig{.query = {.allow_load_csv = true}, .execution_timeout_sec = 600, .replication_replica_check_frequency = std::chrono::seconds(1), From fa39c6740b96d96bda49960f46656cc80519329d Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Thu, 15 Dec 2022 17:02:01 +0100 Subject: [PATCH 09/85] Apply review comments --- src/memgraph.cpp | 10 +++++----- src/query/v2/interpreter.cpp | 4 ++-- src/query/v2/request_router.hpp | 5 ++--- tests/simulation/CMakeLists.txt | 2 +- ...2.cpp => cluster_property_test_cypher_queries.cpp} | 0 tests/simulation/simulation_interpreter.hpp | 11 +++++------ 6 files changed, 15 insertions(+), 17 deletions(-) rename tests/simulation/{cluster_property_test_v2.cpp => cluster_property_test_cypher_queries.cpp} (100%) diff --git a/src/memgraph.cpp b/src/memgraph.cpp index ca03024fe..cdb1e63df 100644 --- a/src/memgraph.cpp +++ b/src/memgraph.cpp @@ -607,15 +607,15 @@ int main(int argc, char **argv) { // to minimize the impact of their failure on the main storage. memgraph::io::local_transport::LocalSystem ls; - auto unique_local_coord_addr_query = memgraph::coordinator::Address::UniqueLocalAddress(); - auto io = ls.Register(unique_local_coord_addr_query); + auto unique_local_addr_query = memgraph::coordinator::Address::UniqueLocalAddress(); + auto io = ls.Register(unique_local_addr_query); memgraph::machine_manager::MachineConfig config{ - .coordinator_addresses = std::vector<memgraph::io::Address>{unique_local_coord_addr_query}, + .coordinator_addresses = std::vector<memgraph::io::Address>{unique_local_addr_query}, .is_storage = true, .is_coordinator = true, - .listen_ip = unique_local_coord_addr_query.last_known_ip, - .listen_port = unique_local_coord_addr_query.last_known_port, + .listen_ip = unique_local_addr_query.last_known_ip, + .listen_port = unique_local_addr_query.last_known_port, }; memgraph::coordinator::ShardMap sm; diff --git a/src/query/v2/interpreter.cpp b/src/query/v2/interpreter.cpp index 8042a763c..46a479f36 100644 --- a/src/query/v2/interpreter.cpp +++ b/src/query/v2/interpreter.cpp @@ -807,10 +807,10 @@ Interpreter::Interpreter(InterpreterContext *interpreter_context) : interpreter_ interpreter_context_->request_router_factory_->CreateRequestRouter(interpreter_context_->coordinator_address); // Get edge ids - const auto [min, max] = + const auto edge_ids_alloc_min_max_pair = request_router_->AllocateInitialEdgeIds(interpreter_context_->coordinator_address); if (edge_ids_alloc_min_max_pair) { - interpreter_context_->edge_ids_alloc = {min, max} + interpreter_context_->edge_ids_alloc = {edge_ids_alloc_min_max_pair->first, edge_ids_alloc_min_max_pair->second}; } } diff --git a/src/query/v2/request_router.hpp b/src/query/v2/request_router.hpp index 7ac5d1816..74117f52e 100644 --- a/src/query/v2/request_router.hpp +++ b/src/query/v2/request_router.hpp @@ -687,11 +687,10 @@ class LocalRequestRouterFactory : public RequestRouterFactory { class SimulatedRequestRouterFactory : public RequestRouterFactory { io::simulator::Simulator *simulator_; - coordinator::Address address_; public: - explicit SimulatedRequestRouterFactory(io::simulator::Simulator &simulator, coordinator::Address address) - : RequestRouterFactory(simulator.GetSimulatorHandle()), simulator_(&simulator), address_(address) {} + explicit SimulatedRequestRouterFactory(io::simulator::Simulator &simulator) + : RequestRouterFactory(simulator.GetSimulatorHandle()), simulator_(&simulator) {} std::unique_ptr<RequestRouterInterface> CreateRequestRouter( const coordinator::Address &coordinator_address) const override { diff --git a/tests/simulation/CMakeLists.txt b/tests/simulation/CMakeLists.txt index f81f89798..28ff60b02 100644 --- a/tests/simulation/CMakeLists.txt +++ b/tests/simulation/CMakeLists.txt @@ -32,4 +32,4 @@ add_simulation_test(trial_query_storage/query_storage_test.cpp) add_simulation_test(sharded_map.cpp) add_simulation_test(shard_rsm.cpp) add_simulation_test(cluster_property_test.cpp) -add_simulation_test(cluster_property_test_v2.cpp) +add_simulation_test(cluster_property_test_cypher_queries.cpp) diff --git a/tests/simulation/cluster_property_test_v2.cpp b/tests/simulation/cluster_property_test_cypher_queries.cpp similarity index 100% rename from tests/simulation/cluster_property_test_v2.cpp rename to tests/simulation/cluster_property_test_cypher_queries.cpp diff --git a/tests/simulation/simulation_interpreter.hpp b/tests/simulation/simulation_interpreter.hpp index e25dfaa99..8e37f4f70 100644 --- a/tests/simulation/simulation_interpreter.hpp +++ b/tests/simulation/simulation_interpreter.hpp @@ -9,17 +9,17 @@ // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. +#include "io/simulator/simulator_handle.hpp" #include "machine_manager/machine_config.hpp" #include "machine_manager/machine_manager.hpp" -#include "io/simulator/simulator_handle.hpp" #include "query/v2/config.hpp" #include "query/v2/discard_value_stream.hpp" #include "query/v2/frontend/ast/ast.hpp" #include "query/v2/interpreter.hpp" #include "query/v2/request_router.hpp" -#include <vector> #include <string> +#include <vector> // TODO(gvolfing) // -How to set up the entire raft cluster with the QE. Also provide abrstraction for that. @@ -33,7 +33,7 @@ class SimulatedInterpreter { public: explicit SimulatedInterpreter(std::unique_ptr<query::v2::InterpreterContext> interpreter_context) : interpreter_context_(std::move(interpreter_context)) { - interpreter_ = std::make_unique<memgraph::query::v2::Interpreter>(interpreter_context_); + interpreter_ = std::make_unique<memgraph::query::v2::Interpreter>(interpreter_context_.get()); } SimulatedInterpreter(const SimulatedInterpreter &) = delete; @@ -76,11 +76,10 @@ class SimulatedInterpreter { }; SimulatedInterpreter SetUpInterpreter(Address coordinator_address, Simulator &simulator) { - auto rr_factory = - std::make_unique<memgraph::query::v2::SimulatedRequestRouterFactory>(simulator, coordinator_address); + auto rr_factory = std::make_unique<memgraph::query::v2::SimulatedRequestRouterFactory>(simulator); auto interpreter_context = std::make_unique<memgraph::query::v2::InterpreterContext>( - nullptr + nullptr, memgraph::query::v2::InterpreterConfig{.query = {.allow_load_csv = true}, .execution_timeout_sec = 600, .replication_replica_check_frequency = std::chrono::seconds(1), From 32231fe49a7707bb61db4cf745f83fe0cc4b1862 Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Thu, 15 Dec 2022 17:10:27 +0100 Subject: [PATCH 10/85] Move the implementation of AllocateInitialEdgeIds into the child class --- src/query/v2/request_router.hpp | 4 +--- tests/unit/query_v2_expression_evaluator.cpp | 4 ++++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/query/v2/request_router.hpp b/src/query/v2/request_router.hpp index 74117f52e..aeaf5740a 100644 --- a/src/query/v2/request_router.hpp +++ b/src/query/v2/request_router.hpp @@ -128,9 +128,7 @@ class RequestRouterInterface { virtual bool IsPrimaryLabel(storage::v3::LabelId label) const = 0; virtual bool IsPrimaryKey(storage::v3::LabelId primary_label, storage::v3::PropertyId property) const = 0; - virtual std::optional<std::pair<uint64_t, uint64_t>> AllocateInitialEdgeIds(io::Address coordinator_address) { - return {}; - } + virtual std::optional<std::pair<uint64_t, uint64_t>> AllocateInitialEdgeIds(io::Address coordinator_address) = 0; }; // TODO(kostasrim)rename this class template diff --git a/tests/unit/query_v2_expression_evaluator.cpp b/tests/unit/query_v2_expression_evaluator.cpp index 5f77ed4e7..393952423 100644 --- a/tests/unit/query_v2_expression_evaluator.cpp +++ b/tests/unit/query_v2_expression_evaluator.cpp @@ -120,6 +120,10 @@ class MockedRequestRouter : public RequestRouterInterface { bool IsPrimaryKey(LabelId primary_label, PropertyId property) const override { return true; } + std::optional<std::pair<uint64_t, uint64_t>> AllocateInitialEdgeIds(io::Address coordinator_address) override { + return {}; + } + private: void SetUpNameIdMappers() { std::unordered_map<uint64_t, std::string> id_to_name; From 68175bc97cf66fda671569f25880ed35f85e16c5 Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Thu, 15 Dec 2022 15:20:32 +0100 Subject: [PATCH 11/85] Init basic cursor class Conform clang-tidy and modify PullMultiple behavior --- src/query/v2/multiframe.hpp | 8 +- src/query/v2/plan/operator.cpp | 167 ++++++++++++++++++++- src/query/v2/plan/pretty_print.hpp | 6 +- src/query/v2/plan/rewrite/index_lookup.hpp | 4 +- src/query/v2/request_router.hpp | 4 +- src/utils/event_counter.cpp | 1 + tests/unit/query_v2_plan.cpp | 24 ++- 7 files changed, 185 insertions(+), 29 deletions(-) diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index 8588993a7..49dad940a 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -200,9 +200,9 @@ class ValidFramesConsumer { explicit ValidFramesConsumer(MultiFrame &multiframe); ~ValidFramesConsumer() noexcept; - ValidFramesConsumer(const ValidFramesConsumer &other) = delete; - ValidFramesConsumer(ValidFramesConsumer &&other) noexcept = delete; - ValidFramesConsumer &operator=(const ValidFramesConsumer &other) = delete; + ValidFramesConsumer(const ValidFramesConsumer &other) = default; + ValidFramesConsumer(ValidFramesConsumer &&other) noexcept = default; + ValidFramesConsumer &operator=(const ValidFramesConsumer &other) = default; ValidFramesConsumer &operator=(ValidFramesConsumer &&other) noexcept = delete; struct Iterator { diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index 7b8cfce07..02abdc3c4 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -462,6 +462,162 @@ class DistributedScanAllAndFilterCursor : public Cursor { std::optional<std::vector<Expression *>> filter_expressions_; }; +class DistributedScanAllByPrimaryKeyCursor : public Cursor { + public: + explicit DistributedScanAllByPrimaryKeyCursor( + Symbol output_symbol, UniqueCursorPtr input_cursor, const char *op_name, + std::optional<storage::v3::LabelId> label, + std::optional<std::pair<storage::v3::PropertyId, Expression *>> property_expression_pair, + std::optional<std::vector<Expression *>> filter_expressions) + : output_symbol_(output_symbol), + input_cursor_(std::move(input_cursor)), + op_name_(op_name), + label_(label), + property_expression_pair_(property_expression_pair), + filter_expressions_(filter_expressions) { + ResetExecutionState(); + } + + enum class State : int8_t { INITIALIZING, COMPLETED }; + + using VertexAccessor = accessors::VertexAccessor; + + bool MakeRequest(RequestRouterInterface &request_router, ExecutionContext &context) { + { + SCOPED_REQUEST_WAIT_PROFILE; + std::optional<std::string> request_label = std::nullopt; + if (label_.has_value()) { + request_label = request_router.LabelToName(*label_); + } + current_batch_ = request_router.ScanVertices(request_label); + } + current_vertex_it_ = current_batch_.begin(); + request_state_ = State::COMPLETED; + return !current_batch_.empty(); + } + + bool Pull(Frame &frame, ExecutionContext &context) override { + SCOPED_PROFILE_OP(op_name_); + + auto &request_router = *context.request_router; + while (true) { + if (MustAbort(context)) { + throw HintedAbortError(); + } + + if (request_state_ == State::INITIALIZING) { + if (!input_cursor_->Pull(frame, context)) { + return false; + } + } + + if (current_vertex_it_ == current_batch_.end() && + (request_state_ == State::COMPLETED || !MakeRequest(request_router, context))) { + ResetExecutionState(); + continue; + } + + frame[output_symbol_] = TypedValue(std::move(*current_vertex_it_)); + ++current_vertex_it_; + return true; + } + } + + void PrepareNextFrames(ExecutionContext &context) { + auto &request_router = *context.request_router; + + input_cursor_->PullMultiple(*own_multi_frames_, context); + valid_frames_consumer_ = own_multi_frames_->GetValidFramesConsumer(); + valid_frames_it_ = valid_frames_consumer_->begin(); + + MakeRequest(request_router, context); + } + + inline bool HasNextFrame() { + return current_vertex_it_ != current_batch_.end() && valid_frames_it_ != valid_frames_consumer_->end(); + } + + FrameWithValidity GetNextFrame(ExecutionContext &context) { + MG_ASSERT(HasNextFrame()); + + auto frame = *valid_frames_it_; + frame[output_symbol_] = TypedValue(*current_vertex_it_); + + ++current_vertex_it_; + if (current_vertex_it_ == current_batch_.end()) { + valid_frames_it_->MakeInvalid(); + ++valid_frames_it_; + + if (valid_frames_it_ == valid_frames_consumer_->end()) { + PrepareNextFrames(context); + } else { + current_vertex_it_ = current_batch_.begin(); + } + }; + + return frame; + } + + void PullMultiple(MultiFrame &input_multi_frame, ExecutionContext &context) override { + SCOPED_PROFILE_OP(op_name_); + + if (!own_multi_frames_.has_value()) { + // NOLINTNEXTLINE(bugprone-narrowing-conversions) + own_multi_frames_.emplace(MultiFrame(input_multi_frame.GetFirstFrame().elems().size(), + kNumberOfFramesInMultiframe, input_multi_frame.GetMemoryResource())); + PrepareNextFrames(context); + } + + while (true) { + if (MustAbort(context)) { + throw HintedAbortError(); + } + + auto invalid_frames_populator = input_multi_frame.GetInvalidFramesPopulator(); + auto invalid_frame_it = invalid_frames_populator.begin(); + auto has_modified_at_least_one_frame = false; + + while (invalid_frames_populator.end() != invalid_frame_it && HasNextFrame()) { + has_modified_at_least_one_frame = true; + *invalid_frame_it = GetNextFrame(context); + ++invalid_frame_it; + } + + if (!has_modified_at_least_one_frame) { + return; + } + } + }; + + void Shutdown() override { input_cursor_->Shutdown(); } + + void ResetExecutionState() { + current_batch_.clear(); + current_vertex_it_ = current_batch_.end(); + request_state_ = State::INITIALIZING; + } + + void Reset() override { + input_cursor_->Reset(); + ResetExecutionState(); + } + + private: + const Symbol output_symbol_; + const UniqueCursorPtr input_cursor_; + const char *op_name_; + std::vector<VertexAccessor> current_batch_; + std::vector<VertexAccessor>::iterator current_vertex_it_; + State request_state_ = State::INITIALIZING; + std::optional<storage::v3::LabelId> label_; + std::optional<std::pair<storage::v3::PropertyId, Expression *>> property_expression_pair_; + std::optional<std::vector<Expression *>> filter_expressions_; + std::optional<MultiFrame> own_multi_frames_; + std::optional<ValidFramesConsumer> valid_frames_consumer_; + ValidFramesConsumer::Iterator valid_frames_it_; + std::queue<FrameWithValidity> frames_buffer_; +}; + ScanAll::ScanAll(const std::shared_ptr<LogicalOperator> &input, Symbol output_symbol, storage::v3::View view) : input_(input ? input : std::make_shared<Once>()), output_symbol_(output_symbol), view_(view) {} @@ -567,8 +723,13 @@ ScanAllByPrimaryKey::ScanAllByPrimaryKey(const std::shared_ptr<LogicalOperator> ACCEPT_WITH_INPUT(ScanAllByPrimaryKey) -UniqueCursorPtr ScanAllByPrimaryKey::MakeCursor(utils::MemoryResource * /*mem*/) const { - // EventCounter::IncrementCounter(EventCounter::ScanAllByPrimaryKeyOperator); +UniqueCursorPtr ScanAllByPrimaryKey::MakeCursor(utils::MemoryResource *mem) const { + EventCounter::IncrementCounter(EventCounter::ScanAllByPrimaryKeyOperator); + + return MakeUniqueCursorPtr<DistributedScanAllByPrimaryKeyCursor>( + mem, output_symbol_, input_->MakeCursor(mem), "ScanAll", std::nullopt /*label*/, + std::nullopt /*property_expression_pair*/, std::nullopt /*filter_expressions*/); + throw QueryRuntimeException("ScanAllByPrimaryKey cursur is yet to be implemented."); } diff --git a/src/query/v2/plan/pretty_print.hpp b/src/query/v2/plan/pretty_print.hpp index 20fde180b..266b16102 100644 --- a/src/query/v2/plan/pretty_print.hpp +++ b/src/query/v2/plan/pretty_print.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -67,7 +67,7 @@ class PlanPrinter : public virtual HierarchicalLogicalOperatorVisitor { bool PreVisit(ScanAllByLabelPropertyValue &) override; bool PreVisit(ScanAllByLabelPropertyRange &) override; bool PreVisit(ScanAllByLabelProperty &) override; - bool PreVisit(query::v2::plan::ScanAllByPrimaryKey &) override; + bool PreVisit(ScanAllByPrimaryKey & /*unused*/) override; bool PreVisit(Expand &) override; bool PreVisit(ExpandVariable &) override; @@ -194,7 +194,7 @@ class PlanToJsonVisitor : public virtual HierarchicalLogicalOperatorVisitor { bool PreVisit(ScanAllByLabelPropertyRange &) override; bool PreVisit(ScanAllByLabelPropertyValue &) override; bool PreVisit(ScanAllByLabelProperty &) override; - bool PreVisit(ScanAllByPrimaryKey &) override; + bool PreVisit(ScanAllByPrimaryKey & /*unused*/) override; bool PreVisit(Produce &) override; bool PreVisit(Accumulate &) override; diff --git a/src/query/v2/plan/rewrite/index_lookup.hpp b/src/query/v2/plan/rewrite/index_lookup.hpp index 1a4a4160c..e134dd49b 100644 --- a/src/query/v2/plan/rewrite/index_lookup.hpp +++ b/src/query/v2/plan/rewrite/index_lookup.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -278,7 +278,7 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor { return true; } - bool PostVisit(ScanAllByPrimaryKey &) override { + bool PostVisit(ScanAllByPrimaryKey & /*unused*/) override { prev_ops_.pop_back(); return true; } diff --git a/src/query/v2/request_router.hpp b/src/query/v2/request_router.hpp index b5878ff92..ca8d759f1 100644 --- a/src/query/v2/request_router.hpp +++ b/src/query/v2/request_router.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -115,7 +115,7 @@ class RequestRouterInterface { virtual bool IsPrimaryLabel(storage::v3::LabelId label) const = 0; virtual bool IsPrimaryKey(storage::v3::LabelId primary_label, storage::v3::PropertyId property) const = 0; // TODO - (gvolfing) Implement this function in the mocked class. - virtual std::vector<coordinator::SchemaProperty> GetSchemaForLabel(storage::v3::LabelId label) const { + virtual std::vector<coordinator::SchemaProperty> GetSchemaForLabel(storage::v3::LabelId /*label*/) const { return std::vector<coordinator::SchemaProperty>{}; }; }; diff --git a/src/utils/event_counter.cpp b/src/utils/event_counter.cpp index 634b6eae2..8f7475ffb 100644 --- a/src/utils/event_counter.cpp +++ b/src/utils/event_counter.cpp @@ -25,6 +25,7 @@ M(ScanAllByLabelPropertyValueOperator, "Number of times ScanAllByLabelPropertyValue operator was used.") \ M(ScanAllByLabelPropertyOperator, "Number of times ScanAllByLabelProperty operator was used.") \ M(ScanAllByIdOperator, "Number of times ScanAllById operator was used.") \ + M(ScanAllByPrimaryKeyOperator, "Number of times ScanAllByPrimaryKey operator was used.") \ M(ExpandOperator, "Number of times Expand operator was used.") \ M(ExpandVariableOperator, "Number of times ExpandVariable operator was used.") \ M(ConstructNamedPathOperator, "Number of times ConstructNamedPath operator was used.") \ diff --git a/tests/unit/query_v2_plan.cpp b/tests/unit/query_v2_plan.cpp index 2eb8295db..54d091df7 100644 --- a/tests/unit/query_v2_plan.cpp +++ b/tests/unit/query_v2_plan.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -86,11 +86,12 @@ class TestPlanner : public ::testing::Test {}; using PlannerTypes = ::testing::Types<Planner>; -void DeleteListContent(std::list<BaseOpChecker *> *list) { - for (BaseOpChecker *ptr : *list) { - delete ptr; - } -} +// void DeleteListContent(std::list<BaseOpChecker *> *list) { +// for (BaseOpChecker *ptr : *list) { +// delete ptr; +// } +// } + TYPED_TEST_CASE(TestPlanner, PlannerTypes); TYPED_TEST(TestPlanner, MatchFilterPropIsNotNull) { @@ -171,8 +172,8 @@ TYPED_TEST(TestPlanner, MatchFilterPropIsNotNull) { dba.SetIndexCount(label, sec_prop_three.second, 1); memgraph::query::v2::AstStorage storage; - memgraph::query::v2::Expression *expected_primary_key; - expected_primary_key = PROPERTY_LOOKUP("n", prim_prop_one); + // memgraph::query::v2::Expression *expected_primary_key; + // expected_primary_key = PROPERTY_LOOKUP("n", prim_prop_one); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", prim_label_name))), WHERE(EQ(PROPERTY_LOOKUP("n", prim_prop_one), LITERAL(1))), RETURN("n"))); auto symbol_table = (memgraph::expr::MakeSymbolTable(query)); @@ -183,12 +184,6 @@ TYPED_TEST(TestPlanner, MatchFilterPropIsNotNull) { } /* - - - - - - TYPED_TEST(TestPlanner, MatchNodeReturn) { // Test MATCH (n) RETURN n AstStorage storage; @@ -1705,5 +1700,4 @@ TYPED_TEST(TestPlanner, Foreach) { } } */ - } // namespace From 599b133a55ba56bd9725d9462b428758bbb582f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Thu, 12 Jan 2023 09:04:18 +0100 Subject: [PATCH 12/85] Fix edge direction when creating edges --- src/query/v2/plan/operator.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index 805b3ec51..8681c6a2a 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -2503,8 +2503,20 @@ class DistributedCreateExpandCursor : public Cursor { // Set src and dest vertices // TODO(jbajic) Currently we are only handling scenario where vertices // are matched - request.src_vertex = v1.Id(); - request.dest_vertex = v2.Id(); + switch (edge_info.direction) { + case EdgeAtom::Direction::IN: { + request.src_vertex = v2.Id(); + request.dest_vertex = v1.Id(); + break; + } + case EdgeAtom::Direction::OUT: { + request.src_vertex = v1.Id(); + request.dest_vertex = v2.Id(); + break; + } + case EdgeAtom::Direction::BOTH: + LOG_FATAL("Must indicate exact expansion direction here"); + } edge_requests.push_back(std::move(request)); } From d7bd2cc7549b428ab7b850115d6d1ed3301cc219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Thu, 12 Jan 2023 09:05:29 +0100 Subject: [PATCH 13/85] Eliminate copying expands --- src/query/v2/request_router.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/query/v2/request_router.hpp b/src/query/v2/request_router.hpp index 3dd2f164b..96d51b05f 100644 --- a/src/query/v2/request_router.hpp +++ b/src/query/v2/request_router.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -299,7 +299,8 @@ class RequestRouter : public RequestRouterInterface { MG_ASSERT(!new_edges.empty()); // create requests - std::vector<ShardRequestState<msgs::CreateExpandRequest>> requests_to_be_sent = RequestsForCreateExpand(new_edges); + std::vector<ShardRequestState<msgs::CreateExpandRequest>> requests_to_be_sent = + RequestsForCreateExpand(std::move(new_edges)); // begin all requests in parallel RunningRequests<msgs::CreateExpandRequest> running_requests = {}; @@ -430,7 +431,7 @@ class RequestRouter : public RequestRouterInterface { } std::vector<ShardRequestState<msgs::CreateExpandRequest>> RequestsForCreateExpand( - const std::vector<msgs::NewExpand> &new_expands) { + std::vector<msgs::NewExpand> new_expands) { std::map<ShardMetadata, msgs::CreateExpandRequest> per_shard_request_table; auto ensure_shard_exists_in_table = [&per_shard_request_table, transaction_id = transaction_id_](const ShardMetadata &shard) { From 41bb988fe9e70a70dac418b16303866f8c4b6b81 Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Thu, 12 Jan 2023 14:14:59 +0100 Subject: [PATCH 14/85] Fix failing benchmark tests and implement cursor The benchmarking tests were failing because of the incorrect implementation of the ScanAllByPrimaryKeyCursor. The previous implementation caused the currently allocateable 1m edgeids to run out very quickly, causing the the tests to freeze. --- src/query/v2/context.hpp | 2 +- src/query/v2/interpreter.cpp | 2 +- src/query/v2/plan/operator.cpp | 40 ++++++++++++++++--- src/query/v2/request_router.hpp | 37 +++++++++++++++-- tests/mgbench/client.cpp | 2 +- tests/simulation/request_router.cpp | 4 +- tests/simulation/test_cluster.hpp | 4 +- tests/unit/high_density_shard_create_scan.cpp | 4 +- tests/unit/machine_manager.cpp | 4 +- tests/unit/query_v2_expression_evaluator.cpp | 7 +++- 10 files changed, 84 insertions(+), 22 deletions(-) diff --git a/src/query/v2/context.hpp b/src/query/v2/context.hpp index cb30a9ced..58f5ada97 100644 --- a/src/query/v2/context.hpp +++ b/src/query/v2/context.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 diff --git a/src/query/v2/interpreter.cpp b/src/query/v2/interpreter.cpp index fde11ac00..9a4bfde90 100644 --- a/src/query/v2/interpreter.cpp +++ b/src/query/v2/interpreter.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index 02abdc3c4..b7841bf16 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -34,6 +34,7 @@ #include "query/v2/bindings/eval.hpp" #include "query/v2/bindings/symbol_table.hpp" #include "query/v2/context.hpp" +#include "query/v2/conversions.hpp" #include "query/v2/db_accessor.hpp" #include "query/v2/exceptions.hpp" #include "query/v2/frontend/ast/ast.hpp" @@ -403,7 +404,7 @@ class DistributedScanAllAndFilterCursor : public Cursor { if (label_.has_value()) { request_label = request_router.LabelToName(*label_); } - current_batch = request_router.ScanVertices(request_label); + current_batch = request_router.ScanVertices(request_label, std::nullopt); } current_vertex_it = current_batch.begin(); request_state_ = State::COMPLETED; @@ -468,13 +469,14 @@ class DistributedScanAllByPrimaryKeyCursor : public Cursor { Symbol output_symbol, UniqueCursorPtr input_cursor, const char *op_name, std::optional<storage::v3::LabelId> label, std::optional<std::pair<storage::v3::PropertyId, Expression *>> property_expression_pair, - std::optional<std::vector<Expression *>> filter_expressions) + std::optional<std::vector<Expression *>> filter_expressions, std::optional<std::vector<Expression *>> primary_key) : output_symbol_(output_symbol), input_cursor_(std::move(input_cursor)), op_name_(op_name), label_(label), property_expression_pair_(property_expression_pair), - filter_expressions_(filter_expressions) { + filter_expressions_(filter_expressions), + primary_key_(primary_key) { ResetExecutionState(); } @@ -489,7 +491,32 @@ class DistributedScanAllByPrimaryKeyCursor : public Cursor { if (label_.has_value()) { request_label = request_router.LabelToName(*label_); } - current_batch_ = request_router.ScanVertices(request_label); + current_batch_ = request_router.ScanVertices(request_label, std::nullopt); + } + current_vertex_it_ = current_batch_.begin(); + request_state_ = State::COMPLETED; + return !current_batch_.empty(); + } + + bool MakeRequestSingleFrame(Frame &frame, RequestRouterInterface &request_router, ExecutionContext &context) { + { + SCOPED_REQUEST_WAIT_PROFILE; + std::optional<std::string> request_label = std::nullopt; + if (label_.has_value()) { + request_label = request_router.LabelToName(*label_); + } + + // Evaluate the expressions that hold the PrimaryKey. + ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.request_router, + storage::v3::View::OLD); + + std::vector<msgs::Value> pk; + MG_ASSERT(primary_key_); + for (auto primary_key : *primary_key_) { + pk.push_back(TypedValueToValue(primary_key->Accept(evaluator))); + } + + current_batch_ = request_router.ScanVertices(request_label, pk); } current_vertex_it_ = current_batch_.begin(); request_state_ = State::COMPLETED; @@ -612,6 +639,7 @@ class DistributedScanAllByPrimaryKeyCursor : public Cursor { std::optional<storage::v3::LabelId> label_; std::optional<std::pair<storage::v3::PropertyId, Expression *>> property_expression_pair_; std::optional<std::vector<Expression *>> filter_expressions_; + std::optional<std::vector<Expression *>> primary_key_; std::optional<MultiFrame> own_multi_frames_; std::optional<ValidFramesConsumer> valid_frames_consumer_; ValidFramesConsumer::Iterator valid_frames_it_; @@ -727,8 +755,8 @@ UniqueCursorPtr ScanAllByPrimaryKey::MakeCursor(utils::MemoryResource *mem) cons EventCounter::IncrementCounter(EventCounter::ScanAllByPrimaryKeyOperator); return MakeUniqueCursorPtr<DistributedScanAllByPrimaryKeyCursor>( - mem, output_symbol_, input_->MakeCursor(mem), "ScanAll", std::nullopt /*label*/, - std::nullopt /*property_expression_pair*/, std::nullopt /*filter_expressions*/); + mem, output_symbol_, input_->MakeCursor(mem), "ScanAll", label_, std::nullopt /*property_expression_pair*/, + std::nullopt /*filter_expressions*/, primary_key_); throw QueryRuntimeException("ScanAllByPrimaryKey cursur is yet to be implemented."); } diff --git a/src/query/v2/request_router.hpp b/src/query/v2/request_router.hpp index ca8d759f1..934190cb4 100644 --- a/src/query/v2/request_router.hpp +++ b/src/query/v2/request_router.hpp @@ -97,7 +97,8 @@ class RequestRouterInterface { virtual void StartTransaction() = 0; virtual void Commit() = 0; - virtual std::vector<VertexAccessor> ScanVertices(std::optional<std::string> label) = 0; + virtual std::vector<VertexAccessor> ScanVertices(std::optional<std::string> label, + std::optional<std::vector<msgs::Value>> primary_key) = 0; virtual std::vector<msgs::CreateVerticesResponse> CreateVertices(std::vector<msgs::NewVertex> new_vertices) = 0; virtual std::vector<msgs::ExpandOneResultRow> ExpandOne(msgs::ExpandOneRequest request) = 0; virtual std::vector<msgs::CreateExpandResponse> CreateExpand(std::vector<msgs::NewExpand> new_edges) = 0; @@ -243,9 +244,16 @@ class RequestRouter : public RequestRouterInterface { bool IsPrimaryLabel(storage::v3::LabelId label) const override { return shards_map_.label_spaces.contains(label); } // TODO(kostasrim) Simplify return result - std::vector<VertexAccessor> ScanVertices(std::optional<std::string> label) override { + std::vector<VertexAccessor> ScanVertices(std::optional<std::string> label, + std::optional<std::vector<msgs::Value>> primary_key) override { // create requests - std::vector<ShardRequestState<msgs::ScanVerticesRequest>> requests_to_be_sent = RequestsForScanVertices(label); + std::vector<ShardRequestState<msgs::ScanVerticesRequest>> requests_to_be_sent; + if (primary_key) { + requests_to_be_sent = RequestsForScanVertexByPrimaryKey(label, *primary_key); + } else { + requests_to_be_sent = RequestsForScanVertices(label); + } + spdlog::trace("created {} ScanVertices requests", requests_to_be_sent.size()); // begin all requests in parallel @@ -510,6 +518,29 @@ class RequestRouter : public RequestRouterInterface { return requests; } + std::vector<ShardRequestState<msgs::ScanVerticesRequest>> RequestsForScanVertexByPrimaryKey( + const std::optional<std::string> &label, const std::vector<msgs::Value> &primary_key) { + const auto label_id = shards_map_.GetLabelId(*label); + MG_ASSERT(label_id); + MG_ASSERT(IsPrimaryLabel(*label_id)); + std::vector<ShardRequestState<msgs::ScanVerticesRequest>> requests = {}; + + auto pk_containing_shard = + shards_map_.GetShardForKey(*label, storage::conversions::ConvertPropertyVector(primary_key)); + + msgs::ScanVerticesRequest request; + request.transaction_id = transaction_id_; + request.batch_limit = 1; + request.start_id.second = primary_key; + + ShardRequestState<msgs::ScanVerticesRequest> shard_request_state{ + .shard = pk_containing_shard, + .request = std::move(request), + }; + requests.emplace_back(std::move(shard_request_state)); + return requests; + } + std::vector<ShardRequestState<msgs::ExpandOneRequest>> RequestsForExpandOne(const msgs::ExpandOneRequest &request) { std::map<ShardMetadata, msgs::ExpandOneRequest> per_shard_request_table; msgs::ExpandOneRequest top_level_rqst_template = request; diff --git a/tests/mgbench/client.cpp b/tests/mgbench/client.cpp index e4b63d477..000c199fa 100644 --- a/tests/mgbench/client.cpp +++ b/tests/mgbench/client.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 diff --git a/tests/simulation/request_router.cpp b/tests/simulation/request_router.cpp index 4248e7876..10976baa9 100644 --- a/tests/simulation/request_router.cpp +++ b/tests/simulation/request_router.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -154,7 +154,7 @@ void RunStorageRaft(Raft<IoImpl, MockedShardRsm, WriteRequests, WriteResponses, } void TestScanVertices(query::v2::RequestRouterInterface &request_router) { - auto result = request_router.ScanVertices("test_label"); + auto result = request_router.ScanVertices("test_label", std::nullopt); MG_ASSERT(result.size() == 2); { auto prop = result[0].GetProperty(msgs::PropertyId::FromUint(0)); diff --git a/tests/simulation/test_cluster.hpp b/tests/simulation/test_cluster.hpp index 2e8bdf92f..699d9f8c7 100644 --- a/tests/simulation/test_cluster.hpp +++ b/tests/simulation/test_cluster.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -182,7 +182,7 @@ void ExecuteOp(query::v2::RequestRouter<SimulatorTransport> &request_router, std void ExecuteOp(query::v2::RequestRouter<SimulatorTransport> &request_router, std::set<CompoundKey> &correctness_model, ScanAll scan_all) { - auto results = request_router.ScanVertices("test_label"); + auto results = request_router.ScanVertices("test_label", std::nullopt); RC_ASSERT(results.size() == correctness_model.size()); diff --git a/tests/unit/high_density_shard_create_scan.cpp b/tests/unit/high_density_shard_create_scan.cpp index 9fabf6ccc..a9647c795 100644 --- a/tests/unit/high_density_shard_create_scan.cpp +++ b/tests/unit/high_density_shard_create_scan.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -192,7 +192,7 @@ void ExecuteOp(query::v2::RequestRouter<LocalTransport> &request_router, std::se void ExecuteOp(query::v2::RequestRouter<LocalTransport> &request_router, std::set<CompoundKey> &correctness_model, ScanAll scan_all) { - auto results = request_router.ScanVertices("test_label"); + auto results = request_router.ScanVertices("test_label", std::nullopt); spdlog::error("got {} results, model size is {}", results.size(), correctness_model.size()); EXPECT_EQ(results.size(), correctness_model.size()); diff --git a/tests/unit/machine_manager.cpp b/tests/unit/machine_manager.cpp index 74b7d3863..8d0670e81 100644 --- a/tests/unit/machine_manager.cpp +++ b/tests/unit/machine_manager.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -111,7 +111,7 @@ ShardMap TestShardMap() { template <typename RequestRouter> void TestScanAll(RequestRouter &request_router) { - auto result = request_router.ScanVertices(kLabelName); + auto result = request_router.ScanVertices(kLabelName, std::nullopt); EXPECT_EQ(result.size(), 2); } diff --git a/tests/unit/query_v2_expression_evaluator.cpp b/tests/unit/query_v2_expression_evaluator.cpp index 5e91d0d5a..fb875a0f2 100644 --- a/tests/unit/query_v2_expression_evaluator.cpp +++ b/tests/unit/query_v2_expression_evaluator.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -84,7 +84,10 @@ class MockedRequestRouter : public RequestRouterInterface { } void StartTransaction() override {} void Commit() override {} - std::vector<VertexAccessor> ScanVertices(std::optional<std::string> /* label */) override { return {}; } + std::vector<VertexAccessor> ScanVertices(std::optional<std::string> /* label */, + std::optional<std::vector<msgs::Value>> /*primary_key*/) override { + return {}; + } std::vector<CreateVerticesResponse> CreateVertices( std::vector<memgraph::msgs::NewVertex> /* new_vertices */) override { From afde0c69265ff64373c60940edaa6bc5f9fca7d4 Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Thu, 12 Jan 2023 15:45:14 +0100 Subject: [PATCH 15/85] Remove outcommented code, conform clang-tidy --- src/query/v2/plan/operator.cpp | 10 +- src/query/v2/plan/operator.lcp | 1 - tests/unit/query_common.hpp | 7 +- tests/unit/query_plan_checker.hpp | 6 +- tests/unit/query_plan_checker_v2.hpp | 166 +-- tests/unit/query_v2_plan.cpp | 1519 -------------------------- 6 files changed, 7 insertions(+), 1702 deletions(-) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index b7841bf16..b545b742f 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -512,7 +512,7 @@ class DistributedScanAllByPrimaryKeyCursor : public Cursor { std::vector<msgs::Value> pk; MG_ASSERT(primary_key_); - for (auto primary_key : *primary_key_) { + for (auto *primary_key : *primary_key_) { pk.push_back(TypedValueToValue(primary_key->Accept(evaluator))); } @@ -589,7 +589,7 @@ class DistributedScanAllByPrimaryKeyCursor : public Cursor { SCOPED_PROFILE_OP(op_name_); if (!own_multi_frames_.has_value()) { - // NOLINTNEXTLINE(bugprone-narrowing-conversions) + // NOLINTNEXTLINE(cppcoreguidelines-narrowing-conversions) own_multi_frames_.emplace(MultiFrame(input_multi_frame.GetFirstFrame().elems().size(), kNumberOfFramesInMultiframe, input_multi_frame.GetMemoryResource())); PrepareNextFrames(context); @@ -755,10 +755,8 @@ UniqueCursorPtr ScanAllByPrimaryKey::MakeCursor(utils::MemoryResource *mem) cons EventCounter::IncrementCounter(EventCounter::ScanAllByPrimaryKeyOperator); return MakeUniqueCursorPtr<DistributedScanAllByPrimaryKeyCursor>( - mem, output_symbol_, input_->MakeCursor(mem), "ScanAll", label_, std::nullopt /*property_expression_pair*/, - std::nullopt /*filter_expressions*/, primary_key_); - - throw QueryRuntimeException("ScanAllByPrimaryKey cursur is yet to be implemented."); + mem, output_symbol_, input_->MakeCursor(mem), "ScanAllByPrimaryKey", label_, + std::nullopt /*property_expression_pair*/, std::nullopt /*filter_expressions*/, primary_key_); } Expand::Expand(const std::shared_ptr<LogicalOperator> &input, Symbol input_symbol, Symbol node_symbol, diff --git a/src/query/v2/plan/operator.lcp b/src/query/v2/plan/operator.lcp index f53425606..bc8b64e5d 100644 --- a/src/query/v2/plan/operator.lcp +++ b/src/query/v2/plan/operator.lcp @@ -868,7 +868,6 @@ given label and property. (:serialize (:slk)) (:clone)) - (lcp:define-struct expand-common () ( ;; info on what's getting expanded diff --git a/tests/unit/query_common.hpp b/tests/unit/query_common.hpp index f7fe6e805..d497b76ba 100644 --- a/tests/unit/query_common.hpp +++ b/tests/unit/query_common.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -46,8 +46,6 @@ #include "storage/v2/id_types.hpp" #include "utils/string.hpp" -#include "query/v2/frontend/ast/ast.hpp" - namespace memgraph::query { namespace test_common { @@ -83,15 +81,12 @@ std::string ToString(NamedExpression *expr) { struct OrderBy { std::vector<SortItem> expressions; }; - struct Skip { Expression *expression = nullptr; }; - struct Limit { Expression *expression = nullptr; }; - struct OnMatch { std::vector<Clause *> set; }; diff --git a/tests/unit/query_plan_checker.hpp b/tests/unit/query_plan_checker.hpp index d04264c24..12ea2a683 100644 --- a/tests/unit/query_plan_checker.hpp +++ b/tests/unit/query_plan_checker.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -344,10 +344,6 @@ class ExpectScanAllByPrimaryKey : public OpChecker<v2::plan::ScanAllByPrimaryKey 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_) { diff --git a/tests/unit/query_plan_checker_v2.hpp b/tests/unit/query_plan_checker_v2.hpp index 1ad1e712a..85c625d59 100644 --- a/tests/unit/query_plan_checker_v2.hpp +++ b/tests/unit/query_plan_checker_v2.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -133,14 +133,11 @@ class OpChecker : public BaseOpChecker { virtual void ExpectOp(TOp &, const SymbolTable &) {} }; -// using ExpectScanAllByPrimaryKey = OpChecker<ScanAllByPrimaryKey>; - using ExpectCreateNode = OpChecker<CreateNode>; using ExpectCreateExpand = OpChecker<CreateExpand>; using ExpectDelete = OpChecker<Delete>; using ExpectScanAll = OpChecker<ScanAll>; using ExpectScanAllByLabel = OpChecker<ScanAllByLabel>; -// using ExpectScanAllById = OpChecker<ScanAllById>; using ExpectExpand = OpChecker<Expand>; using ExpectFilter = OpChecker<Filter>; using ExpectConstructNamedPath = OpChecker<ConstructNamedPath>; @@ -157,120 +154,6 @@ using ExpectOrderBy = OpChecker<OrderBy>; using ExpectUnwind = OpChecker<Unwind>; using ExpectDistinct = OpChecker<Distinct>; -// class ExpectForeach : public OpChecker<Foreach> { -// public: -// ExpectForeach(const std::list<BaseOpChecker *> &input, const std::list<BaseOpChecker *> &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<BaseOpChecker *> input_; -// std::list<BaseOpChecker *> updates_; -// }; - -// class ExpectExpandVariable : public OpChecker<ExpandVariable> { -// public: -// void ExpectOp(ExpandVariable &op, const SymbolTable &) override { -// EXPECT_EQ(op.type_, memgraph::query::EdgeAtom::Type::DEPTH_FIRST); -// } -// }; - -// class ExpectExpandBfs : public OpChecker<ExpandVariable> { -// public: -// void ExpectOp(ExpandVariable &op, const SymbolTable &) override { -// EXPECT_EQ(op.type_, memgraph::query::EdgeAtom::Type::BREADTH_FIRST); -// } -// }; - -// class ExpectAccumulate : public OpChecker<Accumulate> { -// public: -// explicit ExpectAccumulate(const std::unordered_set<Symbol> &symbols) : symbols_(symbols) {} - -// void ExpectOp(Accumulate &op, const SymbolTable &) override { -// std::unordered_set<Symbol> got_symbols(op.symbols_.begin(), op.symbols_.end()); -// EXPECT_EQ(symbols_, got_symbols); -// } - -// private: -// const std::unordered_set<Symbol> symbols_; -// }; - -// class ExpectAggregate : public OpChecker<Aggregate> { -// public: -// ExpectAggregate(const std::vector<memgraph::query::Aggregation *> &aggregations, -// const std::unordered_set<memgraph::query::Expression *> &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<size_t> got_group_by; -// for (auto *expr : op.group_by_) got_group_by.insert(typeid(*expr).hash_code()); -// std::unordered_set<size_t> 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<memgraph::query::Aggregation *> aggregations_; -// std::unordered_set<memgraph::query::Expression *> group_by_; -// }; - -// class ExpectMerge : public OpChecker<Merge> { -// public: -// ExpectMerge(const std::list<BaseOpChecker *> &on_match, const std::list<BaseOpChecker *> &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<BaseOpChecker *> &on_match_; -// const std::list<BaseOpChecker *> &on_create_; -// }; - -// class ExpectOptional : public OpChecker<Optional> { -// public: -// explicit ExpectOptional(const std::list<BaseOpChecker *> &optional) : optional_(optional) {} - -// ExpectOptional(const std::vector<Symbol> &optional_symbols, const std::list<BaseOpChecker *> &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<Symbol> optional_symbols_; -// const std::list<BaseOpChecker *> &optional_; -// }; - class ExpectScanAllByLabelPropertyValue : public OpChecker<ScanAllByLabelPropertyValue> { public: ExpectScanAllByLabelPropertyValue(memgraph::storage::v3::LabelId label, @@ -291,53 +174,6 @@ class ExpectScanAllByLabelPropertyValue : public OpChecker<ScanAllByLabelPropert memgraph::query::v2::Expression *expression_; }; -// class ExpectScanAllByLabelPropertyRange : public OpChecker<ScanAllByLabelPropertyRange> { -// public: -// ExpectScanAllByLabelPropertyRange(memgraph::storage::LabelId label, memgraph::storage::PropertyId property, -// std::optional<ScanAllByLabelPropertyRange::Bound> lower_bound, -// std::optional<ScanAllByLabelPropertyRange::Bound> 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<ScanAllByLabelPropertyRange::Bound> lower_bound_; -// std::optional<ScanAllByLabelPropertyRange::Bound> upper_bound_; -// }; - -// class ExpectScanAllByLabelProperty : public OpChecker<ScanAllByLabelProperty> { -// public: -// ExpectScanAllByLabelProperty(memgraph::storage::LabelId label, -// const std::pair<std::string, memgraph::storage::PropertyId> &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<v2::plan::ScanAllByPrimaryKey> { public: ExpectScanAllByPrimaryKey(memgraph::storage::v3::LabelId label, const std::vector<Expression *> &properties) diff --git a/tests/unit/query_v2_plan.cpp b/tests/unit/query_v2_plan.cpp index 54d091df7..56f90a690 100644 --- a/tests/unit/query_v2_plan.cpp +++ b/tests/unit/query_v2_plan.cpp @@ -172,8 +172,6 @@ TYPED_TEST(TestPlanner, MatchFilterPropIsNotNull) { dba.SetIndexCount(label, sec_prop_three.second, 1); memgraph::query::v2::AstStorage storage; - // memgraph::query::v2::Expression *expected_primary_key; - // expected_primary_key = PROPERTY_LOOKUP("n", prim_prop_one); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", prim_label_name))), WHERE(EQ(PROPERTY_LOOKUP("n", prim_prop_one), LITERAL(1))), RETURN("n"))); auto symbol_table = (memgraph::expr::MakeSymbolTable(query)); @@ -183,1521 +181,4 @@ TYPED_TEST(TestPlanner, MatchFilterPropIsNotNull) { } } -/* -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<TypeParam>(&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<TypeParam>(&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<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<Symbol> optional_symbols{get_symbol(pattern), get_symbol(node_n), get_symbol(edge), get_symbol(node_m)}; - FakeDistributedDbAccessor dba; - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); - std::list<BaseOpChecker *> 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<TypeParam>(&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<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(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<TypeParam>(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<TypeParam>(&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<TypeParam>(&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<TypeParam>(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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(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<BaseOpChecker *> on_match{new ExpectExpand(), new ExpectSetProperty()}; - std::list<BaseOpChecker *> 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<TypeParam>(&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<BaseOpChecker *> optional{new ExpectScanAll(), new ExpectExpand(), new ExpectFilter()}; - CheckPlan<TypeParam>(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<TypeParam>(&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<TypeParam>(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<TypeParam>(&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<TypeParam>(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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(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<BaseOpChecker *> optional{new ExpectFilter(), new ExpectScanAll()}; - CheckPlan<TypeParam>(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<TypeParam>(&dba, storage, symbol_table, query); - CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectExpand(), ExpectProduce()); - std::vector<std::string> output_names; - for (const auto &output_symbol : planner.plan().OutputSymbols(symbol_table)) { - output_names.emplace_back(output_symbol.name()); - } - std::vector<std::string> 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<TypeParam>(&dba, storage, symbol_table, query); - auto *produce = dynamic_cast<Produce *>(&planner.plan()); - ASSERT_TRUE(produce); - const auto &named_expressions = produce->named_expressions_; - ASSERT_EQ(named_expressions.size(), 2); - auto *expanded_ident = dynamic_cast<memgraph::query::Identifier *>(named_expressions[0]->expression_); - ASSERT_TRUE(expanded_ident); - auto aggr = ExpectAggregate({sum}, {expanded_ident}); - CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), aggr, ExpectProduce()); - std::vector<std::string> output_names; - for (const auto &output_symbol : planner.plan().OutputSymbols(symbol_table)) { - output_names.emplace_back(output_symbol.name()); - } - std::vector<std::string> 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<BaseOpChecker *> on_match{new ExpectScanAll(), new ExpectFilter()}; - std::list<BaseOpChecker *> on_create{new ExpectCreateNode()}; - CheckPlan<TypeParam>(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<BaseOpChecker *> on_match{new ExpectScanAllByLabelPropertyValue(label, property, IDENT("i"))}; - std::list<BaseOpChecker *> on_create{new ExpectCreateNode()}; - auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); - CheckPlan(planner.plan(), symbol_table, ExpectUnwind(), ExpectMerge(on_match, on_create)); - 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<BaseOpChecker *> optional{new ExpectScanAll()}; - CheckPlan<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(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<memgraph::query::SubscriptOperator>(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<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(&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<memgraph::query::LabelsTest>( - IDENT("n"), std::vector<memgraph::query::LabelIx>{storage.GetLabelIx("label")}))), - RETURN("n"))); - auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<std::pair<memgraph::query::Expression *, Bound::Type>> 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<std::pair<memgraph::query::Expression *, Bound::Type>> 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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(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<TypeParam>(&dba, storage, symbol_table, query); - auto *root = dynamic_cast<Produce *>(&planner.plan()); - - ASSERT_TRUE(root); - - const auto &nes = root->named_expressions_; - EXPECT_TRUE(nes.size() == 4); - - std::vector<std::string> 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<TypeParam>(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<memgraph::query::EdgeAtom>(IDENT("r"), memgraph::query::EdgeAtom::Type::BREADTH_FIRST, - Direction::OUT, std::vector<memgraph::query::EdgeTypeIx>{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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(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<memgraph::query::Return>(); - 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<TypeParam>(&dba, storage, symbol_table, query); - auto *produce = dynamic_cast<Produce *>(&planner.plan()); - ASSERT_TRUE(produce); - std::vector<std::string> 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<memgraph::query::RegexMatch>(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<TypeParam>(&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<memgraph::query::RegexMatch>(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<TypeParam>(&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<memgraph::query::RegexMatch>(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<TypeParam>(&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<memgraph::query::RegexMatch>(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<TypeParam>(&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<memgraph::query::CallProcedure>(); - 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<Symbol> 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<TypeParam>(&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<memgraph::query::CallProcedure>(); - 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<Symbol> 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<TypeParam>(&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<memgraph::query::CallProcedure>(); - 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<Symbol> 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<TypeParam>(&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<TypeParam>(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<TypeParam>(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<memgraph::query::EdgeAtom>(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<TypeParam>(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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<TypeParam>(&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<BaseOpChecker *> updates{&create}; - std::list<BaseOpChecker *> input; - CheckPlan<TypeParam>(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<BaseOpChecker *> updates{&del}; - std::list<BaseOpChecker *> input; - CheckPlan<TypeParam>(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<BaseOpChecker *> updates{&set_prop}; - std::list<BaseOpChecker *> input; - CheckPlan<TypeParam>(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<BaseOpChecker *> input; - std::list<BaseOpChecker *> nested_updates{{&create, &del}}; - auto nested_foreach = ExpectForeach(input, nested_updates); - std::list<BaseOpChecker *> updates{&nested_foreach}; - CheckPlan<TypeParam>(query, storage, ExpectForeach(input, updates)); - } - { - auto *i = NEXPR("i", IDENT("i")); - auto *j = NEXPR("j", IDENT("j")); - auto create = ExpectCreateNode(); - std::list<BaseOpChecker *> empty; - std::list<BaseOpChecker *> updates{&create}; - auto input_op = ExpectForeach(empty, updates); - std::list<BaseOpChecker *> input{&input_op}; - auto *query = - QUERY(SINGLE_QUERY(FOREACH(i, {CREATE(PATTERN(NODE("n")))}), FOREACH(j, {CREATE(PATTERN(NODE("n")))}))); - CheckPlan<TypeParam>(query, storage, ExpectForeach(input, updates)); - } -} -*/ } // namespace From 3b06db5a02181d4b16495062b447f8779a403ebb Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Thu, 12 Jan 2023 16:13:27 +0100 Subject: [PATCH 16/85] Clang-tidy --- src/query/v2/plan/operator.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index b545b742f..e55859cca 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -589,9 +589,12 @@ class DistributedScanAllByPrimaryKeyCursor : public Cursor { SCOPED_PROFILE_OP(op_name_); if (!own_multi_frames_.has_value()) { - // NOLINTNEXTLINE(cppcoreguidelines-narrowing-conversions) + // NOLINTBEGIN(cppcoreguidelines-narrowing-conversions) + // NOLINTBEGIN(bugprone-narrowing-conversions) own_multi_frames_.emplace(MultiFrame(input_multi_frame.GetFirstFrame().elems().size(), kNumberOfFramesInMultiframe, input_multi_frame.GetMemoryResource())); + // NOLINTEND(bugprone-narrowing-conversions) + // NOLINTEND(cppcoreguidelines-narrowing-conversions) PrepareNextFrames(context); } From 61d84bd62253948d1dae6982d78b904267b44304 Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Thu, 12 Jan 2023 16:58:09 +0100 Subject: [PATCH 17/85] Set the type of the frame size to size_t from int64_t --- src/expr/interpret/frame.hpp | 10 +++++----- src/query/v2/multiframe.cpp | 4 ++-- src/query/v2/multiframe.hpp | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/expr/interpret/frame.hpp b/src/expr/interpret/frame.hpp index 1cd6a99ce..72bfcf245 100644 --- a/src/expr/interpret/frame.hpp +++ b/src/expr/interpret/frame.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -23,9 +23,9 @@ namespace memgraph::expr { class Frame { public: /// Create a Frame of given size backed by a utils::NewDeleteResource() - explicit Frame(int64_t size) : elems_(size, utils::NewDeleteResource()) { MG_ASSERT(size >= 0); } + explicit Frame(size_t size) : elems_(size, utils::NewDeleteResource()) { MG_ASSERT(size >= 0); } - Frame(int64_t size, utils::MemoryResource *memory) : elems_(size, memory) { MG_ASSERT(size >= 0); } + Frame(size_t size, utils::MemoryResource *memory) : elems_(size, memory) { MG_ASSERT(size >= 0); } TypedValue &operator[](const Symbol &symbol) { return elems_[symbol.position()]; } const TypedValue &operator[](const Symbol &symbol) const { return elems_[symbol.position()]; } @@ -43,9 +43,9 @@ class Frame { class FrameWithValidity final : public Frame { public: - explicit FrameWithValidity(int64_t size) : Frame(size), is_valid_(false) {} + explicit FrameWithValidity(size_t size) : Frame(size), is_valid_(false) {} - FrameWithValidity(int64_t size, utils::MemoryResource *memory) : Frame(size, memory), is_valid_(false) {} + FrameWithValidity(size_t size, utils::MemoryResource *memory) : Frame(size, memory), is_valid_(false) {} bool IsValid() const noexcept { return is_valid_; } void MakeValid() noexcept { is_valid_ = true; } diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index 02d396fda..835cdbc0f 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -24,7 +24,7 @@ static_assert(std::forward_iterator<ValidFramesModifier::Iterator>); static_assert(std::forward_iterator<ValidFramesConsumer::Iterator>); static_assert(std::forward_iterator<InvalidFramesPopulator::Iterator>); -MultiFrame::MultiFrame(int64_t size_of_frame, size_t number_of_frames, utils::MemoryResource *execution_memory) +MultiFrame::MultiFrame(size_t size_of_frame, size_t number_of_frames, utils::MemoryResource *execution_memory) : frames_(utils::pmr::vector<FrameWithValidity>( number_of_frames, FrameWithValidity(size_of_frame, execution_memory), execution_memory)) { MG_ASSERT(number_of_frames > 0); diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index 49dad940a..28c972ac4 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -30,7 +30,7 @@ class MultiFrame { friend class ValidFramesReader; friend class InvalidFramesPopulator; - MultiFrame(int64_t size_of_frame, size_t number_of_frames, utils::MemoryResource *execution_memory); + MultiFrame(size_t size_of_frame, size_t number_of_frames, utils::MemoryResource *execution_memory); ~MultiFrame() = default; MultiFrame(const MultiFrame &other); From 4c25a4dfbd8f6b902b48fc15189b61aee0e63318 Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Thu, 12 Jan 2023 20:17:35 +0100 Subject: [PATCH 18/85] Remove unnecessary comments --- src/query/v2/plan/operator.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index e55859cca..e9c8652a2 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -589,12 +589,8 @@ class DistributedScanAllByPrimaryKeyCursor : public Cursor { SCOPED_PROFILE_OP(op_name_); if (!own_multi_frames_.has_value()) { - // NOLINTBEGIN(cppcoreguidelines-narrowing-conversions) - // NOLINTBEGIN(bugprone-narrowing-conversions) own_multi_frames_.emplace(MultiFrame(input_multi_frame.GetFirstFrame().elems().size(), kNumberOfFramesInMultiframe, input_multi_frame.GetMemoryResource())); - // NOLINTEND(bugprone-narrowing-conversions) - // NOLINTEND(cppcoreguidelines-narrowing-conversions) PrepareNextFrames(context); } From d22c962af405d9aafc26320399de9782824f1ecf Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Thu, 12 Jan 2023 21:25:40 +0100 Subject: [PATCH 19/85] Turn RequestRouter into an interface again --- src/query/v2/request_router.hpp | 4 +--- tests/unit/query_v2_expression_evaluator.cpp | 4 ++++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/query/v2/request_router.hpp b/src/query/v2/request_router.hpp index 934190cb4..6004c0b88 100644 --- a/src/query/v2/request_router.hpp +++ b/src/query/v2/request_router.hpp @@ -116,9 +116,7 @@ class RequestRouterInterface { virtual bool IsPrimaryLabel(storage::v3::LabelId label) const = 0; virtual bool IsPrimaryKey(storage::v3::LabelId primary_label, storage::v3::PropertyId property) const = 0; // TODO - (gvolfing) Implement this function in the mocked class. - virtual std::vector<coordinator::SchemaProperty> GetSchemaForLabel(storage::v3::LabelId /*label*/) const { - return std::vector<coordinator::SchemaProperty>{}; - }; + virtual std::vector<coordinator::SchemaProperty> GetSchemaForLabel(storage::v3::LabelId /*label*/) const = 0; }; // TODO(kostasrim)rename this class template diff --git a/tests/unit/query_v2_expression_evaluator.cpp b/tests/unit/query_v2_expression_evaluator.cpp index fb875a0f2..098a94981 100644 --- a/tests/unit/query_v2_expression_evaluator.cpp +++ b/tests/unit/query_v2_expression_evaluator.cpp @@ -128,6 +128,10 @@ class MockedRequestRouter : public RequestRouterInterface { bool IsPrimaryKey(LabelId primary_label, PropertyId property) const override { return true; } + std::vector<coordinator::SchemaProperty> GetSchemaForLabel(storage::v3::LabelId /*label*/) const override { + return std::vector<coordinator::SchemaProperty>{}; + }; + private: void SetUpNameIdMappers() { std::unordered_map<uint64_t, std::string> id_to_name; From 668f7857b1fb84eaebbfa626554c799a2dbc0541 Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Fri, 13 Jan 2023 12:38:50 +0100 Subject: [PATCH 20/85] Pass the correct expression to the operator --- src/query/v2/plan/operator.cpp | 4 ++-- src/query/v2/plan/rewrite/index_lookup.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index e9c8652a2..a5ca9d061 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -508,7 +508,7 @@ class DistributedScanAllByPrimaryKeyCursor : public Cursor { // Evaluate the expressions that hold the PrimaryKey. ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.request_router, - storage::v3::View::OLD); + storage::v3::View::NEW); std::vector<msgs::Value> pk; MG_ASSERT(primary_key_); @@ -539,7 +539,7 @@ class DistributedScanAllByPrimaryKeyCursor : public Cursor { } if (current_vertex_it_ == current_batch_.end() && - (request_state_ == State::COMPLETED || !MakeRequest(request_router, context))) { + (request_state_ == State::COMPLETED || !MakeRequestSingleFrame(frame, request_router, context))) { ResetExecutionState(); continue; } diff --git a/src/query/v2/plan/rewrite/index_lookup.hpp b/src/query/v2/plan/rewrite/index_lookup.hpp index e134dd49b..f2a0f1bc6 100644 --- a/src/query/v2/plan/rewrite/index_lookup.hpp +++ b/src/query/v2/plan/rewrite/index_lookup.hpp @@ -600,7 +600,7 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor { EraseLabelFilters(node_symbol, prim_label); std::vector<query::v2::Expression *> pk_expressions; std::transform(primary_key.begin(), primary_key.end(), std::back_inserter(pk_expressions), - [](const auto &exp) { return exp.first; }); + [](const auto &exp) { return exp.second.property_filter->value_; }); return std::make_unique<ScanAllByPrimaryKey>(input, node_symbol, GetLabel(prim_label), pk_expressions); } } From ace1eb401f4aab75a5b955849957dcb96f149897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Sun, 15 Jan 2023 18:25:32 +0100 Subject: [PATCH 21/85] Make unit tests compile with new gtest version --- tests/unit/query_v2_create_node_multiframe.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/unit/query_v2_create_node_multiframe.cpp b/tests/unit/query_v2_create_node_multiframe.cpp index f19082783..39ce17b43 100644 --- a/tests/unit/query_v2_create_node_multiframe.cpp +++ b/tests/unit/query_v2_create_node_multiframe.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -55,11 +55,7 @@ TEST(CreateNodeTest, CreateNodeCursor) { p.push_back(std::make_pair(msgs::PropertyId::FromUint(2), &literal)); node.properties.emplace<0>(std::move(p)); - auto once_cur = plan::MakeUniqueCursorPtr<MockedCursor>(utils::NewDeleteResource()); - EXPECT_CALL(BaseToMock(once_cur.get()), PullMultiple(_, _)).Times(1); - - std::shared_ptr<plan::LogicalOperator> once_op = std::make_shared<MockedLogicalOperator>(); - EXPECT_CALL(BaseToMock(once_op.get()), MakeCursor(_)).Times(1).WillOnce(Return(std::move(once_cur))); + auto once_op = std::make_shared<plan::Once>(); auto create_expand = plan::CreateNode(once_op, node); auto cursor = create_expand.MakeCursor(utils::NewDeleteResource()); From b2b9b1d5cbc06a110f34bbb250591ecb5d5d22e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Sun, 15 Jan 2023 18:25:48 +0100 Subject: [PATCH 22/85] Eliminate warnings about deprecated methods --- tests/unit/bfs_single_node.cpp | 32 +++++++++---------- tests/unit/cypher_main_visitor.cpp | 6 ++-- ..._print_ast_to_original_expression_test.cpp | 4 +-- tests/unit/query_plan.cpp | 4 +-- .../query_v2_create_expand_multiframe.cpp | 3 +- tests/unit/query_v2_cypher_main_visitor.cpp | 6 ++-- tests/unit/storage_v3.cpp | 6 ++-- tests/unit/storage_v3_edge.cpp | 6 ++-- tests/unit/storage_v3_isolation_level.cpp | 6 ++-- tests/unit/utils_csv_parsing.cpp | 4 +-- tests/unit/utils_file_locker.cpp | 8 ++--- tests/unit/utils_memory.cpp | 4 +-- 12 files changed, 44 insertions(+), 45 deletions(-) diff --git a/tests/unit/bfs_single_node.cpp b/tests/unit/bfs_single_node.cpp index 93002eef5..eab4f3973 100644 --- a/tests/unit/bfs_single_node.cpp +++ b/tests/unit/bfs_single_node.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -88,14 +88,14 @@ TEST_P(SingleNodeBfsTest, All) { std::unique_ptr<SingleNodeDb> SingleNodeBfsTest::db_{nullptr}; -INSTANTIATE_TEST_CASE_P(DirectionAndExpansionDepth, SingleNodeBfsTest, - testing::Combine(testing::Range(-1, kVertexCount), testing::Range(-1, kVertexCount), - testing::Values(EdgeAtom::Direction::OUT, EdgeAtom::Direction::IN, - EdgeAtom::Direction::BOTH), - testing::Values(std::vector<std::string>{}), testing::Bool(), - testing::Values(FilterLambdaType::NONE))); +INSTANTIATE_TEST_SUITE_P(DirectionAndExpansionDepth, SingleNodeBfsTest, + testing::Combine(testing::Range(-1, kVertexCount), testing::Range(-1, kVertexCount), + testing::Values(EdgeAtom::Direction::OUT, EdgeAtom::Direction::IN, + EdgeAtom::Direction::BOTH), + testing::Values(std::vector<std::string>{}), testing::Bool(), + testing::Values(FilterLambdaType::NONE))); -INSTANTIATE_TEST_CASE_P( +INSTANTIATE_TEST_SUITE_P( EdgeType, SingleNodeBfsTest, testing::Combine(testing::Values(-1), testing::Values(-1), testing::Values(EdgeAtom::Direction::OUT, EdgeAtom::Direction::IN, EdgeAtom::Direction::BOTH), @@ -103,11 +103,11 @@ INSTANTIATE_TEST_CASE_P( std::vector<std::string>{"b"}, std::vector<std::string>{"a", "b"}), testing::Bool(), testing::Values(FilterLambdaType::NONE))); -INSTANTIATE_TEST_CASE_P(FilterLambda, SingleNodeBfsTest, - testing::Combine(testing::Values(-1), testing::Values(-1), - testing::Values(EdgeAtom::Direction::OUT, EdgeAtom::Direction::IN, - EdgeAtom::Direction::BOTH), - testing::Values(std::vector<std::string>{}), testing::Bool(), - testing::Values(FilterLambdaType::NONE, FilterLambdaType::USE_FRAME, - FilterLambdaType::USE_FRAME_NULL, FilterLambdaType::USE_CTX, - FilterLambdaType::ERROR))); +INSTANTIATE_TEST_SUITE_P(FilterLambda, SingleNodeBfsTest, + testing::Combine(testing::Values(-1), testing::Values(-1), + testing::Values(EdgeAtom::Direction::OUT, EdgeAtom::Direction::IN, + EdgeAtom::Direction::BOTH), + testing::Values(std::vector<std::string>{}), testing::Bool(), + testing::Values(FilterLambdaType::NONE, FilterLambdaType::USE_FRAME, + FilterLambdaType::USE_FRAME_NULL, FilterLambdaType::USE_CTX, + FilterLambdaType::ERROR))); diff --git a/tests/unit/cypher_main_visitor.cpp b/tests/unit/cypher_main_visitor.cpp index 40aeb0161..93f0653b4 100644 --- a/tests/unit/cypher_main_visitor.cpp +++ b/tests/unit/cypher_main_visitor.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -294,7 +294,7 @@ std::shared_ptr<Base> gAstGeneratorTypes[] = { std::make_shared<CachedAstGenerator>(), }; -INSTANTIATE_TEST_CASE_P(AstGeneratorTypes, CypherMainVisitorTest, ::testing::ValuesIn(gAstGeneratorTypes)); +INSTANTIATE_TEST_SUITE_P(AstGeneratorTypes, CypherMainVisitorTest, ::testing::ValuesIn(gAstGeneratorTypes)); // NOTE: The above used to use *Typed Tests* functionality of gtest library. // Unfortunately, the compilation time of this test increased to full 2 minutes! @@ -308,7 +308,7 @@ INSTANTIATE_TEST_CASE_P(AstGeneratorTypes, CypherMainVisitorTest, ::testing::Val // ClonedAstGenerator, CachedAstGenerator> // AstGeneratorTypes; // -// TYPED_TEST_CASE(CypherMainVisitorTest, AstGeneratorTypes); +// TYPED_TEST_SUITE(CypherMainVisitorTest, AstGeneratorTypes); TEST_P(CypherMainVisitorTest, SyntaxException) { auto &ast_generator = *GetParam(); diff --git a/tests/unit/pretty_print_ast_to_original_expression_test.cpp b/tests/unit/pretty_print_ast_to_original_expression_test.cpp index e5d77ae0e..a2144c8aa 100644 --- a/tests/unit/pretty_print_ast_to_original_expression_test.cpp +++ b/tests/unit/pretty_print_ast_to_original_expression_test.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -67,7 +67,7 @@ TEST_P(ExpressiontoStringTest, Example) { EXPECT_EQ(rewritten_expression, rewritten_expression2); } -INSTANTIATE_TEST_CASE_P( +INSTANTIATE_TEST_SUITE_P( PARAMETER, ExpressiontoStringTest, ::testing::Values( std::make_pair(std::string("2 / 1"), std::string("(2 / 1)")), diff --git a/tests/unit/query_plan.cpp b/tests/unit/query_plan.cpp index 93d2f33c7..935709cec 100644 --- a/tests/unit/query_plan.cpp +++ b/tests/unit/query_plan.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -90,7 +90,7 @@ void DeleteListContent(std::list<BaseOpChecker *> *list) { delete ptr; } } -TYPED_TEST_CASE(TestPlanner, PlannerTypes); +TYPED_TEST_SUITE(TestPlanner, PlannerTypes); TYPED_TEST(TestPlanner, MatchNodeReturn) { // Test MATCH (n) RETURN n diff --git a/tests/unit/query_v2_create_expand_multiframe.cpp b/tests/unit/query_v2_create_expand_multiframe.cpp index 8720656fd..e978a6bb5 100644 --- a/tests/unit/query_v2_create_expand_multiframe.cpp +++ b/tests/unit/query_v2_create_expand_multiframe.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -63,7 +63,6 @@ TEST(CreateExpandTest, Cursor) { node.symbol = symbol_table.CreateSymbol("u", true); auto once_op = std::make_shared<plan::Once>(); - auto once_cur = once_op->MakeCursor(utils::NewDeleteResource()); auto create_expand = plan::CreateExpand(node, edge, once_op, src, true); auto cursor = create_expand.MakeCursor(utils::NewDeleteResource()); diff --git a/tests/unit/query_v2_cypher_main_visitor.cpp b/tests/unit/query_v2_cypher_main_visitor.cpp index d7e4169e2..4d5311a6e 100644 --- a/tests/unit/query_v2_cypher_main_visitor.cpp +++ b/tests/unit/query_v2_cypher_main_visitor.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -299,7 +299,7 @@ std::shared_ptr<Base> gAstGeneratorTypes[] = { std::make_shared<CachedAstGenerator>(), }; -INSTANTIATE_TEST_CASE_P(AstGeneratorTypes, CypherMainVisitorTest, ::testing::ValuesIn(gAstGeneratorTypes)); +INSTANTIATE_TEST_SUITE_P(AstGeneratorTypes, CypherMainVisitorTest, ::testing::ValuesIn(gAstGeneratorTypes)); // NOTE: The above used to use *Typed Tests* functionality of gtest library. // Unfortunately, the compilation time of this test increased to full 2 minutes! @@ -313,7 +313,7 @@ INSTANTIATE_TEST_CASE_P(AstGeneratorTypes, CypherMainVisitorTest, ::testing::Val // ClonedAstGenerator, CachedAstGenerator> // AstGeneratorTypes; // -// TYPED_TEST_CASE(CypherMainVisitorTest, AstGeneratorTypes); +// TYPED_TEST_SUITE(CypherMainVisitorTest, AstGeneratorTypes); TEST_P(CypherMainVisitorTest, SyntaxException) { auto &ast_generator = *GetParam(); diff --git a/tests/unit/storage_v3.cpp b/tests/unit/storage_v3.cpp index 6f6902d1c..1832165f9 100644 --- a/tests/unit/storage_v3.cpp +++ b/tests/unit/storage_v3.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -82,8 +82,8 @@ class StorageV3 : public ::testing::TestWithParam<bool> { Config{.gc = {.reclamation_interval = reclamation_interval}}}; coordinator::Hlc last_hlc{0, io::Time{}}; }; -INSTANTIATE_TEST_CASE_P(WithGc, StorageV3, ::testing::Values(true)); -INSTANTIATE_TEST_CASE_P(WithoutGc, StorageV3, ::testing::Values(false)); +INSTANTIATE_TEST_SUITE_P(WithGc, StorageV3, ::testing::Values(true)); +INSTANTIATE_TEST_SUITE_P(WithoutGc, StorageV3, ::testing::Values(false)); // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageV3, Commit) { diff --git a/tests/unit/storage_v3_edge.cpp b/tests/unit/storage_v3_edge.cpp index 3d2ab8bbd..8637db11c 100644 --- a/tests/unit/storage_v3_edge.cpp +++ b/tests/unit/storage_v3_edge.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -60,8 +60,8 @@ class StorageEdgeTest : public ::testing::TestWithParam<bool> { coordinator::Hlc last_hlc{0, io::Time{}}; }; -INSTANTIATE_TEST_CASE_P(EdgesWithProperties, StorageEdgeTest, ::testing::Values(true)); -INSTANTIATE_TEST_CASE_P(EdgesWithoutProperties, StorageEdgeTest, ::testing::Values(false)); +INSTANTIATE_TEST_SUITE_P(EdgesWithProperties, StorageEdgeTest, ::testing::Values(true)); +INSTANTIATE_TEST_SUITE_P(EdgesWithoutProperties, StorageEdgeTest, ::testing::Values(false)); // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) { diff --git a/tests/unit/storage_v3_isolation_level.cpp b/tests/unit/storage_v3_isolation_level.cpp index 6b661f632..bd0f27b6b 100644 --- a/tests/unit/storage_v3_isolation_level.cpp +++ b/tests/unit/storage_v3_isolation_level.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -135,6 +135,6 @@ TEST_P(StorageIsolationLevelTest, Visibility) { } } -INSTANTIATE_TEST_CASE_P(ParameterizedStorageIsolationLevelTests, StorageIsolationLevelTest, - ::testing::ValuesIn(isolation_levels), StorageIsolationLevelTest::PrintToStringParamName()); +INSTANTIATE_TEST_SUITE_P(ParameterizedStorageIsolationLevelTests, StorageIsolationLevelTest, + ::testing::ValuesIn(isolation_levels), StorageIsolationLevelTest::PrintToStringParamName()); } // namespace memgraph::storage::v3::tests diff --git a/tests/unit/utils_csv_parsing.cpp b/tests/unit/utils_csv_parsing.cpp index 3c852b171..e8d6c2241 100644 --- a/tests/unit/utils_csv_parsing.cpp +++ b/tests/unit/utils_csv_parsing.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -330,4 +330,4 @@ TEST_P(CsvReaderTest, EmptyColumns) { } } -INSTANTIATE_TEST_CASE_P(NewlineParameterizedTest, CsvReaderTest, ::testing::Values("\n", "\r\n")); +INSTANTIATE_TEST_SUITE_P(NewlineParameterizedTest, CsvReaderTest, ::testing::Values("\n", "\r\n")); diff --git a/tests/unit/utils_file_locker.cpp b/tests/unit/utils_file_locker.cpp index 21c71b1a3..f2217953e 100644 --- a/tests/unit/utils_file_locker.cpp +++ b/tests/unit/utils_file_locker.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -190,9 +190,9 @@ TEST_P(FileLockerParameterizedTest, RemovePath) { std::filesystem::current_path(save_path); } -INSTANTIATE_TEST_CASE_P(FileLockerPathVariantTests, FileLockerParameterizedTest, - ::testing::Values(std::make_tuple(false, false), std::make_tuple(false, true), - std::make_tuple(true, false), std::make_tuple(true, true))); +INSTANTIATE_TEST_SUITE_P(FileLockerPathVariantTests, FileLockerParameterizedTest, + ::testing::Values(std::make_tuple(false, false), std::make_tuple(false, true), + std::make_tuple(true, false), std::make_tuple(true, true))); TEST_F(FileLockerTest, MultipleLockers) { CreateFiles(3); diff --git a/tests/unit/utils_memory.cpp b/tests/unit/utils_memory.cpp index 70bf85653..73f78d545 100644 --- a/tests/unit/utils_memory.cpp +++ b/tests/unit/utils_memory.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -393,7 +393,7 @@ class AllocatorTest : public ::testing::Test {}; using ContainersWithAllocators = ::testing::Types<ContainerWithAllocatorLast, ContainerWithAllocatorFirst>; -TYPED_TEST_CASE(AllocatorTest, ContainersWithAllocators); +TYPED_TEST_SUITE(AllocatorTest, ContainersWithAllocators); TYPED_TEST(AllocatorTest, PropagatesToStdUsesAllocator) { std::vector<TypeParam, memgraph::utils::Allocator<TypeParam>> vec(memgraph::utils::NewDeleteResource()); From b30137ab7ac7bdd5005b6f5ffc23d45131e6fa6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Sun, 15 Jan 2023 18:39:58 +0100 Subject: [PATCH 23/85] Improve unit tests to catch bug --- src/query/v2/requests.hpp | 3 ++- tests/unit/query_v2_create_node_multiframe.cpp | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/query/v2/requests.hpp b/src/query/v2/requests.hpp index 9ff9a1bae..2335fea7d 100644 --- a/src/query/v2/requests.hpp +++ b/src/query/v2/requests.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -36,6 +36,7 @@ struct Value; struct Label { LabelId id; friend bool operator==(const Label &lhs, const Label &rhs) { return lhs.id == rhs.id; } + friend bool operator==(const Label &lhs, const LabelId &rhs) { return lhs.id == rhs; } }; // TODO(kostasrim) update this with CompoundKey, same for the rest of the file. diff --git a/tests/unit/query_v2_create_node_multiframe.cpp b/tests/unit/query_v2_create_node_multiframe.cpp index 39ce17b43..5e5d83249 100644 --- a/tests/unit/query_v2_create_node_multiframe.cpp +++ b/tests/unit/query_v2_create_node_multiframe.cpp @@ -36,15 +36,13 @@ MultiFrame CreateMultiFrame(const size_t max_pos) { TEST(CreateNodeTest, CreateNodeCursor) { using testing::_; + using testing::ElementsAre; using testing::Return; AstStorage ast; SymbolTable symbol_table; plan::NodeCreationInfo node; - plan::EdgeCreationInfo edge; - edge.edge_type = msgs::EdgeTypeId::FromUint(1); - edge.direction = EdgeAtom::Direction::IN; auto id_alloc = IdAllocator(0, 100); node.symbol = symbol_table.CreateSymbol("n", true); @@ -67,5 +65,19 @@ TEST(CreateNodeTest, CreateNodeCursor) { auto context = MakeContext(ast, symbol_table, &router, &id_alloc); auto multi_frame = CreateMultiFrame(context.symbol_table.max_position()); cursor->PullMultiple(multi_frame, context); + + auto frames = multi_frame.GetValidFramesReader(); + auto number_of_valid_frames = 0; + for (auto &frame : frames) { + ++number_of_valid_frames; + EXPECT_EQ(frame[node.symbol].IsEdge(), true); + const auto &n = frame[node.symbol].ValueVertex(); + EXPECT_THAT(n.Labels(), ElementsAre(msgs::Label{msgs::LabelId::FromUint(2)})); + } + EXPECT_EQ(number_of_valid_frames, 1); + + auto invalid_frames = multi_frame.GetInvalidFramesPopulator(); + auto number_of_invalid_frames = std::distance(invalid_frames.begin(), invalid_frames.end()); + EXPECT_EQ(number_of_invalid_frames, 99); } } // namespace memgraph::query::v2 From c139856b2a9b17ea52bc9c38107eeaa7f3b966f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Sun, 15 Jan 2023 18:52:36 +0100 Subject: [PATCH 24/85] Fix unit tests --- src/query/v2/plan/operator.cpp | 14 +++++++------- tests/unit/query_v2_create_node_multiframe.cpp | 18 +++++++++--------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index da282387c..630599276 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -192,7 +192,7 @@ class DistributedCreateNodeCursor : public Cursor { void PullMultiple(MultiFrame &multi_frame, ExecutionContext &context) override { SCOPED_PROFILE_OP("CreateNodeMF"); input_cursor_->PullMultiple(multi_frame, context); - auto &request_router = context.request_router; + auto *request_router = context.request_router; { SCOPED_REQUEST_WAIT_PROFILE; request_router->CreateVertices(NodeCreationInfoToRequests(context, multi_frame)); @@ -259,10 +259,10 @@ class DistributedCreateNodeCursor : public Cursor { } void PlaceNodesOnTheMultiFrame(MultiFrame &multi_frame, ExecutionContext &context) { - auto multi_frame_reader = multi_frame.GetValidFramesConsumer(); + auto multi_frame_modifier = multi_frame.GetValidFramesModifier(); size_t i = 0; - MG_ASSERT(std::distance(multi_frame_reader.begin(), multi_frame_reader.end())); - for (auto &frame : multi_frame_reader) { + MG_ASSERT(std::distance(multi_frame_modifier.begin(), multi_frame_modifier.end())); + for (auto &frame : multi_frame_modifier) { const auto primary_label = msgs::Label{.id = nodes_info_[0]->labels[0]}; msgs::Vertex v{.id = std::make_pair(primary_label, primary_keys_[i])}; frame[nodes_info_.front()->symbol] = TypedValue( @@ -272,8 +272,8 @@ class DistributedCreateNodeCursor : public Cursor { std::vector<msgs::NewVertex> NodeCreationInfoToRequests(ExecutionContext &context, MultiFrame &multi_frame) { std::vector<msgs::NewVertex> requests; - auto multi_frame_reader = multi_frame.GetValidFramesConsumer(); - for (auto &frame : multi_frame_reader) { + auto multi_frame_modifier = multi_frame.GetValidFramesModifier(); + for (auto &frame : multi_frame_modifier) { msgs::PrimaryKey pk; for (const auto &node_info : nodes_info_) { msgs::NewVertex rqst; diff --git a/tests/unit/query_v2_create_node_multiframe.cpp b/tests/unit/query_v2_create_node_multiframe.cpp index 5e5d83249..b04b00e96 100644 --- a/tests/unit/query_v2_create_node_multiframe.cpp +++ b/tests/unit/query_v2_create_node_multiframe.cpp @@ -9,6 +9,7 @@ // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. +#include "gmock/gmock.h" #include "mock_helpers.hpp" #include "query/v2/bindings/frame.hpp" @@ -26,17 +27,13 @@ namespace memgraph::query::v2 { MultiFrame CreateMultiFrame(const size_t max_pos) { static constexpr size_t frame_size = 100; MultiFrame multi_frame(max_pos, frame_size, utils::NewDeleteResource()); - auto frames_populator = multi_frame.GetInvalidFramesPopulator(); - for (auto &frame : frames_populator) { - frame.MakeValid(); - } return multi_frame; } TEST(CreateNodeTest, CreateNodeCursor) { using testing::_; - using testing::ElementsAre; + using testing::IsEmpty; using testing::Return; AstStorage ast; @@ -46,11 +43,12 @@ TEST(CreateNodeTest, CreateNodeCursor) { auto id_alloc = IdAllocator(0, 100); node.symbol = symbol_table.CreateSymbol("n", true); - node.labels.push_back(msgs::LabelId::FromUint(2)); + const auto primary_label_id = msgs::LabelId::FromUint(2); + node.labels.push_back(primary_label_id); auto literal = PrimitiveLiteral(); literal.value_ = TypedValue(static_cast<int64_t>(200)); auto p = plan::PropertiesMapList{}; - p.push_back(std::make_pair(msgs::PropertyId::FromUint(2), &literal)); + p.push_back(std::make_pair(msgs::PropertyId::FromUint(3), &literal)); node.properties.emplace<0>(std::move(p)); auto once_op = std::make_shared<plan::Once>(); @@ -70,9 +68,11 @@ TEST(CreateNodeTest, CreateNodeCursor) { auto number_of_valid_frames = 0; for (auto &frame : frames) { ++number_of_valid_frames; - EXPECT_EQ(frame[node.symbol].IsEdge(), true); + EXPECT_EQ(frame[node.symbol].IsVertex(), true); const auto &n = frame[node.symbol].ValueVertex(); - EXPECT_THAT(n.Labels(), ElementsAre(msgs::Label{msgs::LabelId::FromUint(2)})); + EXPECT_THAT(n.Labels(), IsEmpty()); + EXPECT_EQ(n.PrimaryLabel(), primary_label_id); + // TODO(antaljanosbenjamin): Check primary key } EXPECT_EQ(number_of_valid_frames, 1); From e40f7f507b422ddb0daa4379bd529fce2234cfa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Mon, 16 Jan 2023 08:40:43 +0100 Subject: [PATCH 25/85] Fix pull logic for multiframe --- src/query/v2/interpreter.cpp | 4 ++-- src/query/v2/multiframe.cpp | 6 +++++- src/query/v2/multiframe.hpp | 3 ++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/query/v2/interpreter.cpp b/src/query/v2/interpreter.cpp index fde11ac00..594942aec 100644 --- a/src/query/v2/interpreter.cpp +++ b/src/query/v2/interpreter.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -734,7 +734,7 @@ std::optional<plan::ProfilingStatsWithTotalTime> PullPlan::PullMultiple(AnyStrea // Returns true if a result was pulled. const auto pull_result = [&]() -> bool { cursor_->PullMultiple(multi_frame_, ctx_); - return multi_frame_.HasValidFrame(); + return !multi_frame_.HasInvalidFrame(); }; const auto stream_values = [&output_symbols, &stream](const Frame &frame) { diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index 2cb591153..0ddfd3aa7 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -48,6 +48,10 @@ bool MultiFrame::HasValidFrame() const noexcept { return std::any_of(frames_.begin(), frames_.end(), [](auto &frame) { return frame.IsValid(); }); } +bool MultiFrame::HasInvalidFrame() const noexcept { + return std::any_of(frames_.rbegin(), frames_.rend(), [](auto &frame) { return !frame.IsValid(); }); +} + // NOLINTNEXTLINE (bugprone-exception-escape) void MultiFrame::DefragmentValidFrames() noexcept { /* diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index 7d9b73700..b46be171a 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -81,6 +81,7 @@ class MultiFrame { void MakeAllFramesInvalid() noexcept; bool HasValidFrame() const noexcept; + bool HasInvalidFrame() const noexcept; inline utils::MemoryResource *GetMemoryResource() { return frames_[0].GetMemoryResource(); } From 392f6e2b730f644f7707a7861b81dcc43ee71280 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Mon, 16 Jan 2023 08:57:23 +0100 Subject: [PATCH 26/85] Reduce the number of node infos to a maximum of one --- src/query/v2/plan/operator.cpp | 141 +++++++++++++++------------------ 1 file changed, 65 insertions(+), 76 deletions(-) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index 630599276..2f191c113 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -170,9 +170,8 @@ uint64_t ComputeProfilingKey(const T *obj) { class DistributedCreateNodeCursor : public Cursor { public: using InputOperator = std::shared_ptr<memgraph::query::v2::plan::LogicalOperator>; - DistributedCreateNodeCursor(const InputOperator &op, utils::MemoryResource *mem, - std::vector<const NodeCreationInfo *> nodes_info) - : input_cursor_(op->MakeCursor(mem)), nodes_info_(std::move(nodes_info)) {} + DistributedCreateNodeCursor(const InputOperator &op, utils::MemoryResource *mem, const NodeCreationInfo &node_info) + : input_cursor_(op->MakeCursor(mem)), node_info_(node_info) {} bool Pull(Frame &frame, ExecutionContext &context) override { SCOPED_PROFILE_OP("CreateNode"); @@ -206,27 +205,78 @@ class DistributedCreateNodeCursor : public Cursor { void PlaceNodeOnTheFrame(Frame &frame, ExecutionContext &context) { // TODO(kostasrim) Make this work with batching - const auto primary_label = msgs::Label{.id = nodes_info_[0]->labels[0]}; + const auto primary_label = msgs::Label{.id = node_info_.labels[0]}; msgs::Vertex v{.id = std::make_pair(primary_label, primary_keys_[0])}; - frame[nodes_info_.front()->symbol] = + frame[node_info_.symbol] = TypedValue(query::v2::accessors::VertexAccessor(std::move(v), src_vertex_props_[0], context.request_router)); } std::vector<msgs::NewVertex> NodeCreationInfoToRequest(ExecutionContext &context, Frame &frame) { std::vector<msgs::NewVertex> requests; - // TODO(kostasrim) this assertion should be removed once we support multiple vertex creation - MG_ASSERT(nodes_info_.size() == 1); msgs::PrimaryKey pk; - for (const auto &node_info : nodes_info_) { + msgs::NewVertex rqst; + MG_ASSERT(!node_info_.labels.empty(), "Cannot determine primary label"); + const auto primary_label = node_info_.labels[0]; + // TODO(jbajic) Fix properties not send, + // suggestion: ignore distinction between properties and primary keys + // since schema validation is done on storage side + ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, nullptr, + storage::v3::View::NEW); + if (const auto *node_info_properties = std::get_if<PropertiesMapList>(&node_info_.properties)) { + for (const auto &[key, value_expression] : *node_info_properties) { + TypedValue val = value_expression->Accept(evaluator); + if (context.request_router->IsPrimaryKey(primary_label, key)) { + rqst.primary_key.push_back(TypedValueToValue(val)); + pk.push_back(TypedValueToValue(val)); + } + } + } else { + auto property_map = evaluator.Visit(*std::get<ParameterLookup *>(node_info_.properties)).ValueMap(); + for (const auto &[key, value] : property_map) { + auto key_str = std::string(key); + auto property_id = context.request_router->NameToProperty(key_str); + if (context.request_router->IsPrimaryKey(primary_label, property_id)) { + rqst.primary_key.push_back(TypedValueToValue(value)); + pk.push_back(TypedValueToValue(value)); + } + } + } + + // TODO(kostasrim) Copy non primary labels as well + rqst.label_ids.push_back(msgs::Label{.id = primary_label}); + src_vertex_props_.push_back(rqst.properties); + requests.push_back(std::move(rqst)); + + primary_keys_.push_back(std::move(pk)); + return requests; + } + + void PlaceNodesOnTheMultiFrame(MultiFrame &multi_frame, ExecutionContext &context) { + auto multi_frame_modifier = multi_frame.GetValidFramesModifier(); + size_t i = 0; + MG_ASSERT(std::distance(multi_frame_modifier.begin(), multi_frame_modifier.end())); + for (auto &frame : multi_frame_modifier) { + const auto primary_label = msgs::Label{.id = node_info_.labels[0]}; + msgs::Vertex v{.id = std::make_pair(primary_label, primary_keys_[i])}; + frame[node_info_.symbol] = TypedValue( + query::v2::accessors::VertexAccessor(std::move(v), src_vertex_props_[i++], context.request_router)); + } + } + + std::vector<msgs::NewVertex> NodeCreationInfoToRequests(ExecutionContext &context, MultiFrame &multi_frame) { + std::vector<msgs::NewVertex> requests; + auto multi_frame_modifier = multi_frame.GetValidFramesModifier(); + for (auto &frame : multi_frame_modifier) { + msgs::PrimaryKey pk; msgs::NewVertex rqst; - MG_ASSERT(!node_info->labels.empty(), "Cannot determine primary label"); - const auto primary_label = node_info->labels[0]; + MG_ASSERT(!node_info_.labels.empty(), "Cannot determine primary label"); + const auto primary_label = node_info_.labels[0]; // TODO(jbajic) Fix properties not send, // suggestion: ignore distinction between properties and primary keys // since schema validation is done on storage side ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, nullptr, storage::v3::View::NEW); - if (const auto *node_info_properties = std::get_if<PropertiesMapList>(&node_info->properties)) { + if (const auto *node_info_properties = std::get_if<PropertiesMapList>(&node_info_.properties)) { for (const auto &[key, value_expression] : *node_info_properties) { TypedValue val = value_expression->Accept(evaluator); if (context.request_router->IsPrimaryKey(primary_label, key)) { @@ -235,7 +285,7 @@ class DistributedCreateNodeCursor : public Cursor { } } } else { - auto property_map = evaluator.Visit(*std::get<ParameterLookup *>(node_info->properties)).ValueMap(); + auto property_map = evaluator.Visit(*std::get<ParameterLookup *>(node_info_.properties)).ValueMap(); for (const auto &[key, value] : property_map) { auto key_str = std::string(key); auto property_id = context.request_router->NameToProperty(key_str); @@ -246,80 +296,19 @@ class DistributedCreateNodeCursor : public Cursor { } } - if (node_info->labels.empty()) { - throw QueryRuntimeException("Primary label must be defined!"); - } // TODO(kostasrim) Copy non primary labels as well rqst.label_ids.push_back(msgs::Label{.id = primary_label}); src_vertex_props_.push_back(rqst.properties); requests.push_back(std::move(rqst)); - } - primary_keys_.push_back(std::move(pk)); - return requests; - } - - void PlaceNodesOnTheMultiFrame(MultiFrame &multi_frame, ExecutionContext &context) { - auto multi_frame_modifier = multi_frame.GetValidFramesModifier(); - size_t i = 0; - MG_ASSERT(std::distance(multi_frame_modifier.begin(), multi_frame_modifier.end())); - for (auto &frame : multi_frame_modifier) { - const auto primary_label = msgs::Label{.id = nodes_info_[0]->labels[0]}; - msgs::Vertex v{.id = std::make_pair(primary_label, primary_keys_[i])}; - frame[nodes_info_.front()->symbol] = TypedValue( - query::v2::accessors::VertexAccessor(std::move(v), src_vertex_props_[i++], context.request_router)); - } - } - - std::vector<msgs::NewVertex> NodeCreationInfoToRequests(ExecutionContext &context, MultiFrame &multi_frame) { - std::vector<msgs::NewVertex> requests; - auto multi_frame_modifier = multi_frame.GetValidFramesModifier(); - for (auto &frame : multi_frame_modifier) { - msgs::PrimaryKey pk; - for (const auto &node_info : nodes_info_) { - msgs::NewVertex rqst; - MG_ASSERT(!node_info->labels.empty(), "Cannot determine primary label"); - const auto primary_label = node_info->labels[0]; - // TODO(jbajic) Fix properties not send, - // suggestion: ignore distinction between properties and primary keys - // since schema validation is done on storage side - ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, nullptr, - storage::v3::View::NEW); - if (const auto *node_info_properties = std::get_if<PropertiesMapList>(&node_info->properties)) { - for (const auto &[key, value_expression] : *node_info_properties) { - TypedValue val = value_expression->Accept(evaluator); - if (context.request_router->IsPrimaryKey(primary_label, key)) { - rqst.primary_key.push_back(TypedValueToValue(val)); - pk.push_back(TypedValueToValue(val)); - } - } - } else { - auto property_map = evaluator.Visit(*std::get<ParameterLookup *>(node_info->properties)).ValueMap(); - for (const auto &[key, value] : property_map) { - auto key_str = std::string(key); - auto property_id = context.request_router->NameToProperty(key_str); - if (context.request_router->IsPrimaryKey(primary_label, property_id)) { - rqst.primary_key.push_back(TypedValueToValue(value)); - pk.push_back(TypedValueToValue(value)); - } - } - } - - if (node_info->labels.empty()) { - throw QueryRuntimeException("Primary label must be defined!"); - } - // TODO(kostasrim) Copy non primary labels as well - rqst.label_ids.push_back(msgs::Label{.id = primary_label}); - src_vertex_props_.push_back(rqst.properties); - requests.push_back(std::move(rqst)); - } primary_keys_.push_back(std::move(pk)); } + return requests; } private: const UniqueCursorPtr input_cursor_; - std::vector<const NodeCreationInfo *> nodes_info_; + NodeCreationInfo node_info_; std::vector<std::vector<std::pair<storage::v3::PropertyId, msgs::Value>>> src_vertex_props_; std::vector<msgs::PrimaryKey> primary_keys_; }; @@ -364,7 +353,7 @@ ACCEPT_WITH_INPUT(CreateNode) UniqueCursorPtr CreateNode::MakeCursor(utils::MemoryResource *mem) const { EventCounter::IncrementCounter(EventCounter::CreateNodeOperator); - return MakeUniqueCursorPtr<DistributedCreateNodeCursor>(mem, input_, mem, std::vector{&this->node_info_}); + return MakeUniqueCursorPtr<DistributedCreateNodeCursor>(mem, input_, mem, this->node_info_); } std::vector<Symbol> CreateNode::ModifiedSymbols(const SymbolTable &table) const { From 920ad277a5e44a34083f0030d3618695a7eb42bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Mon, 16 Jan 2023 09:03:35 +0100 Subject: [PATCH 27/85] Add assertion about primary label --- src/query/v2/plan/operator.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index 2f191c113..4ea189772 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -271,6 +271,7 @@ class DistributedCreateNodeCursor : public Cursor { msgs::NewVertex rqst; MG_ASSERT(!node_info_.labels.empty(), "Cannot determine primary label"); const auto primary_label = node_info_.labels[0]; + MG_ASSERT(context.request_router->IsPrimaryLabel(primary_label), "First label has to be a primary label!"); // TODO(jbajic) Fix properties not send, // suggestion: ignore distinction between properties and primary keys // since schema validation is done on storage side From 775e950dbae0bc34339c91bd9def081c89f66308 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Mon, 16 Jan 2023 10:16:12 +0100 Subject: [PATCH 28/85] Update unit tests to test the new logic --- tests/unit/query_v2_create_node_multiframe.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/unit/query_v2_create_node_multiframe.cpp b/tests/unit/query_v2_create_node_multiframe.cpp index b04b00e96..014fff6ce 100644 --- a/tests/unit/query_v2_create_node_multiframe.cpp +++ b/tests/unit/query_v2_create_node_multiframe.cpp @@ -56,10 +56,9 @@ TEST(CreateNodeTest, CreateNodeCursor) { auto create_expand = plan::CreateNode(once_op, node); auto cursor = create_expand.MakeCursor(utils::NewDeleteResource()); MockedRequestRouter router; - EXPECT_CALL(router, CreateVertices(testing::_)) - .Times(1) - .WillOnce(::testing::Return(std::vector<msgs::CreateVerticesResponse>{})); - EXPECT_CALL(router, IsPrimaryKey(testing::_, testing::_)).WillRepeatedly(::testing::Return(true)); + EXPECT_CALL(router, CreateVertices(_)).Times(1).WillOnce(Return(std::vector<msgs::CreateVerticesResponse>{})); + EXPECT_CALL(router, IsPrimaryLabel(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(router, IsPrimaryKey(_, _)).WillRepeatedly(Return(true)); auto context = MakeContext(ast, symbol_table, &router, &id_alloc); auto multi_frame = CreateMultiFrame(context.symbol_table.max_position()); cursor->PullMultiple(multi_frame, context); From fdd89e0e81be9d6f22804482b1fa48ae5f4540d2 Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Mon, 16 Jan 2023 11:27:41 +0100 Subject: [PATCH 29/85] Replace ScanVertices with GetProperties request, in the case of ScanAllByPrimaryKey operator --- src/query/v2/plan/operator.cpp | 36 ++++++++++++++++++++++++++++++++- src/query/v2/request_router.hpp | 3 +-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index a5ca9d061..a7a3efe3b 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -516,7 +516,41 @@ class DistributedScanAllByPrimaryKeyCursor : public Cursor { pk.push_back(TypedValueToValue(primary_key->Accept(evaluator))); } - current_batch_ = request_router.ScanVertices(request_label, pk); + // Original + // current_batch_ = request_router.ScanVertices(request_label, pk); + + // VertexAccessor(Vertex v, std::vector<std::pair<PropertyId, Value>> props, + // const RequestRouterInterface *request_router); + + // VertexAccessor(Vertex v, std::map<PropertyId, Value> &&props, const RequestRouterInterface *request_router); + // VertexAccessor(Vertex v, const std::map<PropertyId, Value> &props, const RequestRouterInterface + // *request_router); + + // struct Vertex { + // VertexId id; + // std::vector<Label> labels; + // friend bool operator==(const Vertex &lhs, const Vertex &rhs) { return lhs.id == rhs.id; } + // }; + + msgs::Label label = {.id = msgs::LabelId::FromUint(label_->AsUint())}; + + msgs::GetPropertiesRequest req = {.vertex_ids = {std::make_pair(label, pk)}}; + auto get_prop_result = request_router.GetProperties(req); + MG_ASSERT(get_prop_result.size() == 1); + + // std::vector<std::pair<PropertyId, Value>> props + std::map<msgs::PropertyId, msgs::Value> transformed_properties; + auto properties = get_prop_result[0].props; + std::transform(properties.begin(), properties.end(), + std::inserter(transformed_properties, transformed_properties.end()), [](const auto &prop) { + return std::make_pair(msgs::PropertyId::FromUint(prop.first.AsUint()), prop.second); + }); + + msgs::Vertex vertex = {.id = get_prop_result[0].vertex, .labels = {label}}; + // auto va = VertexAccessor(vertex, std::move(transformed_properties), &request_router); + auto va = VertexAccessor(vertex, properties, &request_router); + + current_batch_ = {va}; } current_vertex_it_ = current_batch_.begin(); request_state_ = State::COMPLETED; diff --git a/src/query/v2/request_router.hpp b/src/query/v2/request_router.hpp index 6004c0b88..d4223e880 100644 --- a/src/query/v2/request_router.hpp +++ b/src/query/v2/request_router.hpp @@ -115,8 +115,7 @@ class RequestRouterInterface { virtual std::optional<storage::v3::LabelId> MaybeNameToLabel(const std::string &name) const = 0; virtual bool IsPrimaryLabel(storage::v3::LabelId label) const = 0; virtual bool IsPrimaryKey(storage::v3::LabelId primary_label, storage::v3::PropertyId property) const = 0; - // TODO - (gvolfing) Implement this function in the mocked class. - virtual std::vector<coordinator::SchemaProperty> GetSchemaForLabel(storage::v3::LabelId /*label*/) const = 0; + virtual std::vector<coordinator::SchemaProperty> GetSchemaForLabel(storage::v3::LabelId label) const = 0; }; // TODO(kostasrim)rename this class template From 9be5ee1ae9e25c107c02d5a93b01082bebb521e9 Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Mon, 16 Jan 2023 13:26:33 +0100 Subject: [PATCH 30/85] Make GetProperties return the PrimaryKeys as well, when queried for all properties like ScanAll --- src/query/v2/plan/operator.cpp | 24 +++++++++++------------- src/query/v2/request_router.hpp | 2 ++ src/storage/v3/shard_rsm.cpp | 12 ++++++++---- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index a7a3efe3b..a410d31a7 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -519,6 +519,8 @@ class DistributedScanAllByPrimaryKeyCursor : public Cursor { // Original // current_batch_ = request_router.ScanVertices(request_label, pk); + auto asd_debug = request_router.ScanVertices(request_label, pk); + // VertexAccessor(Vertex v, std::vector<std::pair<PropertyId, Value>> props, // const RequestRouterInterface *request_router); @@ -536,21 +538,17 @@ class DistributedScanAllByPrimaryKeyCursor : public Cursor { msgs::GetPropertiesRequest req = {.vertex_ids = {std::make_pair(label, pk)}}; auto get_prop_result = request_router.GetProperties(req); - MG_ASSERT(get_prop_result.size() == 1); + MG_ASSERT(get_prop_result.size() <= 1); - // std::vector<std::pair<PropertyId, Value>> props - std::map<msgs::PropertyId, msgs::Value> transformed_properties; - auto properties = get_prop_result[0].props; - std::transform(properties.begin(), properties.end(), - std::inserter(transformed_properties, transformed_properties.end()), [](const auto &prop) { - return std::make_pair(msgs::PropertyId::FromUint(prop.first.AsUint()), prop.second); - }); + if (get_prop_result.empty()) { + current_batch_ = std::vector<VertexAccessor>{}; + } else { + auto properties = get_prop_result[0].props; + // TODO (gvolfing) figure out labels when relevant. + msgs::Vertex vertex = {.id = get_prop_result[0].vertex, .labels = {}}; - msgs::Vertex vertex = {.id = get_prop_result[0].vertex, .labels = {label}}; - // auto va = VertexAccessor(vertex, std::move(transformed_properties), &request_router); - auto va = VertexAccessor(vertex, properties, &request_router); - - current_batch_ = {va}; + current_batch_ = {VertexAccessor(vertex, properties, &request_router)}; + } } current_vertex_it_ = current_batch_.begin(); request_state_ = State::COMPLETED; diff --git a/src/query/v2/request_router.hpp b/src/query/v2/request_router.hpp index d4223e880..d83dd6928 100644 --- a/src/query/v2/request_router.hpp +++ b/src/query/v2/request_router.hpp @@ -372,6 +372,8 @@ class RequestRouter : public RequestRouterInterface { } std::vector<msgs::GetPropertiesResultRow> GetProperties(msgs::GetPropertiesRequest requests) override { + // Add HLC to GetProperties request + requests.transaction_id = transaction_id_; // create requests std::vector<ShardRequestState<msgs::GetPropertiesRequest>> requests_to_be_sent = RequestsForGetProperties(std::move(requests)); diff --git a/src/storage/v3/shard_rsm.cpp b/src/storage/v3/shard_rsm.cpp index b919d217c..c23d31ebc 100644 --- a/src/storage/v3/shard_rsm.cpp +++ b/src/storage/v3/shard_rsm.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -535,13 +535,17 @@ msgs::ReadResponses ShardRsm::HandleRead(msgs::GetPropertiesRequest &&req) { return result; }; - auto collect_props = [&req](const VertexAccessor &v_acc, - const std::optional<EdgeAccessor> &e_acc) -> ShardResult<std::map<PropertyId, Value>> { + auto collect_props = [this, &req]( + const VertexAccessor &v_acc, + const std::optional<EdgeAccessor> &e_acc) -> ShardResult<std::map<PropertyId, Value>> { if (!req.property_ids) { + const auto *schema = shard_->GetSchema(shard_->PrimaryLabel()); + MG_ASSERT(schema); + if (e_acc) { return CollectAllPropertiesFromAccessor(*e_acc, view); } - return CollectAllPropertiesFromAccessor(v_acc, view); + return CollectAllPropertiesFromAccessor(v_acc, view, *schema); } if (e_acc) { From 3257d46d1822dbb59959df35d71e1aa6c16ed0e8 Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Mon, 16 Jan 2023 13:44:02 +0100 Subject: [PATCH 31/85] Remove assertion --- src/query/v2/plan/operator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index a410d31a7..ed0b51632 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -538,7 +538,7 @@ class DistributedScanAllByPrimaryKeyCursor : public Cursor { msgs::GetPropertiesRequest req = {.vertex_ids = {std::make_pair(label, pk)}}; auto get_prop_result = request_router.GetProperties(req); - MG_ASSERT(get_prop_result.size() <= 1); + // MG_ASSERT(get_prop_result.size() <= 1); if (get_prop_result.empty()) { current_batch_ = std::vector<VertexAccessor>{}; From d1fe73c987b2ce3ef660b263b9983ce03b18620c Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Mon, 16 Jan 2023 14:05:13 +0100 Subject: [PATCH 32/85] Conform simulation to test GetProperties correctly --- tests/simulation/shard_rsm.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/simulation/shard_rsm.cpp b/tests/simulation/shard_rsm.cpp index 768217945..5c35b822a 100644 --- a/tests/simulation/shard_rsm.cpp +++ b/tests/simulation/shard_rsm.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -1305,7 +1305,7 @@ void TestGetProperties(ShardClient &client) { MG_ASSERT(!result.error); MG_ASSERT(result.result_row.size() == 2); for (const auto &elem : result.result_row) { - MG_ASSERT(elem.props.size() == 3); + MG_ASSERT(elem.props.size() == 4); } } { From 40835b8c9c6106b3c4cbb342eb66c7c48b8ba251 Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Mon, 16 Jan 2023 14:54:20 +0100 Subject: [PATCH 33/85] General clean-up --- src/query/v2/multiframe.hpp | 2 +- src/query/v2/plan/operator.cpp | 24 +--- src/query/v2/request_router.hpp | 37 +------ tests/simulation/request_router.cpp | 2 +- tests/simulation/test_cluster.hpp | 2 +- tests/unit/high_density_shard_create_scan.cpp | 2 +- tests/unit/machine_manager.cpp | 2 +- tests/unit/query_plan_checker.hpp | 104 ------------------ tests/unit/query_plan_checker_v2.hpp | 4 - tests/unit/query_v2_expression_evaluator.cpp | 5 +- tests/unit/query_v2_plan.cpp | 6 - 11 files changed, 12 insertions(+), 178 deletions(-) diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index 28c972ac4..a3b1e4d7d 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -203,7 +203,7 @@ class ValidFramesConsumer { ValidFramesConsumer(const ValidFramesConsumer &other) = default; ValidFramesConsumer(ValidFramesConsumer &&other) noexcept = default; ValidFramesConsumer &operator=(const ValidFramesConsumer &other) = default; - ValidFramesConsumer &operator=(ValidFramesConsumer &&other) noexcept = delete; + ValidFramesConsumer &operator=(ValidFramesConsumer &&other) noexcept = default; struct Iterator { using iterator_category = std::forward_iterator_tag; diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index ed0b51632..491241635 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -404,7 +404,7 @@ class DistributedScanAllAndFilterCursor : public Cursor { if (label_.has_value()) { request_label = request_router.LabelToName(*label_); } - current_batch = request_router.ScanVertices(request_label, std::nullopt); + current_batch = request_router.ScanVertices(request_label); } current_vertex_it = current_batch.begin(); request_state_ = State::COMPLETED; @@ -491,7 +491,7 @@ class DistributedScanAllByPrimaryKeyCursor : public Cursor { if (label_.has_value()) { request_label = request_router.LabelToName(*label_); } - current_batch_ = request_router.ScanVertices(request_label, std::nullopt); + current_batch_ = request_router.ScanVertices(request_label); } current_vertex_it_ = current_batch_.begin(); request_state_ = State::COMPLETED; @@ -516,29 +516,11 @@ class DistributedScanAllByPrimaryKeyCursor : public Cursor { pk.push_back(TypedValueToValue(primary_key->Accept(evaluator))); } - // Original - // current_batch_ = request_router.ScanVertices(request_label, pk); - - auto asd_debug = request_router.ScanVertices(request_label, pk); - - // VertexAccessor(Vertex v, std::vector<std::pair<PropertyId, Value>> props, - // const RequestRouterInterface *request_router); - - // VertexAccessor(Vertex v, std::map<PropertyId, Value> &&props, const RequestRouterInterface *request_router); - // VertexAccessor(Vertex v, const std::map<PropertyId, Value> &props, const RequestRouterInterface - // *request_router); - - // struct Vertex { - // VertexId id; - // std::vector<Label> labels; - // friend bool operator==(const Vertex &lhs, const Vertex &rhs) { return lhs.id == rhs.id; } - // }; - msgs::Label label = {.id = msgs::LabelId::FromUint(label_->AsUint())}; msgs::GetPropertiesRequest req = {.vertex_ids = {std::make_pair(label, pk)}}; auto get_prop_result = request_router.GetProperties(req); - // MG_ASSERT(get_prop_result.size() <= 1); + MG_ASSERT(get_prop_result.size() <= 1); if (get_prop_result.empty()) { current_batch_ = std::vector<VertexAccessor>{}; diff --git a/src/query/v2/request_router.hpp b/src/query/v2/request_router.hpp index d83dd6928..0afee54f0 100644 --- a/src/query/v2/request_router.hpp +++ b/src/query/v2/request_router.hpp @@ -97,8 +97,7 @@ class RequestRouterInterface { virtual void StartTransaction() = 0; virtual void Commit() = 0; - virtual std::vector<VertexAccessor> ScanVertices(std::optional<std::string> label, - std::optional<std::vector<msgs::Value>> primary_key) = 0; + virtual std::vector<VertexAccessor> ScanVertices(std::optional<std::string> label) = 0; virtual std::vector<msgs::CreateVerticesResponse> CreateVertices(std::vector<msgs::NewVertex> new_vertices) = 0; virtual std::vector<msgs::ExpandOneResultRow> ExpandOne(msgs::ExpandOneRequest request) = 0; virtual std::vector<msgs::CreateExpandResponse> CreateExpand(std::vector<msgs::NewExpand> new_edges) = 0; @@ -241,15 +240,9 @@ class RequestRouter : public RequestRouterInterface { bool IsPrimaryLabel(storage::v3::LabelId label) const override { return shards_map_.label_spaces.contains(label); } // TODO(kostasrim) Simplify return result - std::vector<VertexAccessor> ScanVertices(std::optional<std::string> label, - std::optional<std::vector<msgs::Value>> primary_key) override { + std::vector<VertexAccessor> ScanVertices(std::optional<std::string> label) override { // create requests - std::vector<ShardRequestState<msgs::ScanVerticesRequest>> requests_to_be_sent; - if (primary_key) { - requests_to_be_sent = RequestsForScanVertexByPrimaryKey(label, *primary_key); - } else { - requests_to_be_sent = RequestsForScanVertices(label); - } + auto requests_to_be_sent = RequestsForScanVertices(label); spdlog::trace("created {} ScanVertices requests", requests_to_be_sent.size()); @@ -372,7 +365,6 @@ class RequestRouter : public RequestRouterInterface { } std::vector<msgs::GetPropertiesResultRow> GetProperties(msgs::GetPropertiesRequest requests) override { - // Add HLC to GetProperties request requests.transaction_id = transaction_id_; // create requests std::vector<ShardRequestState<msgs::GetPropertiesRequest>> requests_to_be_sent = @@ -517,29 +509,6 @@ class RequestRouter : public RequestRouterInterface { return requests; } - std::vector<ShardRequestState<msgs::ScanVerticesRequest>> RequestsForScanVertexByPrimaryKey( - const std::optional<std::string> &label, const std::vector<msgs::Value> &primary_key) { - const auto label_id = shards_map_.GetLabelId(*label); - MG_ASSERT(label_id); - MG_ASSERT(IsPrimaryLabel(*label_id)); - std::vector<ShardRequestState<msgs::ScanVerticesRequest>> requests = {}; - - auto pk_containing_shard = - shards_map_.GetShardForKey(*label, storage::conversions::ConvertPropertyVector(primary_key)); - - msgs::ScanVerticesRequest request; - request.transaction_id = transaction_id_; - request.batch_limit = 1; - request.start_id.second = primary_key; - - ShardRequestState<msgs::ScanVerticesRequest> shard_request_state{ - .shard = pk_containing_shard, - .request = std::move(request), - }; - requests.emplace_back(std::move(shard_request_state)); - return requests; - } - std::vector<ShardRequestState<msgs::ExpandOneRequest>> RequestsForExpandOne(const msgs::ExpandOneRequest &request) { std::map<ShardMetadata, msgs::ExpandOneRequest> per_shard_request_table; msgs::ExpandOneRequest top_level_rqst_template = request; diff --git a/tests/simulation/request_router.cpp b/tests/simulation/request_router.cpp index 10976baa9..037674b66 100644 --- a/tests/simulation/request_router.cpp +++ b/tests/simulation/request_router.cpp @@ -154,7 +154,7 @@ void RunStorageRaft(Raft<IoImpl, MockedShardRsm, WriteRequests, WriteResponses, } void TestScanVertices(query::v2::RequestRouterInterface &request_router) { - auto result = request_router.ScanVertices("test_label", std::nullopt); + auto result = request_router.ScanVertices("test_label"); MG_ASSERT(result.size() == 2); { auto prop = result[0].GetProperty(msgs::PropertyId::FromUint(0)); diff --git a/tests/simulation/test_cluster.hpp b/tests/simulation/test_cluster.hpp index 699d9f8c7..791b45faa 100644 --- a/tests/simulation/test_cluster.hpp +++ b/tests/simulation/test_cluster.hpp @@ -182,7 +182,7 @@ void ExecuteOp(query::v2::RequestRouter<SimulatorTransport> &request_router, std void ExecuteOp(query::v2::RequestRouter<SimulatorTransport> &request_router, std::set<CompoundKey> &correctness_model, ScanAll scan_all) { - auto results = request_router.ScanVertices("test_label", std::nullopt); + auto results = request_router.ScanVertices("test_label"); RC_ASSERT(results.size() == correctness_model.size()); diff --git a/tests/unit/high_density_shard_create_scan.cpp b/tests/unit/high_density_shard_create_scan.cpp index a9647c795..4a90b98b2 100644 --- a/tests/unit/high_density_shard_create_scan.cpp +++ b/tests/unit/high_density_shard_create_scan.cpp @@ -192,7 +192,7 @@ void ExecuteOp(query::v2::RequestRouter<LocalTransport> &request_router, std::se void ExecuteOp(query::v2::RequestRouter<LocalTransport> &request_router, std::set<CompoundKey> &correctness_model, ScanAll scan_all) { - auto results = request_router.ScanVertices("test_label", std::nullopt); + auto results = request_router.ScanVertices("test_label"); spdlog::error("got {} results, model size is {}", results.size(), correctness_model.size()); EXPECT_EQ(results.size(), correctness_model.size()); diff --git a/tests/unit/machine_manager.cpp b/tests/unit/machine_manager.cpp index 8d0670e81..6cc6a3ff1 100644 --- a/tests/unit/machine_manager.cpp +++ b/tests/unit/machine_manager.cpp @@ -111,7 +111,7 @@ ShardMap TestShardMap() { template <typename RequestRouter> void TestScanAll(RequestRouter &request_router) { - auto result = request_router.ScanVertices(kLabelName, std::nullopt); + auto result = request_router.ScanVertices(kLabelName); EXPECT_EQ(result.size(), 2); } diff --git a/tests/unit/query_plan_checker.hpp b/tests/unit/query_plan_checker.hpp index 12ea2a683..da910ff99 100644 --- a/tests/unit/query_plan_checker.hpp +++ b/tests/unit/query_plan_checker.hpp @@ -516,108 +516,4 @@ class FakeDbAccessor { std::vector<std::tuple<memgraph::storage::LabelId, memgraph::storage::PropertyId, int64_t>> 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<std::string, memgraph::storage::v3::LabelId> primary_labels_; -// std::unordered_map<std::string, memgraph::storage::v3::LabelId> secondary_labels_; -// std::unordered_map<std::string, memgraph::storage::EdgeTypeId> edge_types_; -// std::unordered_map<std::string, memgraph::storage::PropertyId> primary_properties_; -// std::unordered_map<std::string, memgraph::storage::PropertyId> secondary_properties_; - -// std::unordered_map<memgraph::storage::v3::LabelId, int64_t> label_index_; -// std::vector<std::tuple<memgraph::storage::v3::LabelId, memgraph::storage::PropertyId, int64_t>> -// 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 index 85c625d59..1a971c4f0 100644 --- a/tests/unit/query_plan_checker_v2.hpp +++ b/tests/unit/query_plan_checker_v2.hpp @@ -181,10 +181,6 @@ class ExpectScanAllByPrimaryKey : public OpChecker<v2::plan::ScanAllByPrimaryKey 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_) { diff --git a/tests/unit/query_v2_expression_evaluator.cpp b/tests/unit/query_v2_expression_evaluator.cpp index 098a94981..ffbebb4ba 100644 --- a/tests/unit/query_v2_expression_evaluator.cpp +++ b/tests/unit/query_v2_expression_evaluator.cpp @@ -84,10 +84,7 @@ class MockedRequestRouter : public RequestRouterInterface { } void StartTransaction() override {} void Commit() override {} - std::vector<VertexAccessor> ScanVertices(std::optional<std::string> /* label */, - std::optional<std::vector<msgs::Value>> /*primary_key*/) override { - return {}; - } + std::vector<VertexAccessor> ScanVertices(std::optional<std::string> /* label */) override { return {}; } std::vector<CreateVerticesResponse> CreateVertices( std::vector<memgraph::msgs::NewVertex> /* new_vertices */) override { diff --git a/tests/unit/query_v2_plan.cpp b/tests/unit/query_v2_plan.cpp index 56f90a690..215b6b8a3 100644 --- a/tests/unit/query_v2_plan.cpp +++ b/tests/unit/query_v2_plan.cpp @@ -86,12 +86,6 @@ class TestPlanner : public ::testing::Test {}; using PlannerTypes = ::testing::Types<Planner>; -// void DeleteListContent(std::list<BaseOpChecker *> *list) { -// for (BaseOpChecker *ptr : *list) { -// delete ptr; -// } -// } - TYPED_TEST_CASE(TestPlanner, PlannerTypes); TYPED_TEST(TestPlanner, MatchFilterPropIsNotNull) { From b91b16de963b3c7b2cd2202dc2b5602aa0d4e915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Tue, 17 Jan 2023 07:06:25 +0100 Subject: [PATCH 34/85] Fix `Interpreter::PullMultiple` for queries that return some values --- src/query/v2/interpreter.cpp | 20 +++++++++++--------- src/query/v2/multiframe.cpp | 4 ---- src/query/v2/multiframe.hpp | 1 - 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/query/v2/interpreter.cpp b/src/query/v2/interpreter.cpp index 594942aec..23386bd1c 100644 --- a/src/query/v2/interpreter.cpp +++ b/src/query/v2/interpreter.cpp @@ -704,7 +704,6 @@ PullPlan::PullPlan(const std::shared_ptr<CachedPlan> plan, const Parameters &par ctx_.request_router = request_router; ctx_.edge_ids_alloc = &interpreter_context->edge_ids_alloc; } - std::optional<plan::ProfilingStatsWithTotalTime> PullPlan::PullMultiple(AnyStream *stream, std::optional<int> n, const std::vector<Symbol> &output_symbols, std::map<std::string, TypedValue> *summary) { @@ -734,7 +733,7 @@ std::optional<plan::ProfilingStatsWithTotalTime> PullPlan::PullMultiple(AnyStrea // Returns true if a result was pulled. const auto pull_result = [&]() -> bool { cursor_->PullMultiple(multi_frame_, ctx_); - return !multi_frame_.HasInvalidFrame(); + return multi_frame_.HasValidFrame(); }; const auto stream_values = [&output_symbols, &stream](const Frame &frame) { @@ -755,13 +754,14 @@ std::optional<plan::ProfilingStatsWithTotalTime> PullPlan::PullMultiple(AnyStrea int i = 0; if (has_unsent_results_ && !output_symbols.empty()) { // stream unsent results from previous pull - - auto iterator_for_valid_frame_only = multi_frame_.GetValidFramesReader(); - for (const auto &frame : iterator_for_valid_frame_only) { + for (auto &frame : multi_frame_.GetValidFramesConsumer()) { stream_values(frame); + frame.MakeInvalid(); ++i; + if (i == n) { + break; + } } - multi_frame_.MakeAllFramesInvalid(); } for (; !n || i < n;) { @@ -770,13 +770,15 @@ std::optional<plan::ProfilingStatsWithTotalTime> PullPlan::PullMultiple(AnyStrea } if (!output_symbols.empty()) { - auto iterator_for_valid_frame_only = multi_frame_.GetValidFramesReader(); - for (const auto &frame : iterator_for_valid_frame_only) { + for (auto &frame : multi_frame_.GetValidFramesConsumer()) { stream_values(frame); + frame.MakeInvalid(); ++i; + if (i == n) { + break; + } } } - multi_frame_.MakeAllFramesInvalid(); } // If we finished because we streamed the requested n results, diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index 0ddfd3aa7..835cdbc0f 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -48,10 +48,6 @@ bool MultiFrame::HasValidFrame() const noexcept { return std::any_of(frames_.begin(), frames_.end(), [](auto &frame) { return frame.IsValid(); }); } -bool MultiFrame::HasInvalidFrame() const noexcept { - return std::any_of(frames_.rbegin(), frames_.rend(), [](auto &frame) { return !frame.IsValid(); }); -} - // NOLINTNEXTLINE (bugprone-exception-escape) void MultiFrame::DefragmentValidFrames() noexcept { /* diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index f84fe6421..5f821bb6b 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -81,7 +81,6 @@ class MultiFrame { void MakeAllFramesInvalid() noexcept; bool HasValidFrame() const noexcept; - bool HasInvalidFrame() const noexcept; inline utils::MemoryResource *GetMemoryResource() { return frames_[0].GetMemoryResource(); } From 36891c119b28d472e4d831af2f318cc36c3fd670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Tue, 17 Jan 2023 07:17:53 +0100 Subject: [PATCH 35/85] Remove unnecessary state from `DistributedScanAllAndFilterCursor` --- src/query/v2/plan/operator.cpp | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index a57799367..1e84eaae5 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -467,31 +467,24 @@ class DistributedScanAllAndFilterCursor : public Cursor { current_batch_ = request_router.ScanVertices(request_label); } current_vertex_it_ = current_batch_.begin(); - request_state_ = State::COMPLETED; return !current_batch_.empty(); } bool Pull(Frame &frame, ExecutionContext &context) override { SCOPED_PROFILE_OP(op_name_); - auto &request_router = *context.request_router; while (true) { if (MustAbort(context)) { throw HintedAbortError(); } - if (request_state_ == State::INITIALIZING) { - if (!input_cursor_->Pull(frame, context)) { + if (current_vertex_it_ == current_batch_.end()) { + ResetExecutionState(); + if (!input_cursor_->Pull(frame, context) || !MakeRequest(*context.request_router, context)) { return false; } } - if (current_vertex_it_ == current_batch_.end() && - (request_state_ == State::COMPLETED || !MakeRequest(request_router, context))) { - ResetExecutionState(); - continue; - } - frame[output_symbol_] = TypedValue(std::move(*current_vertex_it_)); ++current_vertex_it_; return true; @@ -568,7 +561,6 @@ class DistributedScanAllAndFilterCursor : public Cursor { void ResetExecutionState() { current_batch_.clear(); current_vertex_it_ = current_batch_.end(); - request_state_ = State::INITIALIZING; } void Reset() override { @@ -581,15 +573,13 @@ class DistributedScanAllAndFilterCursor : public Cursor { const UniqueCursorPtr input_cursor_; const char *op_name_; std::vector<VertexAccessor> current_batch_; - std::vector<VertexAccessor>::iterator current_vertex_it_; - State request_state_ = State::INITIALIZING; + std::vector<VertexAccessor>::iterator current_vertex_it_{current_batch_.begin()}; std::optional<storage::v3::LabelId> label_; std::optional<std::pair<storage::v3::PropertyId, Expression *>> property_expression_pair_; std::optional<std::vector<Expression *>> filter_expressions_; std::optional<MultiFrame> own_multi_frames_; std::optional<ValidFramesConsumer> valid_frames_consumer_; ValidFramesConsumer::Iterator valid_frames_it_; - std::queue<FrameWithValidity> frames_buffer_; }; ScanAll::ScanAll(const std::shared_ptr<LogicalOperator> &input, Symbol output_symbol, storage::v3::View view) @@ -597,8 +587,6 @@ ScanAll::ScanAll(const std::shared_ptr<LogicalOperator> &input, Symbol output_sy ACCEPT_WITH_INPUT(ScanAll) -class DistributedScanAllCursor; - UniqueCursorPtr ScanAll::MakeCursor(utils::MemoryResource *mem) const { EventCounter::IncrementCounter(EventCounter::ScanAllOperator); From d11d5c3fa93c02383566f712775c669861e32ae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Tue, 17 Jan 2023 08:33:40 +0100 Subject: [PATCH 36/85] Make special member functions of `MultiFrame` iterators consistent --- src/query/v2/multiframe.hpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index 5f821bb6b..28009eb5d 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -96,9 +96,9 @@ class ValidFramesReader { ~ValidFramesReader() = default; ValidFramesReader(const ValidFramesReader &other) = delete; - ValidFramesReader(ValidFramesReader &&other) noexcept = delete; + ValidFramesReader(ValidFramesReader &&other) noexcept = default; ValidFramesReader &operator=(const ValidFramesReader &other) = delete; - ValidFramesReader &operator=(ValidFramesReader &&other) noexcept = delete; + ValidFramesReader &operator=(ValidFramesReader &&other) noexcept = default; struct Iterator { using iterator_category = std::forward_iterator_tag; @@ -146,9 +146,9 @@ class ValidFramesModifier { ~ValidFramesModifier() = default; ValidFramesModifier(const ValidFramesModifier &other) = delete; - ValidFramesModifier(ValidFramesModifier &&other) noexcept = delete; + ValidFramesModifier(ValidFramesModifier &&other) noexcept = default; ValidFramesModifier &operator=(const ValidFramesModifier &other) = delete; - ValidFramesModifier &operator=(ValidFramesModifier &&other) noexcept = delete; + ValidFramesModifier &operator=(ValidFramesModifier &&other) noexcept = default; struct Iterator { using iterator_category = std::forward_iterator_tag; @@ -200,10 +200,10 @@ class ValidFramesConsumer { explicit ValidFramesConsumer(MultiFrame &multiframe); ~ValidFramesConsumer() noexcept; - ValidFramesConsumer(const ValidFramesConsumer &other) = default; + ValidFramesConsumer(const ValidFramesConsumer &other) = delete; ValidFramesConsumer(ValidFramesConsumer &&other) noexcept = default; - ValidFramesConsumer &operator=(const ValidFramesConsumer &other) = default; - ValidFramesConsumer &operator=(ValidFramesConsumer &&other) noexcept = delete; + ValidFramesConsumer &operator=(const ValidFramesConsumer &other) = delete; + ValidFramesConsumer &operator=(ValidFramesConsumer &&other) noexcept = default; struct Iterator { using iterator_category = std::forward_iterator_tag; From 57690c5390b0ba6e59ffe9410a0b95c07681239c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Tue, 17 Jan 2023 08:34:08 +0100 Subject: [PATCH 37/85] Refactor `DistributedScanAllAndFilterCursor` --- src/query/v2/plan/operator.cpp | 81 +++++++++++++++------------------- 1 file changed, 36 insertions(+), 45 deletions(-) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index 1e84eaae5..9841791e6 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -457,14 +457,14 @@ class DistributedScanAllAndFilterCursor : public Cursor { using VertexAccessor = accessors::VertexAccessor; - bool MakeRequest(RequestRouterInterface &request_router, ExecutionContext &context) { + bool MakeRequest(ExecutionContext &context) { { SCOPED_REQUEST_WAIT_PROFILE; std::optional<std::string> request_label = std::nullopt; if (label_.has_value()) { - request_label = request_router.LabelToName(*label_); + request_label = context.request_router->LabelToName(*label_); } - current_batch_ = request_router.ScanVertices(request_label); + current_batch_ = context.request_router->ScanVertices(request_label); } current_vertex_it_ = current_batch_.begin(); return !current_batch_.empty(); @@ -480,7 +480,7 @@ class DistributedScanAllAndFilterCursor : public Cursor { if (current_vertex_it_ == current_batch_.end()) { ResetExecutionState(); - if (!input_cursor_->Pull(frame, context) || !MakeRequest(*context.request_router, context)) { + if (!input_cursor_->Pull(frame, context) || !MakeRequest(context)) { return false; } } @@ -491,66 +491,57 @@ class DistributedScanAllAndFilterCursor : public Cursor { } } - void PrepareNextFrames(ExecutionContext &context) { - auto &request_router = *context.request_router; - - input_cursor_->PullMultiple(*own_multi_frames_, context); - valid_frames_consumer_ = own_multi_frames_->GetValidFramesConsumer(); - valid_frames_it_ = valid_frames_consumer_->begin(); - - MakeRequest(request_router, context); + bool PullNextFrames(ExecutionContext &context) { + input_cursor_->PullMultiple(*own_multi_frame_, context); + own_frames_consumer_ = own_multi_frame_->GetValidFramesConsumer(); + own_frames_it_ = own_frames_consumer_->begin(); + return own_multi_frame_->HasValidFrame(); } - inline bool HasNextFrame() { - return current_vertex_it_ != current_batch_.end() && valid_frames_it_ != valid_frames_consumer_->end(); + inline bool HasMoreResult() { + return current_vertex_it_ != current_batch_.end() && own_frames_it_ != own_frames_consumer_->end(); } - FrameWithValidity GetNextFrame(ExecutionContext &context) { - MG_ASSERT(HasNextFrame()); + bool PopulateFrame(ExecutionContext &context, FrameWithValidity &frame) { + MG_ASSERT(HasMoreResult()); - auto frame = *valid_frames_it_; + frame = *own_frames_it_; frame[output_symbol_] = TypedValue(*current_vertex_it_); ++current_vertex_it_; if (current_vertex_it_ == current_batch_.end()) { - valid_frames_it_->MakeInvalid(); - ++valid_frames_it_; + own_frames_it_->MakeInvalid(); + ++own_frames_it_; - if (valid_frames_it_ == valid_frames_consumer_->end()) { - PrepareNextFrames(context); - } else { - current_vertex_it_ = current_batch_.begin(); + current_vertex_it_ = current_batch_.begin(); + + if (own_frames_it_ == own_frames_consumer_->end()) { + return PullNextFrames(context); } }; - - return frame; + return true; } void PullMultiple(MultiFrame &input_multi_frame, ExecutionContext &context) override { SCOPED_PROFILE_OP(op_name_); - if (!own_multi_frames_.has_value()) { - own_multi_frames_.emplace(MultiFrame(input_multi_frame.GetFirstFrame().elems().size(), - kNumberOfFramesInMultiframe, input_multi_frame.GetMemoryResource())); - PrepareNextFrames(context); + if (!own_multi_frame_.has_value()) { + own_multi_frame_.emplace(MultiFrame(input_multi_frame.GetFirstFrame().elems().size(), kNumberOfFramesInMultiframe, + input_multi_frame.GetMemoryResource())); + + MakeRequest(context); + PullNextFrames(context); } - while (true) { + if (!HasMoreResult()) { + return; + } + + for (auto &frame : input_multi_frame.GetInvalidFramesPopulator()) { if (MustAbort(context)) { throw HintedAbortError(); } - - auto invalid_frames_populator = input_multi_frame.GetInvalidFramesPopulator(); - auto invalid_frame_it = invalid_frames_populator.begin(); - auto has_modified_at_least_one_frame = false; - - while (invalid_frames_populator.end() != invalid_frame_it && HasNextFrame()) { - has_modified_at_least_one_frame = true; - *invalid_frame_it = GetNextFrame(context); - ++invalid_frame_it; - } - - if (!has_modified_at_least_one_frame) { + if (!PopulateFrame(context, frame)) { return; } } @@ -577,9 +568,9 @@ class DistributedScanAllAndFilterCursor : public Cursor { std::optional<storage::v3::LabelId> label_; std::optional<std::pair<storage::v3::PropertyId, Expression *>> property_expression_pair_; std::optional<std::vector<Expression *>> filter_expressions_; - std::optional<MultiFrame> own_multi_frames_; - std::optional<ValidFramesConsumer> valid_frames_consumer_; - ValidFramesConsumer::Iterator valid_frames_it_; + std::optional<MultiFrame> own_multi_frame_; + std::optional<ValidFramesConsumer> own_frames_consumer_; + ValidFramesConsumer::Iterator own_frames_it_; }; ScanAll::ScanAll(const std::shared_ptr<LogicalOperator> &input, Symbol output_symbol, storage::v3::View view) From b38a9b9c901c24c3bb99d62465c42282398aea45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Tue, 17 Jan 2023 20:25:10 +0100 Subject: [PATCH 38/85] Use `tests` namespace for tests --- tests/unit/mock_helpers.hpp | 6 +++--- tests/unit/query_v2_create_expand_multiframe.cpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/unit/mock_helpers.hpp b/tests/unit/mock_helpers.hpp index 6c03889ba..c7994586a 100644 --- a/tests/unit/mock_helpers.hpp +++ b/tests/unit/mock_helpers.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -18,7 +18,7 @@ #include "query/v2/plan/operator.hpp" #include "query/v2/request_router.hpp" -namespace memgraph::query::v2 { +namespace memgraph::query::v2::tests { class MockedRequestRouter : public RequestRouterInterface { public: MOCK_METHOD(std::vector<VertexAccessor>, ScanVertices, (std::optional<std::string> label)); @@ -79,4 +79,4 @@ inline MockedLogicalOperator &BaseToMock(plan::LogicalOperator &op) { inline MockedCursor &BaseToMock(plan::Cursor &cursor) { return dynamic_cast<MockedCursor &>(cursor); } -} // namespace memgraph::query::v2 +} // namespace memgraph::query::v2::tests diff --git a/tests/unit/query_v2_create_expand_multiframe.cpp b/tests/unit/query_v2_create_expand_multiframe.cpp index 8720656fd..ebdc4a9a7 100644 --- a/tests/unit/query_v2_create_expand_multiframe.cpp +++ b/tests/unit/query_v2_create_expand_multiframe.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -23,7 +23,7 @@ #include "utils/logging.hpp" #include "utils/memory.hpp" -namespace memgraph::query::v2 { +namespace memgraph::query::v2::tests { MultiFrame CreateMultiFrame(const size_t max_pos, const Symbol &src, const Symbol &dst, MockedRequestRouter *router) { static constexpr size_t number_of_frames = 100; @@ -91,4 +91,4 @@ TEST(CreateExpandTest, Cursor) { EXPECT_EQ(number_of_invalid_frames, 99); } -} // namespace memgraph::query::v2 +} // namespace memgraph::query::v2::tests From a3b1676c42bb8a6a69d872ace5f0b8d75891ea63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Tue, 17 Jan 2023 20:25:28 +0100 Subject: [PATCH 39/85] Separate include blocks --- tests/unit/mock_helpers.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/mock_helpers.hpp b/tests/unit/mock_helpers.hpp index c7994586a..c522b8602 100644 --- a/tests/unit/mock_helpers.hpp +++ b/tests/unit/mock_helpers.hpp @@ -13,6 +13,7 @@ #include <gmock/gmock.h> #include <gtest/gtest.h> + #include "query/v2/common.hpp" #include "query/v2/context.hpp" #include "query/v2/plan/operator.hpp" From 7fb828bca38ab8e8929bc40494255f70645797b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Tue, 17 Jan 2023 20:32:00 +0100 Subject: [PATCH 40/85] Update outdated comments --- src/query/v2/plan/operator.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index 7ac02b451..49d25f4e2 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -217,9 +217,7 @@ class DistributedCreateNodeCursor : public Cursor { msgs::NewVertex rqst; MG_ASSERT(!node_info_.labels.empty(), "Cannot determine primary label"); const auto primary_label = node_info_.labels[0]; - // TODO(jbajic) Fix properties not send, - // suggestion: ignore distinction between properties and primary keys - // since schema validation is done on storage side + // TODO(jbajic) Send also the properties that are not part of primary key ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, nullptr, storage::v3::View::NEW); if (const auto *node_info_properties = std::get_if<PropertiesMapList>(&node_info_.properties)) { @@ -272,9 +270,7 @@ class DistributedCreateNodeCursor : public Cursor { MG_ASSERT(!node_info_.labels.empty(), "Cannot determine primary label"); const auto primary_label = node_info_.labels[0]; MG_ASSERT(context.request_router->IsPrimaryLabel(primary_label), "First label has to be a primary label!"); - // TODO(jbajic) Fix properties not send, - // suggestion: ignore distinction between properties and primary keys - // since schema validation is done on storage side + // TODO(jbajic) Send also the properties that are not part of primary key ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, nullptr, storage::v3::View::NEW); if (const auto *node_info_properties = std::get_if<PropertiesMapList>(&node_info_.properties)) { From 81675106fd9c1b00c779f5157bc97265d3214489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Tue, 17 Jan 2023 20:33:14 +0100 Subject: [PATCH 41/85] Use `tests` namespace for tests --- tests/unit/query_v2_create_node_multiframe.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/query_v2_create_node_multiframe.cpp b/tests/unit/query_v2_create_node_multiframe.cpp index 014fff6ce..b298d2781 100644 --- a/tests/unit/query_v2_create_node_multiframe.cpp +++ b/tests/unit/query_v2_create_node_multiframe.cpp @@ -23,7 +23,7 @@ #include "storage/v3/shard.hpp" #include "utils/memory.hpp" -namespace memgraph::query::v2 { +namespace memgraph::query::v2::tests { MultiFrame CreateMultiFrame(const size_t max_pos) { static constexpr size_t frame_size = 100; MultiFrame multi_frame(max_pos, frame_size, utils::NewDeleteResource()); @@ -79,4 +79,4 @@ TEST(CreateNodeTest, CreateNodeCursor) { auto number_of_invalid_frames = std::distance(invalid_frames.begin(), invalid_frames.end()); EXPECT_EQ(number_of_invalid_frames, 99); } -} // namespace memgraph::query::v2 +} // namespace memgraph::query::v2::tests From 901da4c9b3ad3636aebb3bf4f0210f739ce35d60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Tue, 17 Jan 2023 21:01:22 +0100 Subject: [PATCH 42/85] Update `InvalidFramesPopulator` to follow the conventions --- src/query/v2/multiframe.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index 28009eb5d..e286e00a3 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -255,9 +255,9 @@ class InvalidFramesPopulator { ~InvalidFramesPopulator() = default; InvalidFramesPopulator(const InvalidFramesPopulator &other) = delete; - InvalidFramesPopulator(InvalidFramesPopulator &&other) noexcept = delete; + InvalidFramesPopulator(InvalidFramesPopulator &&other) noexcept = default; InvalidFramesPopulator &operator=(const InvalidFramesPopulator &other) = delete; - InvalidFramesPopulator &operator=(InvalidFramesPopulator &&other) noexcept = delete; + InvalidFramesPopulator &operator=(InvalidFramesPopulator &&other) noexcept = default; struct Iterator { using iterator_category = std::forward_iterator_tag; From 575361827e9b3a1e9772deb76b02530ccf2dea23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Tue, 17 Jan 2023 21:01:54 +0100 Subject: [PATCH 43/85] Add comment about invalid usage of `MutliFrame` --- src/query/v2/multiframe.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index e286e00a3..f464343b4 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -33,6 +33,7 @@ class MultiFrame { MultiFrame(size_t size_of_frame, size_t number_of_frames, utils::MemoryResource *execution_memory); ~MultiFrame() = default; + // Assigning and moving the MultiFrame is not allowed if any accessor from the above ones are alive. MultiFrame(const MultiFrame &other); MultiFrame(MultiFrame &&other) noexcept; MultiFrame &operator=(const MultiFrame &other) = delete; From f39a937323ccdec367eb37373cc15b6063b7539b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Wed, 18 Jan 2023 13:31:35 +0100 Subject: [PATCH 44/85] Add first, but buggy implementation --- src/expr/interpret/frame.hpp | 3 +- src/query/v2/plan/operator.cpp | 173 ++++++++++++++++++++++++++++++--- src/query/v2/requests.hpp | 46 +++++++++ 3 files changed, 206 insertions(+), 16 deletions(-) diff --git a/src/expr/interpret/frame.hpp b/src/expr/interpret/frame.hpp index 1cd6a99ce..9f4068226 100644 --- a/src/expr/interpret/frame.hpp +++ b/src/expr/interpret/frame.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -34,6 +34,7 @@ class Frame { const TypedValue &at(const Symbol &symbol) const { return elems_.at(symbol.position()); } auto &elems() { return elems_; } + const auto &elems() const { return elems_; } utils::MemoryResource *GetMemoryResource() const { return elems_.get_allocator().GetMemoryResource(); } diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index 9841791e6..900ec15a0 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -522,12 +522,12 @@ class DistributedScanAllAndFilterCursor : public Cursor { return true; } - void PullMultiple(MultiFrame &input_multi_frame, ExecutionContext &context) override { + void PullMultiple(MultiFrame &output_multi_frame, ExecutionContext &context) override { SCOPED_PROFILE_OP(op_name_); if (!own_multi_frame_.has_value()) { - own_multi_frame_.emplace(MultiFrame(input_multi_frame.GetFirstFrame().elems().size(), kNumberOfFramesInMultiframe, - input_multi_frame.GetMemoryResource())); + own_multi_frame_.emplace(MultiFrame(output_multi_frame.GetFirstFrame().elems().size(), + kNumberOfFramesInMultiframe, output_multi_frame.GetMemoryResource())); MakeRequest(context); PullNextFrames(context); @@ -537,7 +537,7 @@ class DistributedScanAllAndFilterCursor : public Cursor { return; } - for (auto &frame : input_multi_frame.GetInvalidFramesPopulator()) { + for (auto &frame : output_multi_frame.GetInvalidFramesPopulator()) { if (MustAbort(context)) { throw HintedAbortError(); } @@ -2629,7 +2629,7 @@ class DistributedCreateExpandCursor : public Cursor { class DistributedExpandCursor : public Cursor { public: - explicit DistributedExpandCursor(const Expand &self, utils::MemoryResource *mem) + DistributedExpandCursor(const Expand &self, utils::MemoryResource *mem) : self_(self), input_cursor_(self.input_->MakeCursor(mem)), current_in_edge_it_(current_in_edges_.begin()), @@ -2666,16 +2666,10 @@ class DistributedExpandCursor : public Cursor { throw std::runtime_error("EdgeDirection Both not implemented"); } }; - msgs::ExpandOneRequest request; - // to not fetch any properties of the edges - request.edge_properties.emplace(); - request.src_vertices.push_back(get_dst_vertex(edge, direction)); - request.direction = (direction == EdgeAtom::Direction::IN) ? msgs::EdgeDirection::OUT : msgs::EdgeDirection::IN; - auto result_rows = context.request_router->ExpandOne(std::move(request)); - MG_ASSERT(result_rows.size() == 1); - auto &result_row = result_rows.front(); - frame[self_.common_.node_symbol] = accessors::VertexAccessor( - msgs::Vertex{result_row.src_vertex}, result_row.src_vertex_properties, context.request_router); + + frame[self_.common_.node_symbol] = + accessors::VertexAccessor(msgs::Vertex{get_dst_vertex(edge, direction)}, + std::vector<std::pair<msgs::PropertyId, msgs::Value>>{}, context.request_router); } bool InitEdges(Frame &frame, ExecutionContext &context) { @@ -2784,6 +2778,149 @@ class DistributedExpandCursor : public Cursor { } } + void InitEdgesMultiple(ExecutionContext &context) { + TypedValue &vertex_value = (*own_frames_it_)[self_.input_symbol_]; + + if (vertex_value.IsNull()) { + return; + } + + ExpectType(self_.input_symbol_, vertex_value, TypedValue::Type::Vertex); + auto &vertex = vertex_value.ValueVertex(); + + const auto convert_edges = [&vertex, &context]( + std::vector<msgs::ExpandOneResultRow::EdgeWithSpecificProperties> &&edge_messages, + const EdgeAtom::Direction direction) { + std::vector<EdgeAccessor> edge_accessors; + edge_accessors.reserve(edge_messages.size()); + + switch (direction) { + case EdgeAtom::Direction::IN: { + for (auto &edge : edge_messages) { + edge_accessors.emplace_back(msgs::Edge{std::move(edge.other_end), vertex.Id(), {}, {edge.gid}, edge.type}, + context.request_router); + } + break; + } + case EdgeAtom::Direction::OUT: { + for (auto &edge : edge_messages) { + edge_accessors.emplace_back(msgs::Edge{vertex.Id(), std::move(edge.other_end), {}, {edge.gid}, edge.type}, + context.request_router); + } + break; + } + case EdgeAtom::Direction::BOTH: { + LOG_FATAL("Must indicate exact expansion direction here"); + } + } + return edge_accessors; + }; + + auto *result_row = vertex_id_to_result_row[vertex.Id()]; + current_in_edges_.clear(); + current_in_edges_ = + convert_edges(std::move(result_row->in_edges_with_specific_properties), EdgeAtom::Direction::IN); + current_in_edge_it_ = current_in_edges_.begin(); + current_out_edges_ = + convert_edges(std::move(result_row->out_edges_with_specific_properties), EdgeAtom::Direction::OUT); + current_out_edge_it_ = current_out_edges_.begin(); + vertex_id_to_result_row.erase(vertex.Id()); + } + + void PullEdgesFromStorage(ExecutionContext &context) { + // Input Vertex could be null if it is created by a failed optional match. In + // those cases we skip that input pull and continue with the next. + + msgs::ExpandOneRequest request; + request.direction = DirectionToMsgsDirection(self_.common_.direction); + // to not fetch any properties of the edges + request.edge_properties.emplace(); + for (const auto &frame : own_multi_frame_->GetValidFramesReader()) { + const auto &vertex_value = frame[self_.input_symbol_]; + + // Null check due to possible failed optional match. + MG_ASSERT(!vertex_value.IsNull()); + + ExpectType(self_.input_symbol_, vertex_value, TypedValue::Type::Vertex); + auto &vertex = vertex_value.ValueVertex(); + request.src_vertices.push_back(vertex.Id()); + } + + result_rows_ = std::invoke([&context, &request]() mutable { + SCOPED_REQUEST_WAIT_PROFILE; + return context.request_router->ExpandOne(std::move(request)); + }); + vertex_id_to_result_row.clear(); + for (auto &row : result_rows_) { + vertex_id_to_result_row[row.src_vertex.id] = &row; + } + } + + void PullMultiple(MultiFrame &output_multi_frame, ExecutionContext &context) override { + SCOPED_PROFILE_OP("DistributedExpandMF"); + MG_ASSERT(!self_.common_.existing_node); + EnsureOwnMultiFrameIsGood(output_multi_frame); + // A helper function for expanding a node from an edge. + + while (true) { + if (MustAbort(context)) throw HintedAbortError(); + if (own_frames_it_ == own_frames_consumer_->end()) { + input_cursor_->PullMultiple(*own_multi_frame_, context); + own_frames_consumer_ = own_multi_frame_->GetValidFramesConsumer(); + own_frames_it_ = own_frames_consumer_->begin(); + if (!own_multi_frame_->HasValidFrame()) { + break; + } + + PullEdgesFromStorage(context); + InitEdgesMultiple(context); + } + + while (own_frames_it_ != own_frames_consumer_->end()) { + if (current_in_edge_it_ == current_in_edges_.end() && current_out_edge_it_ == current_out_edges_.end()) { + own_frames_it_->MakeInvalid(); + ++own_frames_it_; + + InitEdgesMultiple(context); + } + + auto &input_frame = *own_frames_it_; + + auto output_frames_populator = output_multi_frame.GetInvalidFramesPopulator(); + + auto populate_edges = [this, &context, &output_frames_populator, &input_frame]( + std::vector<EdgeAccessor>::iterator ¤t, + const std::vector<EdgeAccessor>::iterator &end) { + for (auto output_frame_it = output_frames_populator.begin(); + output_frame_it != output_frames_populator.end() && current != end; ++output_frame_it) { + auto &edge = *current; + ++current; + auto &output_frame = *output_frame_it++; + output_frame = input_frame; + output_frame[self_.common_.edge_symbol] = edge; + PullDstVertex(output_frame, context, EdgeAtom::Direction::IN); + } + }; + populate_edges(current_in_edge_it_, current_in_edges_.end()); + populate_edges(current_out_edge_it_, current_out_edges_.end()); + + if (output_frames_populator.begin() == output_frames_populator.end()) { + return; + } + } + } + } + + void EnsureOwnMultiFrameIsGood(MultiFrame &output_multi_frame) { + if (!own_multi_frame_.has_value()) { + own_multi_frame_.emplace(MultiFrame(output_multi_frame.GetFirstFrame().elems().size(), + kNumberOfFramesInMultiframe, output_multi_frame.GetMemoryResource())); + own_frames_consumer_.emplace(own_multi_frame_->GetValidFramesConsumer()); + own_frames_it_ = own_frames_consumer_->begin(); + } + MG_ASSERT(output_multi_frame.GetFirstFrame().elems().size() == own_multi_frame_->GetFirstFrame().elems().size()); + } + void Shutdown() override { input_cursor_->Shutdown(); } void Reset() override { @@ -2801,6 +2938,12 @@ class DistributedExpandCursor : public Cursor { std::vector<EdgeAccessor> current_out_edges_; std::vector<EdgeAccessor>::iterator current_in_edge_it_; std::vector<EdgeAccessor>::iterator current_out_edge_it_; + std::optional<MultiFrame> own_multi_frame_; + std::optional<ValidFramesConsumer> own_frames_consumer_; + ValidFramesConsumer::Iterator own_frames_it_; + std::vector<msgs::ExpandOneResultRow> result_rows_; + // This won't work if any vertex id is duplicated in the input + std::unordered_map<msgs::VertexId, msgs::ExpandOneResultRow *> vertex_id_to_result_row; }; } // namespace memgraph::query::v2::plan diff --git a/src/query/v2/requests.hpp b/src/query/v2/requests.hpp index 2335fea7d..b2d7f9123 100644 --- a/src/query/v2/requests.hpp +++ b/src/query/v2/requests.hpp @@ -25,6 +25,7 @@ #include "storage/v3/id_types.hpp" #include "storage/v3/property_value.hpp" #include "storage/v3/result.hpp" +#include "utils/fnv.hpp" namespace memgraph::msgs { @@ -579,3 +580,48 @@ using WriteResponses = std::variant<CreateVerticesResponse, DeleteVerticesRespon CreateExpandResponse, DeleteEdgesResponse, UpdateEdgesResponse, CommitResponse>; } // namespace memgraph::msgs + +namespace std { + +template <> +struct hash<memgraph::msgs::Value>; + +template <> +struct hash<memgraph::msgs::VertexId> { + size_t operator()(const memgraph::msgs::VertexId &id) const { + using LabelId = memgraph::storage::v3::LabelId; + using Value = memgraph::msgs::Value; + return memgraph::utils::HashCombine<LabelId, std::vector<Value>, std::hash<LabelId>, + memgraph::utils::FnvCollection<std::vector<Value>, Value>>{}(id.first.id, + id.second); + } +}; + +template <> +struct hash<memgraph::msgs::Value> { + size_t operator()(const memgraph::msgs::Value &value) const { + using Type = memgraph::msgs::Value::Type; + switch (value.type) { + case Type::Null: + return std::hash<size_t>{}(0U); + case Type::Bool: + return std::hash<bool>{}(value.bool_v); + case Type::Int64: + return std::hash<int64_t>{}(value.int_v); + case Type::Double: + return std::hash<double>{}(value.double_v); + case Type::String: + return std::hash<std::string>{}(value.string_v); + case Type::List: + LOG_FATAL("Add hash for lists"); + case Type::Map: + LOG_FATAL("Add hash for maps"); + case Type::Vertex: + LOG_FATAL("Add hash for vertices"); + case Type::Edge: + LOG_FATAL("Add hash for edges"); + } + } +}; + +} // namespace std From 94a536a2b93af239dff985ca2a85e2d29a5e153e Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Wed, 18 Jan 2023 16:03:34 +0100 Subject: [PATCH 45/85] Fix hanging conditionvariable --- src/query/v2/interpreter.hpp | 5 +++-- src/query/v2/request_router.hpp | 5 +++-- tests/simulation/simulation_interpreter.hpp | 8 +++++++- tests/simulation/test_cluster.hpp | 3 ++- tests/unit/query_v2_expression_evaluator.cpp | 4 +++- 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/query/v2/interpreter.hpp b/src/query/v2/interpreter.hpp index 05b05dd8e..8a535bf3c 100644 --- a/src/query/v2/interpreter.hpp +++ b/src/query/v2/interpreter.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -301,9 +301,9 @@ class Interpreter final { private: struct QueryExecution { - std::optional<PreparedQuery> prepared_query; utils::MonotonicBufferResource execution_memory{kExecutionMemoryBlockSize}; utils::ResourceWithOutOfMemoryException execution_memory_with_exception{&execution_memory}; + std::optional<PreparedQuery> prepared_query; std::map<std::string, TypedValue> summary; std::vector<Notification> notifications; @@ -388,6 +388,7 @@ std::map<std::string, TypedValue> Interpreter::Pull(TStream *result_stream, std: // Wrap the (statically polymorphic) stream type into a common type which // the handler knows. AnyStream stream{result_stream, &query_execution->execution_memory}; + auto asd = query_execution->prepared_query->query_handler; const auto maybe_res = query_execution->prepared_query->query_handler(&stream, n); // Stream is using execution memory of the query_execution which // can be deleted after its execution so the stream should be cleared diff --git a/src/query/v2/request_router.hpp b/src/query/v2/request_router.hpp index 2b7b7c89a..d6c484e86 100644 --- a/src/query/v2/request_router.hpp +++ b/src/query/v2/request_router.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -119,6 +119,7 @@ class RequestRouterInterface { virtual bool IsPrimaryKey(storage::v3::LabelId primary_label, storage::v3::PropertyId property) const = 0; virtual std::optional<std::pair<uint64_t, uint64_t>> AllocateInitialEdgeIds(io::Address coordinator_address) = 0; + virtual void InstallSimulatorTicker(std::function<bool()> tick_simulator) = 0; }; // TODO(kostasrim)rename this class template @@ -143,7 +144,7 @@ class RequestRouter : public RequestRouterInterface { ~RequestRouter() override {} - void InstallSimulatorTicker(std::function<bool()> tick_simulator) { + void InstallSimulatorTicker(std::function<bool()> tick_simulator) override { notifier_.InstallSimulatorTicker(tick_simulator); } diff --git a/tests/simulation/simulation_interpreter.hpp b/tests/simulation/simulation_interpreter.hpp index 8e37f4f70..497ee7103 100644 --- a/tests/simulation/simulation_interpreter.hpp +++ b/tests/simulation/simulation_interpreter.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -42,6 +42,12 @@ class SimulatedInterpreter { SimulatedInterpreter &operator=(SimulatedInterpreter &&) = delete; ~SimulatedInterpreter() = default; + void InstallSimulatorTicker(Simulator &simulator) { + std::function<bool()> tick_simulator = simulator.GetSimulatorTickClosure(); + auto *request_router = interpreter_->GetRequestRouter(); + request_router->InstallSimulatorTicker(tick_simulator); + } + std::vector<ResultStream> RunQueries(const std::vector<std::string> &queries) { std::vector<ResultStream> results; results.reserve(queries.size()); diff --git a/tests/simulation/test_cluster.hpp b/tests/simulation/test_cluster.hpp index 9fa331818..f10e88e61 100644 --- a/tests/simulation/test_cluster.hpp +++ b/tests/simulation/test_cluster.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -314,6 +314,7 @@ std::pair<SimulatorStats, LatencyHistogramSummaries> RunClusterSimulationWithQue WaitForShardsToInitialize(coordinator_client); auto simulated_interpreter = io::simulator::SetUpInterpreter(coordinator_address, simulator); + simulated_interpreter.InstallSimulatorTicker(simulator); auto query_results = simulated_interpreter.RunQueries(queries); diff --git a/tests/unit/query_v2_expression_evaluator.cpp b/tests/unit/query_v2_expression_evaluator.cpp index b799bdafb..1127958d0 100644 --- a/tests/unit/query_v2_expression_evaluator.cpp +++ b/tests/unit/query_v2_expression_evaluator.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -129,6 +129,8 @@ class MockedRequestRouter : public RequestRouterInterface { return {}; } + void InstallSimulatorTicker(std::function<bool()> tick_simulator) override {} + private: void SetUpNameIdMappers() { std::unordered_map<uint64_t, std::string> id_to_name; From e888464de212a50cf90a28b52b777d8ca5a59a7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Wed, 18 Jan 2023 17:32:22 +0100 Subject: [PATCH 46/85] Implement automaton for `ExpandOneCursor` --- src/query/v2/multiframe.cpp | 4 ++ src/query/v2/multiframe.hpp | 1 + src/query/v2/plan/operator.cpp | 109 ++++++++++++++++++++------------- 3 files changed, 71 insertions(+), 43 deletions(-) diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index 835cdbc0f..14841d2c4 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -48,6 +48,10 @@ bool MultiFrame::HasValidFrame() const noexcept { return std::any_of(frames_.begin(), frames_.end(), [](auto &frame) { return frame.IsValid(); }); } +bool MultiFrame::HasInvalidFrame() const noexcept { + return std::any_of(frames_.begin(), frames_.end(), [](auto &frame) { return !frame.IsValid(); }); +} + // NOLINTNEXTLINE (bugprone-exception-escape) void MultiFrame::DefragmentValidFrames() noexcept { /* diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index f464343b4..6958ffbe8 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -82,6 +82,7 @@ class MultiFrame { void MakeAllFramesInvalid() noexcept; bool HasValidFrame() const noexcept; + bool HasInvalidFrame() const noexcept; inline utils::MemoryResource *GetMemoryResource() { return frames_[0].GetMemoryResource(); } diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index 900ec15a0..eba45b0b7 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -2827,9 +2827,15 @@ class DistributedExpandCursor : public Cursor { vertex_id_to_result_row.erase(vertex.Id()); } - void PullEdgesFromStorage(ExecutionContext &context) { - // Input Vertex could be null if it is created by a failed optional match. In - // those cases we skip that input pull and continue with the next. + bool PullInputFrames(ExecutionContext &context) { + input_cursor_->PullMultiple(*own_multi_frame_, context); + // These needs to be updated regardless of the result of the pull, otherwise the consumer and iterator might + // get corrupted because of the operations done on our MultiFrame. + own_frames_consumer_ = own_multi_frame_->GetValidFramesConsumer(); + own_frames_it_ = own_frames_consumer_->begin(); + if (!own_multi_frame_->HasValidFrame()) { + return false; + } msgs::ExpandOneRequest request; request.direction = DirectionToMsgsDirection(self_.common_.direction); @@ -2842,7 +2848,7 @@ class DistributedExpandCursor : public Cursor { MG_ASSERT(!vertex_value.IsNull()); ExpectType(self_.input_symbol_, vertex_value, TypedValue::Type::Vertex); - auto &vertex = vertex_value.ValueVertex(); + const auto &vertex = vertex_value.ValueVertex(); request.src_vertices.push_back(vertex.Id()); } @@ -2854,6 +2860,8 @@ class DistributedExpandCursor : public Cursor { for (auto &row : result_rows_) { vertex_id_to_result_row[row.src_vertex.id] = &row; } + + return true; } void PullMultiple(MultiFrame &output_multi_frame, ExecutionContext &context) override { @@ -2862,49 +2870,55 @@ class DistributedExpandCursor : public Cursor { EnsureOwnMultiFrameIsGood(output_multi_frame); // A helper function for expanding a node from an edge. + auto output_frames_populator = output_multi_frame.GetInvalidFramesPopulator(); + while (true) { - if (MustAbort(context)) throw HintedAbortError(); - if (own_frames_it_ == own_frames_consumer_->end()) { - input_cursor_->PullMultiple(*own_multi_frame_, context); - own_frames_consumer_ = own_multi_frame_->GetValidFramesConsumer(); - own_frames_it_ = own_frames_consumer_->begin(); - if (!own_multi_frame_->HasValidFrame()) { + switch (state_) { + case State::PullInputAndEdges: { + if (!PullInputFrames(context)) { + state_ = State::Exhausted; + return; + } + state_ = State::InitInOutEdgesIt; break; } - - PullEdgesFromStorage(context); - InitEdgesMultiple(context); - } - - while (own_frames_it_ != own_frames_consumer_->end()) { - if (current_in_edge_it_ == current_in_edges_.end() && current_out_edge_it_ == current_out_edges_.end()) { - own_frames_it_->MakeInvalid(); - ++own_frames_it_; - - InitEdgesMultiple(context); - } - - auto &input_frame = *own_frames_it_; - - auto output_frames_populator = output_multi_frame.GetInvalidFramesPopulator(); - - auto populate_edges = [this, &context, &output_frames_populator, &input_frame]( - std::vector<EdgeAccessor>::iterator ¤t, - const std::vector<EdgeAccessor>::iterator &end) { - for (auto output_frame_it = output_frames_populator.begin(); - output_frame_it != output_frames_populator.end() && current != end; ++output_frame_it) { - auto &edge = *current; - ++current; - auto &output_frame = *output_frame_it++; - output_frame = input_frame; - output_frame[self_.common_.edge_symbol] = edge; - PullDstVertex(output_frame, context, EdgeAtom::Direction::IN); + case State::InitInOutEdgesIt: { + if (own_frames_it_ == own_frames_consumer_->end()) { + state_ = State::PullInputAndEdges; + } else { + InitEdges(*own_frames_it_, context); + state_ = State::PopulateOutput; } - }; - populate_edges(current_in_edge_it_, current_in_edges_.end()); - populate_edges(current_out_edge_it_, current_out_edges_.end()); - - if (output_frames_populator.begin() == output_frames_populator.end()) { + break; + } + case State::PopulateOutput: { + if (!output_multi_frame.HasInvalidFrame()) { + return; + } + if (current_in_edge_it_ == current_in_edges_.end() && current_out_edge_it_ == current_out_edges_.end()) { + own_frames_it_->MakeInvalid(); + ++own_frames_it_; + state_ = State::InitInOutEdgesIt; + continue; + } + auto populate_edges = [this, &context, &output_frames_populator]( + const EdgeAtom::Direction direction, std::vector<EdgeAccessor>::iterator ¤t, + const std::vector<EdgeAccessor>::iterator &end) { + for (auto output_frame_it = output_frames_populator.begin(); + output_frame_it != output_frames_populator.end() && current != end; ++output_frame_it) { + auto &edge = *current; + ++current; + auto &output_frame = *output_frame_it++; + output_frame = *own_frames_it_; + output_frame[self_.common_.edge_symbol] = edge; + PullDstVertex(output_frame, context, direction); + } + }; + populate_edges(EdgeAtom::Direction::IN, current_in_edge_it_, current_in_edges_.end()); + populate_edges(EdgeAtom::Direction::OUT, current_out_edge_it_, current_out_edges_.end()); + break; + } + case State::Exhausted: { return; } } @@ -2925,6 +2939,12 @@ class DistributedExpandCursor : public Cursor { void Reset() override { input_cursor_->Reset(); + vertex_id_to_result_row.clear(); + result_rows_.clear(); + own_frames_it_ = ValidFramesConsumer::Iterator{}; + own_frames_consumer_.reset(); + own_multi_frame_->MakeAllFramesInvalid(); + state_ = State::PullInputAndEdges; current_in_edges_.clear(); current_out_edges_.clear(); current_in_edge_it_ = current_in_edges_.end(); @@ -2932,12 +2952,15 @@ class DistributedExpandCursor : public Cursor { } private: + enum class State { PullInputAndEdges, InitInOutEdgesIt, PopulateOutput, Exhausted }; + const Expand &self_; const UniqueCursorPtr input_cursor_; std::vector<EdgeAccessor> current_in_edges_; std::vector<EdgeAccessor> current_out_edges_; std::vector<EdgeAccessor>::iterator current_in_edge_it_; std::vector<EdgeAccessor>::iterator current_out_edge_it_; + State state_{State::PullInputAndEdges}; std::optional<MultiFrame> own_multi_frame_; std::optional<ValidFramesConsumer> own_frames_consumer_; ValidFramesConsumer::Iterator own_frames_it_; From a0274bbdd91759ef303d091ab38d8de179e56e98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Thu, 19 Jan 2023 10:42:09 +0100 Subject: [PATCH 47/85] Prevent reexecution of cursor when no output symbols are present --- src/query/v2/interpreter.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/query/v2/interpreter.cpp b/src/query/v2/interpreter.cpp index 23386bd1c..b447a0a2b 100644 --- a/src/query/v2/interpreter.cpp +++ b/src/query/v2/interpreter.cpp @@ -778,6 +778,8 @@ std::optional<plan::ProfilingStatsWithTotalTime> PullPlan::PullMultiple(AnyStrea break; } } + } else { + multi_frame_.MakeAllFramesInvalid(); } } From 52baaf80303561746a4cd8f6553710ceca36ff0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Thu, 19 Jan 2023 10:43:07 +0100 Subject: [PATCH 48/85] Detect when no work should be done because of lack of input data in cursors --- src/query/v2/plan/operator.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index d343afbfc..82a1df4ee 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -193,6 +193,9 @@ class DistributedCreateNodeCursor : public Cursor { SCOPED_PROFILE_OP("CreateNodeMF"); input_cursor_->PullMultiple(multi_frame, context); auto *request_router = context.request_router; + if (!multi_frame.HasValidFrame()) { + return; + } { SCOPED_REQUEST_WAIT_PROFILE; request_router->CreateVertices(NodeCreationInfoToRequests(context, multi_frame)); @@ -2484,6 +2487,9 @@ class DistributedCreateExpandCursor : public Cursor { void PullMultiple(MultiFrame &multi_frame, ExecutionContext &context) override { SCOPED_PROFILE_OP("CreateExpandMF"); input_cursor_->PullMultiple(multi_frame, context); + if (!multi_frame.HasValidFrame()) { + return; + } auto request_vertices = ExpandCreationInfoToRequests(multi_frame, context); { SCOPED_REQUEST_WAIT_PROFILE; From d1548c9253d82b24b4519b0217dec2dc945d9643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Thu, 19 Jan 2023 10:43:54 +0100 Subject: [PATCH 49/85] Eliminate fully --- src/query/v2/plan/operator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index 82a1df4ee..4381da0dd 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -2922,7 +2922,7 @@ class DistributedExpandCursor : public Cursor { output_frame_it != output_frames_populator.end() && current != end; ++output_frame_it) { auto &edge = *current; ++current; - auto &output_frame = *output_frame_it++; + auto &output_frame = *output_frame_it; output_frame = *own_frames_it_; output_frame[self_.common_.edge_symbol] = edge; PullDstVertex(output_frame, context, direction); From ca62fa51233f865517f05d072b556b3c86a95fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Thu, 19 Jan 2023 11:42:24 +0100 Subject: [PATCH 50/85] Fetch properties of destination vertex --- src/query/v2/plan/operator.cpp | 9 +++++++-- src/storage/v3/shard_rsm.cpp | 11 +++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index 4381da0dd..15f34fe34 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -2681,9 +2681,14 @@ class DistributedExpandCursor : public Cursor { } }; + msgs::GetPropertiesRequest request; + // to not fetch any properties of the edges + request.vertex_ids.push_back(get_dst_vertex(edge, direction)); + auto result_rows = context.request_router->GetProperties(std::move(request)); + MG_ASSERT(result_rows.size() == 1); + auto &result_row = result_rows.front(); frame[self_.common_.node_symbol] = - accessors::VertexAccessor(msgs::Vertex{get_dst_vertex(edge, direction)}, - std::vector<std::pair<msgs::PropertyId, msgs::Value>>{}, context.request_router); + accessors::VertexAccessor(msgs::Vertex{result_row.vertex}, result_row.props, context.request_router); } bool InitEdges(Frame &frame, ExecutionContext &context) { diff --git a/src/storage/v3/shard_rsm.cpp b/src/storage/v3/shard_rsm.cpp index b919d217c..67a58e5cc 100644 --- a/src/storage/v3/shard_rsm.cpp +++ b/src/storage/v3/shard_rsm.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -535,13 +535,16 @@ msgs::ReadResponses ShardRsm::HandleRead(msgs::GetPropertiesRequest &&req) { return result; }; - auto collect_props = [&req](const VertexAccessor &v_acc, - const std::optional<EdgeAccessor> &e_acc) -> ShardResult<std::map<PropertyId, Value>> { + auto collect_props = [this, &req]( + const VertexAccessor &v_acc, + const std::optional<EdgeAccessor> &e_acc) -> ShardResult<std::map<PropertyId, Value>> { if (!req.property_ids) { if (e_acc) { return CollectAllPropertiesFromAccessor(*e_acc, view); } - return CollectAllPropertiesFromAccessor(v_acc, view); + const auto *schema = shard_->GetSchema(shard_->PrimaryLabel()); + MG_ASSERT(schema); + return CollectAllPropertiesFromAccessor(v_acc, view, *schema); } if (e_acc) { From a0ada914abb1e8c0fa40e1d0d449f74c876d027f Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Thu, 19 Jan 2023 13:10:53 +0100 Subject: [PATCH 51/85] Fix segfault --- src/query/v2/interpreter.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/query/v2/interpreter.hpp b/src/query/v2/interpreter.hpp index 8a535bf3c..8f754b84c 100644 --- a/src/query/v2/interpreter.hpp +++ b/src/query/v2/interpreter.hpp @@ -388,7 +388,6 @@ std::map<std::string, TypedValue> Interpreter::Pull(TStream *result_stream, std: // Wrap the (statically polymorphic) stream type into a common type which // the handler knows. AnyStream stream{result_stream, &query_execution->execution_memory}; - auto asd = query_execution->prepared_query->query_handler; const auto maybe_res = query_execution->prepared_query->query_handler(&stream, n); // Stream is using execution memory of the query_execution which // can be deleted after its execution so the stream should be cleared From 8f6fac3cdec1267fd619afe83a7602889b558ee7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Thu, 19 Jan 2023 14:35:25 +0100 Subject: [PATCH 52/85] Make arguments const --- src/query/v2/multiframe.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index 0ddfd3aa7..38cc7549a 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -45,11 +45,11 @@ void MultiFrame::MakeAllFramesInvalid() noexcept { } bool MultiFrame::HasValidFrame() const noexcept { - return std::any_of(frames_.begin(), frames_.end(), [](auto &frame) { return frame.IsValid(); }); + return std::any_of(frames_.begin(), frames_.end(), [](const auto &frame) { return frame.IsValid(); }); } bool MultiFrame::HasInvalidFrame() const noexcept { - return std::any_of(frames_.rbegin(), frames_.rend(), [](auto &frame) { return !frame.IsValid(); }); + return std::any_of(frames_.rbegin(), frames_.rend(), [](const auto &frame) { return !frame.IsValid(); }); } // NOLINTNEXTLINE (bugprone-exception-escape) From ede6281e0005da20a6d9372b0a09e102bf37e923 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Thu, 19 Jan 2023 16:58:11 +0100 Subject: [PATCH 53/85] Fix unit tests --- tests/simulation/shard_rsm.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/simulation/shard_rsm.cpp b/tests/simulation/shard_rsm.cpp index 768217945..8a79cb23b 100644 --- a/tests/simulation/shard_rsm.cpp +++ b/tests/simulation/shard_rsm.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -1316,7 +1316,7 @@ void TestGetProperties(ShardClient &client) { MG_ASSERT(!result.result_row.empty()); MG_ASSERT(result.result_row.size() == 3); for (const auto &elem : result.result_row) { - MG_ASSERT(elem.props.size() == 3); + MG_ASSERT(elem.props.size() == 4); } } { From 6fe244b20926b3be396423dc966572f7d7fd37e7 Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Thu, 19 Jan 2023 17:16:29 +0100 Subject: [PATCH 54/85] Add missing MOCK_METHODS to MockedRequestRouter --- tests/unit/mock_helpers.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unit/mock_helpers.hpp b/tests/unit/mock_helpers.hpp index c522b8602..89ae9bb7d 100644 --- a/tests/unit/mock_helpers.hpp +++ b/tests/unit/mock_helpers.hpp @@ -42,6 +42,8 @@ class MockedRequestRouter : public RequestRouterInterface { MOCK_METHOD(std::optional<storage::v3::LabelId>, MaybeNameToLabel, (const std::string &), (const)); MOCK_METHOD(bool, IsPrimaryLabel, (storage::v3::LabelId), (const)); MOCK_METHOD(bool, IsPrimaryKey, (storage::v3::LabelId, storage::v3::PropertyId), (const)); + MOCK_METHOD(std::optional<std::pair<uint64_t, uint64_t>>, AllocateInitialEdgeIds, (io::Address)); + MOCK_METHOD(void, InstallSimulatorTicker, (std::function<bool()>)); }; class MockedLogicalOperator : public plan::LogicalOperator { From e7f10ec8f482d7c99d94b203ecdff54ad0f2a21c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Thu, 19 Jan 2023 17:44:03 +0100 Subject: [PATCH 55/85] Remove duplicated definition --- src/query/v2/multiframe.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index a6d34ee11..38cc7549a 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -52,10 +52,6 @@ bool MultiFrame::HasInvalidFrame() const noexcept { return std::any_of(frames_.rbegin(), frames_.rend(), [](const auto &frame) { return !frame.IsValid(); }); } -bool MultiFrame::HasInvalidFrame() const noexcept { - return std::any_of(frames_.begin(), frames_.end(), [](auto &frame) { return !frame.IsValid(); }); -} - // NOLINTNEXTLINE (bugprone-exception-escape) void MultiFrame::DefragmentValidFrames() noexcept { /* From cc643aac69eee6b28d5f48d3cb42a75efa22c1e1 Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Thu, 19 Jan 2023 17:45:15 +0100 Subject: [PATCH 56/85] Deal with unprotected comma in MOCKED_METHOD --- tests/unit/mock_helpers.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/mock_helpers.hpp b/tests/unit/mock_helpers.hpp index 89ae9bb7d..278649e47 100644 --- a/tests/unit/mock_helpers.hpp +++ b/tests/unit/mock_helpers.hpp @@ -42,7 +42,7 @@ class MockedRequestRouter : public RequestRouterInterface { MOCK_METHOD(std::optional<storage::v3::LabelId>, MaybeNameToLabel, (const std::string &), (const)); MOCK_METHOD(bool, IsPrimaryLabel, (storage::v3::LabelId), (const)); MOCK_METHOD(bool, IsPrimaryKey, (storage::v3::LabelId, storage::v3::PropertyId), (const)); - MOCK_METHOD(std::optional<std::pair<uint64_t, uint64_t>>, AllocateInitialEdgeIds, (io::Address)); + MOCK_METHOD((std::optional<std::pair<uint64_t, uint64_t>>), AllocateInitialEdgeIds, (io::Address)); MOCK_METHOD(void, InstallSimulatorTicker, (std::function<bool()>)); }; From 5eea3ceee4a4f00fbd35d72bda485ad3e60b269a Mon Sep 17 00:00:00 2001 From: gvolfing <107616712+gvolfing@users.noreply.github.com> Date: Fri, 20 Jan 2023 06:22:37 +0100 Subject: [PATCH 57/85] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jure Bajic <jure.bajic@memgraph.com> Co-authored-by: János Benjamin Antal <antaljanosbenjamin@users.noreply.github.com> --- src/query/v2/plan/operator.cpp | 4 ++-- src/query/v2/plan/pretty_print.hpp | 2 +- src/query/v2/plan/rewrite/index_lookup.hpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index 491241635..c3140f585 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -487,7 +487,7 @@ class DistributedScanAllByPrimaryKeyCursor : public Cursor { bool MakeRequest(RequestRouterInterface &request_router, ExecutionContext &context) { { SCOPED_REQUEST_WAIT_PROFILE; - std::optional<std::string> request_label = std::nullopt; + std::optional<std::string> request_label; if (label_.has_value()) { request_label = request_router.LabelToName(*label_); } @@ -512,7 +512,7 @@ class DistributedScanAllByPrimaryKeyCursor : public Cursor { std::vector<msgs::Value> pk; MG_ASSERT(primary_key_); - for (auto *primary_key : *primary_key_) { + for (auto *primary_property : *primary_key_) { pk.push_back(TypedValueToValue(primary_key->Accept(evaluator))); } diff --git a/src/query/v2/plan/pretty_print.hpp b/src/query/v2/plan/pretty_print.hpp index 266b16102..30ab87978 100644 --- a/src/query/v2/plan/pretty_print.hpp +++ b/src/query/v2/plan/pretty_print.hpp @@ -194,7 +194,7 @@ class PlanToJsonVisitor : public virtual HierarchicalLogicalOperatorVisitor { bool PreVisit(ScanAllByLabelPropertyRange &) override; bool PreVisit(ScanAllByLabelPropertyValue &) override; bool PreVisit(ScanAllByLabelProperty &) override; - bool PreVisit(ScanAllByPrimaryKey & /*unused*/) override; + bool PreVisit(ScanAllByPrimaryKey &) override; bool PreVisit(Produce &) override; bool PreVisit(Accumulate &) override; diff --git a/src/query/v2/plan/rewrite/index_lookup.hpp b/src/query/v2/plan/rewrite/index_lookup.hpp index f2a0f1bc6..30f2c21f6 100644 --- a/src/query/v2/plan/rewrite/index_lookup.hpp +++ b/src/query/v2/plan/rewrite/index_lookup.hpp @@ -278,7 +278,7 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor { return true; } - bool PostVisit(ScanAllByPrimaryKey & /*unused*/) override { + bool PostVisit(ScanAllByPrimaryKey &) override { prev_ops_.pop_back(); return true; } @@ -593,7 +593,7 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor { } if (!primary_key.empty()) { // Mark the expressions so they won't be used for an additional, unnecessary filter. - for (const auto &pk : primary_key) { + for (const auto &primary_property : primary_key) { filter_exprs_for_removal_.insert(pk.first); filters_.EraseFilter(pk.second); } From be39fac72ebc80d91f70845322e686df81061ed3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Fri, 20 Jan 2023 15:36:24 +0100 Subject: [PATCH 58/85] Add return value to `PullMultiple` Because the `FilterCursor` might push down the same multiframe multiple times, it is easier if each cursor maintains whether it put any new data on the `MultiFrame` or not. This way each cursor can decide easily whether it has to do more work or not. --- src/query/v2/interpreter.cpp | 5 +-- src/query/v2/plan/operator.cpp | 64 +++++++++++++++++++++------------- src/query/v2/plan/operator.lcp | 6 ++-- 3 files changed, 43 insertions(+), 32 deletions(-) diff --git a/src/query/v2/interpreter.cpp b/src/query/v2/interpreter.cpp index b447a0a2b..c7943e6df 100644 --- a/src/query/v2/interpreter.cpp +++ b/src/query/v2/interpreter.cpp @@ -731,10 +731,7 @@ std::optional<plan::ProfilingStatsWithTotalTime> PullPlan::PullMultiple(AnyStrea } // Returns true if a result was pulled. - const auto pull_result = [&]() -> bool { - cursor_->PullMultiple(multi_frame_, ctx_); - return multi_frame_.HasValidFrame(); - }; + const auto pull_result = [&]() -> bool { return cursor_->PullMultiple(multi_frame_, ctx_); }; const auto stream_values = [&output_symbols, &stream](const Frame &frame) { // TODO: The streamed values should also probably use the above memory. diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index 15f34fe34..1ac5d98b8 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -189,18 +189,19 @@ class DistributedCreateNodeCursor : public Cursor { return false; } - void PullMultiple(MultiFrame &multi_frame, ExecutionContext &context) override { + bool PullMultiple(MultiFrame &multi_frame, ExecutionContext &context) override { SCOPED_PROFILE_OP("CreateNodeMF"); - input_cursor_->PullMultiple(multi_frame, context); + auto *request_router = context.request_router; - if (!multi_frame.HasValidFrame()) { - return; + if (!input_cursor_->PullMultiple(multi_frame, context)) { + return false; } { SCOPED_REQUEST_WAIT_PROFILE; request_router->CreateVertices(NodeCreationInfoToRequests(context, multi_frame)); } PlaceNodesOnTheMultiFrame(multi_frame, context); + return false; } void Shutdown() override { input_cursor_->Shutdown(); } @@ -324,14 +325,16 @@ bool Once::OnceCursor::Pull(Frame &, ExecutionContext &context) { return false; } -void Once::OnceCursor::PullMultiple(MultiFrame &multi_frame, ExecutionContext &context) { +bool Once::OnceCursor::PullMultiple(MultiFrame &multi_frame, ExecutionContext &context) { SCOPED_PROFILE_OP("OnceMF"); if (!did_pull_) { auto &first_frame = multi_frame.GetFirstFrame(); first_frame.MakeValid(); did_pull_ = true; + return true; } + return false; } UniqueCursorPtr Once::MakeCursor(utils::MemoryResource *mem) const { @@ -491,10 +494,10 @@ class DistributedScanAllAndFilterCursor : public Cursor { } bool PullNextFrames(ExecutionContext &context) { - input_cursor_->PullMultiple(*own_multi_frame_, context); + const auto pulled_any = input_cursor_->PullMultiple(*own_multi_frame_, context); own_frames_consumer_ = own_multi_frame_->GetValidFramesConsumer(); own_frames_it_ = own_frames_consumer_->begin(); - return own_multi_frame_->HasValidFrame(); + return pulled_any; } inline bool HasMoreResult() { @@ -521,7 +524,7 @@ class DistributedScanAllAndFilterCursor : public Cursor { return true; } - void PullMultiple(MultiFrame &output_multi_frame, ExecutionContext &context) override { + bool PullMultiple(MultiFrame &output_multi_frame, ExecutionContext &context) override { SCOPED_PROFILE_OP(op_name_); if (!own_multi_frame_.has_value()) { @@ -533,17 +536,20 @@ class DistributedScanAllAndFilterCursor : public Cursor { } if (!HasMoreResult()) { - return; + return false; } + bool populated_any = false; for (auto &frame : output_multi_frame.GetInvalidFramesPopulator()) { if (MustAbort(context)) { throw HintedAbortError(); } if (!PopulateFrame(context, frame)) { - return; + break; } + populated_any = true; } + return populated_any; }; void Shutdown() override { input_cursor_->Shutdown(); } @@ -859,19 +865,22 @@ bool Produce::ProduceCursor::Pull(Frame &frame, ExecutionContext &context) { // Produce should always yield the latest results. ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.request_router, storage::v3::View::NEW); - for (auto named_expr : self_.named_expressions_) named_expr->Accept(evaluator); + for (auto *named_expr : self_.named_expressions_) named_expr->Accept(evaluator); return true; } return false; } -void Produce::ProduceCursor::PullMultiple(MultiFrame &multi_frame, ExecutionContext &context) { +bool Produce::ProduceCursor::PullMultiple(MultiFrame &multi_frame, ExecutionContext &context) { SCOPED_PROFILE_OP("ProduceMF"); - input_cursor_->PullMultiple(multi_frame, context); + if (!input_cursor_->PullMultiple(multi_frame, context)) { + return false; + } auto iterator_for_valid_frame_only = multi_frame.GetValidFramesModifier(); + for (auto &frame : iterator_for_valid_frame_only) { // Produce should always yield the latest results. ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.request_router, @@ -881,7 +890,9 @@ void Produce::ProduceCursor::PullMultiple(MultiFrame &multi_frame, ExecutionCont named_expr->Accept(evaluator); } } -}; + + return true; +} void Produce::ProduceCursor::Shutdown() { input_cursor_->Shutdown(); } @@ -2484,11 +2495,10 @@ class DistributedCreateExpandCursor : public Cursor { return true; } - void PullMultiple(MultiFrame &multi_frame, ExecutionContext &context) override { + bool PullMultiple(MultiFrame &multi_frame, ExecutionContext &context) override { SCOPED_PROFILE_OP("CreateExpandMF"); - input_cursor_->PullMultiple(multi_frame, context); - if (!multi_frame.HasValidFrame()) { - return; + if (!input_cursor_->PullMultiple(multi_frame, context)) { + return false; } auto request_vertices = ExpandCreationInfoToRequests(multi_frame, context); { @@ -2501,6 +2511,7 @@ class DistributedCreateExpandCursor : public Cursor { } } } + return true; } void Shutdown() override { input_cursor_->Shutdown(); } @@ -2847,12 +2858,12 @@ class DistributedExpandCursor : public Cursor { } bool PullInputFrames(ExecutionContext &context) { - input_cursor_->PullMultiple(*own_multi_frame_, context); + const auto pulled_any = !input_cursor_->PullMultiple(*own_multi_frame_, context); // These needs to be updated regardless of the result of the pull, otherwise the consumer and iterator might // get corrupted because of the operations done on our MultiFrame. own_frames_consumer_ = own_multi_frame_->GetValidFramesConsumer(); own_frames_it_ = own_frames_consumer_->begin(); - if (!own_multi_frame_->HasValidFrame()) { + if (!pulled_any) { return false; } @@ -2883,20 +2894,21 @@ class DistributedExpandCursor : public Cursor { return true; } - void PullMultiple(MultiFrame &output_multi_frame, ExecutionContext &context) override { + bool PullMultiple(MultiFrame &output_multi_frame, ExecutionContext &context) override { SCOPED_PROFILE_OP("DistributedExpandMF"); MG_ASSERT(!self_.common_.existing_node); EnsureOwnMultiFrameIsGood(output_multi_frame); // A helper function for expanding a node from an edge. auto output_frames_populator = output_multi_frame.GetInvalidFramesPopulator(); + auto populated_any = false; while (true) { switch (state_) { case State::PullInputAndEdges: { if (!PullInputFrames(context)) { state_ = State::Exhausted; - return; + return populated_any; } state_ = State::InitInOutEdgesIt; break; @@ -2912,7 +2924,7 @@ class DistributedExpandCursor : public Cursor { } case State::PopulateOutput: { if (!output_multi_frame.HasInvalidFrame()) { - return; + return populated_any; } if (current_in_edge_it_ == current_in_edges_.end() && current_out_edge_it_ == current_out_edges_.end()) { own_frames_it_->MakeInvalid(); @@ -2920,7 +2932,7 @@ class DistributedExpandCursor : public Cursor { state_ = State::InitInOutEdgesIt; continue; } - auto populate_edges = [this, &context, &output_frames_populator]( + auto populate_edges = [this, &context, &output_frames_populator, &populated_any]( const EdgeAtom::Direction direction, std::vector<EdgeAccessor>::iterator ¤t, const std::vector<EdgeAccessor>::iterator &end) { for (auto output_frame_it = output_frames_populator.begin(); @@ -2931,6 +2943,7 @@ class DistributedExpandCursor : public Cursor { output_frame = *own_frames_it_; output_frame[self_.common_.edge_symbol] = edge; PullDstVertex(output_frame, context, direction); + populated_any = true; } }; populate_edges(EdgeAtom::Direction::IN, current_in_edge_it_, current_in_edges_.end()); @@ -2938,10 +2951,11 @@ class DistributedExpandCursor : public Cursor { break; } case State::Exhausted: { - return; + return populated_any; } } } + return populated_any; } void EnsureOwnMultiFrameIsGood(MultiFrame &output_multi_frame) { diff --git a/src/query/v2/plan/operator.lcp b/src/query/v2/plan/operator.lcp index efa0d5df0..c31ae9762 100644 --- a/src/query/v2/plan/operator.lcp +++ b/src/query/v2/plan/operator.lcp @@ -72,7 +72,7 @@ class Cursor { /// @throws QueryRuntimeException if something went wrong with execution virtual bool Pull(Frame &, ExecutionContext &) = 0; - virtual void PullMultiple(MultiFrame &, ExecutionContext &) { LOG_FATAL("PullMultipleIsNotImplemented"); } + virtual bool PullMultiple(MultiFrame &, ExecutionContext &) { LOG_FATAL("PullMultipleIsNotImplemented"); } /// Resets the Cursor to its initial state. virtual void Reset() = 0; @@ -335,7 +335,7 @@ and false on every following Pull.") class OnceCursor : public Cursor { public: OnceCursor() {} - void PullMultiple(MultiFrame &, ExecutionContext &) override; + bool PullMultiple(MultiFrame &, ExecutionContext &) override; bool Pull(Frame &, ExecutionContext &) override; void Shutdown() override; void Reset() override; @@ -1211,7 +1211,7 @@ RETURN clause) the Produce's pull succeeds exactly once.") public: ProduceCursor(const Produce &, utils::MemoryResource *); bool Pull(Frame &, ExecutionContext &) override; - void PullMultiple(MultiFrame &, ExecutionContext &) override; + bool PullMultiple(MultiFrame &, ExecutionContext &) override; void Shutdown() override; void Reset() override; From de99025c39a1cca5e17e3033d40a009cc0af3d52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Fri, 20 Jan 2023 15:36:38 +0100 Subject: [PATCH 59/85] Implement `PullMultiple` for `FilterCursor` --- src/query/v2/plan/operator.cpp | 21 +++++++++++++++++++++ src/query/v2/plan/operator.lcp | 1 + 2 files changed, 22 insertions(+) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index 1ac5d98b8..676576098 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -830,6 +830,27 @@ bool Filter::FilterCursor::Pull(Frame &frame, ExecutionContext &context) { return false; } +bool Filter::FilterCursor::PullMultiple(MultiFrame &multi_frame, ExecutionContext &context) { + SCOPED_PROFILE_OP("Filter"); + auto populated_any = false; + + while (multi_frame.HasInvalidFrame()) { + if (!input_cursor_->PullMultiple(multi_frame, context)) { + return populated_any; + } + for (auto &frame : multi_frame.GetValidFramesConsumer()) { + ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.request_router, + storage::v3::View::OLD); + if (!EvaluateFilter(evaluator, self_.expression_)) { + frame.MakeInvalid(); + } else { + populated_any = true; + } + } + } + return populated_any; +} + void Filter::FilterCursor::Shutdown() { input_cursor_->Shutdown(); } void Filter::FilterCursor::Reset() { input_cursor_->Reset(); } diff --git a/src/query/v2/plan/operator.lcp b/src/query/v2/plan/operator.lcp index c31ae9762..65a562be3 100644 --- a/src/query/v2/plan/operator.lcp +++ b/src/query/v2/plan/operator.lcp @@ -1160,6 +1160,7 @@ a boolean value.") public: FilterCursor(const Filter &, utils::MemoryResource *); bool Pull(Frame &, ExecutionContext &) override; + bool PullMultiple(MultiFrame &, ExecutionContext &) override; void Shutdown() override; void Reset() override; From c9299a6c72c7895be45115f7e53d1a04c69e7ef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Fri, 20 Jan 2023 21:37:57 +0100 Subject: [PATCH 60/85] Turn the scan all cursor into an automaton --- src/query/v2/plan/operator.cpp | 105 ++++++++++++++++++--------------- 1 file changed, 58 insertions(+), 47 deletions(-) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index 676576098..b8449c4bb 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -455,8 +455,6 @@ class DistributedScanAllAndFilterCursor : public Cursor { ResetExecutionState(); } - enum class State : int8_t { INITIALIZING, COMPLETED }; - using VertexAccessor = accessors::VertexAccessor; bool MakeRequest(ExecutionContext &context) { @@ -493,61 +491,71 @@ class DistributedScanAllAndFilterCursor : public Cursor { } } - bool PullNextFrames(ExecutionContext &context) { - const auto pulled_any = input_cursor_->PullMultiple(*own_multi_frame_, context); - own_frames_consumer_ = own_multi_frame_->GetValidFramesConsumer(); - own_frames_it_ = own_frames_consumer_->begin(); - return pulled_any; - } - - inline bool HasMoreResult() { - return current_vertex_it_ != current_batch_.end() && own_frames_it_ != own_frames_consumer_->end(); - } - - bool PopulateFrame(ExecutionContext &context, FrameWithValidity &frame) { - MG_ASSERT(HasMoreResult()); - - frame = *own_frames_it_; - frame[output_symbol_] = TypedValue(*current_vertex_it_); - - ++current_vertex_it_; - if (current_vertex_it_ == current_batch_.end()) { - own_frames_it_->MakeInvalid(); - ++own_frames_it_; - - current_vertex_it_ = current_batch_.begin(); - - if (own_frames_it_ == own_frames_consumer_->end()) { - return PullNextFrames(context); - } - }; - return true; - } - bool PullMultiple(MultiFrame &output_multi_frame, ExecutionContext &context) override { SCOPED_PROFILE_OP(op_name_); if (!own_multi_frame_.has_value()) { own_multi_frame_.emplace(MultiFrame(output_multi_frame.GetFirstFrame().elems().size(), kNumberOfFramesInMultiframe, output_multi_frame.GetMemoryResource())); - - MakeRequest(context); - PullNextFrames(context); + own_frames_consumer_.emplace(own_multi_frame_->GetValidFramesConsumer()); + own_frames_it_ = own_frames_consumer_->begin(); } - if (!HasMoreResult()) { - return false; - } + auto output_frames_populator = output_multi_frame.GetInvalidFramesPopulator(); + auto populated_any = false; - bool populated_any = false; - for (auto &frame : output_multi_frame.GetInvalidFramesPopulator()) { - if (MustAbort(context)) { - throw HintedAbortError(); + while (true) { + switch (state_) { + case State::PullInput: { + if (!input_cursor_->PullMultiple(*own_multi_frame_, context)) { + state_ = State::Exhausted; + return populated_any; + } + own_frames_consumer_.emplace(own_multi_frame_->GetValidFramesConsumer()); + own_frames_it_ = own_frames_consumer_->begin(); + state_ = State::FetchVertices; + break; + } + case State::FetchVertices: { + if (own_frames_it_ == own_frames_consumer_->end()) { + state_ = State::PullInput; + continue; + } + if (!filter_expressions_->empty() || property_expression_pair_.has_value() || current_batch_.empty()) { + MakeRequest(context); + } else { + // We can reuse the vertices as they don't depend on any value from the frames + current_vertex_it_ = current_batch_.begin(); + } + state_ = State::PopulateOutput; + break; + } + case State::PopulateOutput: { + if (!output_multi_frame.HasInvalidFrame()) { + return populated_any; + } + if (current_vertex_it_ == current_batch_.end()) { + own_frames_it_->MakeInvalid(); + ++own_frames_it_; + state_ = State::FetchVertices; + continue; + } + + for (auto output_frame_it = output_frames_populator.begin(); + output_frame_it != output_frames_populator.end() && current_vertex_it_ != current_batch_.end(); + ++output_frame_it) { + auto &output_frame = *output_frame_it; + output_frame = *own_frames_it_; + output_frame[output_symbol_] = TypedValue(*current_vertex_it_); + current_vertex_it_++; + populated_any = true; + } + break; + } + case State::Exhausted: { + return populated_any; + } } - if (!PopulateFrame(context, frame)) { - break; - } - populated_any = true; } return populated_any; }; @@ -565,6 +573,9 @@ class DistributedScanAllAndFilterCursor : public Cursor { } private: + enum class State { PullInput, FetchVertices, PopulateOutput, Exhausted }; + + State state_{State::PullInput}; const Symbol output_symbol_; const UniqueCursorPtr input_cursor_; const char *op_name_; From 0eee3ad7b7c43176b563e1e7eb7217f96be1e22f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Fri, 20 Jan 2023 21:38:35 +0100 Subject: [PATCH 61/85] Fix `DistributedExpandCursor` --- src/query/v2/plan/operator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index b8449c4bb..4461ab580 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -2890,7 +2890,7 @@ class DistributedExpandCursor : public Cursor { } bool PullInputFrames(ExecutionContext &context) { - const auto pulled_any = !input_cursor_->PullMultiple(*own_multi_frame_, context); + const auto pulled_any = input_cursor_->PullMultiple(*own_multi_frame_, context); // These needs to be updated regardless of the result of the pull, otherwise the consumer and iterator might // get corrupted because of the operations done on our MultiFrame. own_frames_consumer_ = own_multi_frame_->GetValidFramesConsumer(); @@ -2949,7 +2949,7 @@ class DistributedExpandCursor : public Cursor { if (own_frames_it_ == own_frames_consumer_->end()) { state_ = State::PullInputAndEdges; } else { - InitEdges(*own_frames_it_, context); + InitEdgesMultiple(context); state_ = State::PopulateOutput; } break; From 55b5d7609244c4414a4e5b3f54cf098925016a6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Fri, 20 Jan 2023 21:38:51 +0100 Subject: [PATCH 62/85] Add docs to `PullMultiple` --- src/query/v2/plan/operator.lcp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/query/v2/plan/operator.lcp b/src/query/v2/plan/operator.lcp index 65a562be3..82e98df03 100644 --- a/src/query/v2/plan/operator.lcp +++ b/src/query/v2/plan/operator.lcp @@ -72,7 +72,21 @@ class Cursor { /// @throws QueryRuntimeException if something went wrong with execution virtual bool Pull(Frame &, ExecutionContext &) = 0; - virtual bool PullMultiple(MultiFrame &, ExecutionContext &) { LOG_FATAL("PullMultipleIsNotImplemented"); } + /// Run an iteration of a @c LogicalOperator with MultiFrame. + /// + /// Since operators may be chained, the iteration may pull results from + /// multiple operators. + /// + /// @param MultiFrame May be read from or written to while performing the + /// iteration. + /// @param ExecutionContext Used to get the position of symbols in frame and + /// other information. + /// @return True if the operator was able to populate at least one Frame on the MultiFrame, + /// thus if an operator returns true, that means there is at least one valid Frame in the + /// MultiFrame. + /// + /// @throws QueryRuntimeException if something went wrong with execution + virtual bool PullMultiple(MultiFrame &, ExecutionContext &) {MG_ASSERT(false, "PullMultipleIsNotImplemented"); return false; } /// Resets the Cursor to its initial state. virtual void Reset() = 0; From 515a52130e554c81a6d9cd3e2f28c0d48b716af0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Fri, 20 Jan 2023 22:12:24 +0100 Subject: [PATCH 63/85] Prevent moving from valid frames during defregmentation of `MultiFrame` --- src/query/v2/multiframe.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index 38cc7549a..477ef6c0c 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -54,15 +54,17 @@ bool MultiFrame::HasInvalidFrame() const noexcept { // NOLINTNEXTLINE (bugprone-exception-escape) void MultiFrame::DefragmentValidFrames() noexcept { - /* - from: https://en.cppreference.com/w/cpp/algorithm/remove - "Removing is done by shifting (by means of copy assignment (until C++11)move assignment (since C++11)) the elements - in the range in such a way that the elements that are not to be removed appear in the beginning of the range. - Relative order of the elements that remain is preserved and the physical size of the container is unchanged." - */ - - // NOLINTNEXTLINE (bugprone-unused-return-value) - std::remove_if(frames_.begin(), frames_.end(), [](auto &frame) { return !frame.IsValid(); }); + static constexpr auto kIsValid = [](const FrameWithValidity &frame) { return frame.IsValid(); }; + static constexpr auto kIsInvalid = [](const FrameWithValidity &frame) { return !frame.IsValid(); }; + auto first_invalid_frame = std::find_if(frames_.begin(), frames_.end(), kIsInvalid); + auto following_first_valid = std::find_if(first_invalid_frame, frames_.end(), kIsValid); + while (first_invalid_frame != frames_.end() && following_first_valid != frames_.end()) { + std::swap(*first_invalid_frame, *following_first_valid); + first_invalid_frame++; + first_invalid_frame = std::find_if(first_invalid_frame, frames_.end(), kIsInvalid); + following_first_valid++; + following_first_valid = std::find_if(following_first_valid, frames_.end(), kIsValid); + } } ValidFramesReader MultiFrame::GetValidFramesReader() { return ValidFramesReader{*this}; } From 0285b5691518323d67abb9b168a37e914f12fbdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Fri, 20 Jan 2023 23:01:23 +0100 Subject: [PATCH 64/85] Fix compilation error --- tests/unit/mock_helpers.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/mock_helpers.hpp b/tests/unit/mock_helpers.hpp index c522b8602..302b2ff55 100644 --- a/tests/unit/mock_helpers.hpp +++ b/tests/unit/mock_helpers.hpp @@ -58,7 +58,7 @@ class MockedLogicalOperator : public plan::LogicalOperator { class MockedCursor : public plan::Cursor { public: MOCK_METHOD(bool, Pull, (Frame &, expr::ExecutionContext &)); - MOCK_METHOD(void, PullMultiple, (MultiFrame &, expr::ExecutionContext &)); + MOCK_METHOD(bool, PullMultiple, (MultiFrame &, expr::ExecutionContext &)); MOCK_METHOD(void, Reset, ()); MOCK_METHOD(void, Shutdown, ()); }; From 544c75c212f8bcac954406c45c1cdfaead8e4b61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Fri, 20 Jan 2023 23:04:33 +0100 Subject: [PATCH 65/85] Add explanation about limitations of current implementation --- src/query/v2/plan/operator.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index 4461ab580..ad91da728 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -2841,14 +2841,19 @@ class DistributedExpandCursor : public Cursor { } void InitEdgesMultiple(ExecutionContext &context) { - TypedValue &vertex_value = (*own_frames_it_)[self_.input_symbol_]; + // This function won't work if any vertex id is duplicated in the input, because: + // 1. vertex_id_to_result_row is not a multimap + // 2. if self_.common_.existing_node is true, then we erase edges that might be necessary for the input vertex on a + // later frame + const auto &frame = (*own_frames_it_); + const auto &vertex_value = frame[self_.input_symbol_]; if (vertex_value.IsNull()) { return; } ExpectType(self_.input_symbol_, vertex_value, TypedValue::Type::Vertex); - auto &vertex = vertex_value.ValueVertex(); + const auto &vertex = vertex_value.ValueVertex(); const auto convert_edges = [&vertex, &context]( std::vector<msgs::ExpandOneResultRow::EdgeWithSpecificProperties> &&edge_messages, From 900ece8109f3ee635d0bfef54cb42122008aea93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Fri, 20 Jan 2023 23:04:52 +0100 Subject: [PATCH 66/85] Add `PullMultiple` to `DeleteCursor` --- src/query/v2/plan/operator.cpp | 2 ++ src/query/v2/plan/operator.lcp | 1 + 2 files changed, 3 insertions(+) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index ad91da728..fdb89168c 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -949,6 +949,8 @@ Delete::DeleteCursor::DeleteCursor(const Delete &self, utils::MemoryResource *me bool Delete::DeleteCursor::Pull(Frame & /*frame*/, ExecutionContext & /*context*/) { return false; } +bool Delete::DeleteCursor::PullMultiple(MultiFrame & /*multi_frame*/, ExecutionContext & /*context*/) { return false; } + void Delete::DeleteCursor::Shutdown() { input_cursor_->Shutdown(); } void Delete::DeleteCursor::Reset() { input_cursor_->Reset(); } diff --git a/src/query/v2/plan/operator.lcp b/src/query/v2/plan/operator.lcp index 82e98df03..91726752b 100644 --- a/src/query/v2/plan/operator.lcp +++ b/src/query/v2/plan/operator.lcp @@ -1274,6 +1274,7 @@ Has a flag for using DETACH DELETE when deleting vertices.") public: DeleteCursor(const Delete &, utils::MemoryResource *); bool Pull(Frame &, ExecutionContext &) override; + bool PullMultiple(MultiFrame &, ExecutionContext &) override; void Shutdown() override; void Reset() override; From cf76e0e19b6f7052aa133da6aa335c9c4310c8d0 Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Fri, 20 Jan 2023 08:08:45 +0100 Subject: [PATCH 67/85] React to PR comments --- src/query/v2/multiframe.hpp | 8 +- src/query/v2/plan/operator.cpp | 204 +++++------------- src/query/v2/plan/pretty_print.hpp | 2 +- src/query/v2/plan/read_write_type_checker.cpp | 3 +- src/query/v2/plan/read_write_type_checker.hpp | 3 +- src/query/v2/plan/rewrite/index_lookup.hpp | 50 ++++- src/query/v2/plan/vertex_count_cache.hpp | 32 +-- src/storage/v3/shard_rsm.cpp | 6 +- tests/unit/query_common.hpp | 1 - tests/unit/query_plan_checker_v2.hpp | 23 +- 10 files changed, 133 insertions(+), 199 deletions(-) diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index a3b1e4d7d..dc3239393 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -200,10 +200,10 @@ class ValidFramesConsumer { explicit ValidFramesConsumer(MultiFrame &multiframe); ~ValidFramesConsumer() noexcept; - ValidFramesConsumer(const ValidFramesConsumer &other) = default; - ValidFramesConsumer(ValidFramesConsumer &&other) noexcept = default; - ValidFramesConsumer &operator=(const ValidFramesConsumer &other) = default; - ValidFramesConsumer &operator=(ValidFramesConsumer &&other) noexcept = default; + ValidFramesConsumer(const ValidFramesConsumer &other) = delete; + ValidFramesConsumer(ValidFramesConsumer &&other) noexcept = delete; + ValidFramesConsumer &operator=(const ValidFramesConsumer &other) = delete; + ValidFramesConsumer &operator=(ValidFramesConsumer &&other) noexcept = delete; struct Iterator { using iterator_category = std::forward_iterator_tag; diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index c3140f585..f42bf1bec 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -465,194 +465,86 @@ class DistributedScanAllAndFilterCursor : public Cursor { class DistributedScanAllByPrimaryKeyCursor : public Cursor { public: - explicit DistributedScanAllByPrimaryKeyCursor( - Symbol output_symbol, UniqueCursorPtr input_cursor, const char *op_name, - std::optional<storage::v3::LabelId> label, - std::optional<std::pair<storage::v3::PropertyId, Expression *>> property_expression_pair, - std::optional<std::vector<Expression *>> filter_expressions, std::optional<std::vector<Expression *>> primary_key) + explicit DistributedScanAllByPrimaryKeyCursor(Symbol output_symbol, UniqueCursorPtr input_cursor, const char *op_name, + storage::v3::LabelId label, + std::optional<std::vector<Expression *>> filter_expressions, + std::vector<Expression *> primary_key) : output_symbol_(output_symbol), input_cursor_(std::move(input_cursor)), op_name_(op_name), label_(label), - property_expression_pair_(property_expression_pair), filter_expressions_(filter_expressions), - primary_key_(primary_key) { - ResetExecutionState(); - } + primary_key_(primary_key) {} enum class State : int8_t { INITIALIZING, COMPLETED }; using VertexAccessor = accessors::VertexAccessor; - bool MakeRequest(RequestRouterInterface &request_router, ExecutionContext &context) { - { - SCOPED_REQUEST_WAIT_PROFILE; - std::optional<std::string> request_label; - if (label_.has_value()) { - request_label = request_router.LabelToName(*label_); - } - current_batch_ = request_router.ScanVertices(request_label); + std::optional<VertexAccessor> MakeRequestSingleFrame(Frame &frame, RequestRouterInterface &request_router, + ExecutionContext &context) { + // Evaluate the expressions that hold the PrimaryKey. + ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.request_router, + storage::v3::View::NEW); + + std::vector<msgs::Value> pk; + for (auto *primary_property : primary_key_) { + pk.push_back(TypedValueToValue(primary_property->Accept(evaluator))); } - current_vertex_it_ = current_batch_.begin(); - request_state_ = State::COMPLETED; - return !current_batch_.empty(); - } - bool MakeRequestSingleFrame(Frame &frame, RequestRouterInterface &request_router, ExecutionContext &context) { - { + msgs::Label label = {.id = msgs::LabelId::FromUint(label_.AsUint())}; + + msgs::GetPropertiesRequest req = {.vertex_ids = {std::make_pair(label, pk)}}; + auto get_prop_result = std::invoke([&context, &request_router, &req]() mutable { SCOPED_REQUEST_WAIT_PROFILE; - std::optional<std::string> request_label = std::nullopt; - if (label_.has_value()) { - request_label = request_router.LabelToName(*label_); - } + return request_router.GetProperties(req); + }); + MG_ASSERT(get_prop_result.size() <= 1); - // Evaluate the expressions that hold the PrimaryKey. - ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.request_router, - storage::v3::View::NEW); - - std::vector<msgs::Value> pk; - MG_ASSERT(primary_key_); - for (auto *primary_property : *primary_key_) { - pk.push_back(TypedValueToValue(primary_key->Accept(evaluator))); - } - - msgs::Label label = {.id = msgs::LabelId::FromUint(label_->AsUint())}; - - msgs::GetPropertiesRequest req = {.vertex_ids = {std::make_pair(label, pk)}}; - auto get_prop_result = request_router.GetProperties(req); - MG_ASSERT(get_prop_result.size() <= 1); - - if (get_prop_result.empty()) { - current_batch_ = std::vector<VertexAccessor>{}; - } else { - auto properties = get_prop_result[0].props; - // TODO (gvolfing) figure out labels when relevant. - msgs::Vertex vertex = {.id = get_prop_result[0].vertex, .labels = {}}; - - current_batch_ = {VertexAccessor(vertex, properties, &request_router)}; - } + if (get_prop_result.empty()) { + return std::nullopt; } - current_vertex_it_ = current_batch_.begin(); - request_state_ = State::COMPLETED; - return !current_batch_.empty(); + auto properties = get_prop_result[0].props; + // TODO (gvolfing) figure out labels when relevant. + msgs::Vertex vertex = {.id = get_prop_result[0].vertex, .labels = {}}; + + return VertexAccessor(vertex, properties, &request_router); } bool Pull(Frame &frame, ExecutionContext &context) override { SCOPED_PROFILE_OP(op_name_); + if (MustAbort(context)) { + throw HintedAbortError(); + } + + if (!input_cursor_->Pull(frame, context)) { + return false; + } + auto &request_router = *context.request_router; - while (true) { - if (MustAbort(context)) { - throw HintedAbortError(); - } - - if (request_state_ == State::INITIALIZING) { - if (!input_cursor_->Pull(frame, context)) { - return false; - } - } - - if (current_vertex_it_ == current_batch_.end() && - (request_state_ == State::COMPLETED || !MakeRequestSingleFrame(frame, request_router, context))) { - ResetExecutionState(); - continue; - } - - frame[output_symbol_] = TypedValue(std::move(*current_vertex_it_)); - ++current_vertex_it_; + auto vertex = MakeRequestSingleFrame(frame, request_router, context); + if (vertex) { + frame[output_symbol_] = TypedValue(std::move(*vertex)); return true; } - } - - void PrepareNextFrames(ExecutionContext &context) { - auto &request_router = *context.request_router; - - input_cursor_->PullMultiple(*own_multi_frames_, context); - valid_frames_consumer_ = own_multi_frames_->GetValidFramesConsumer(); - valid_frames_it_ = valid_frames_consumer_->begin(); - - MakeRequest(request_router, context); - } - - inline bool HasNextFrame() { - return current_vertex_it_ != current_batch_.end() && valid_frames_it_ != valid_frames_consumer_->end(); - } - - FrameWithValidity GetNextFrame(ExecutionContext &context) { - MG_ASSERT(HasNextFrame()); - - auto frame = *valid_frames_it_; - frame[output_symbol_] = TypedValue(*current_vertex_it_); - - ++current_vertex_it_; - if (current_vertex_it_ == current_batch_.end()) { - valid_frames_it_->MakeInvalid(); - ++valid_frames_it_; - - if (valid_frames_it_ == valid_frames_consumer_->end()) { - PrepareNextFrames(context); - } else { - current_vertex_it_ = current_batch_.begin(); - } - }; - - return frame; + return false; } void PullMultiple(MultiFrame &input_multi_frame, ExecutionContext &context) override { - SCOPED_PROFILE_OP(op_name_); - - if (!own_multi_frames_.has_value()) { - own_multi_frames_.emplace(MultiFrame(input_multi_frame.GetFirstFrame().elems().size(), - kNumberOfFramesInMultiframe, input_multi_frame.GetMemoryResource())); - PrepareNextFrames(context); - } - - while (true) { - if (MustAbort(context)) { - throw HintedAbortError(); - } - - auto invalid_frames_populator = input_multi_frame.GetInvalidFramesPopulator(); - auto invalid_frame_it = invalid_frames_populator.begin(); - auto has_modified_at_least_one_frame = false; - - while (invalid_frames_populator.end() != invalid_frame_it && HasNextFrame()) { - has_modified_at_least_one_frame = true; - *invalid_frame_it = GetNextFrame(context); - ++invalid_frame_it; - } - - if (!has_modified_at_least_one_frame) { - return; - } - } + throw utils::NotYetImplemented("Multiframe version of ScanAllByPrimaryKey is yet to be implemented."); }; + void Reset() override { input_cursor_->Reset(); } + void Shutdown() override { input_cursor_->Shutdown(); } - void ResetExecutionState() { - current_batch_.clear(); - current_vertex_it_ = current_batch_.end(); - request_state_ = State::INITIALIZING; - } - - void Reset() override { - input_cursor_->Reset(); - ResetExecutionState(); - } - private: const Symbol output_symbol_; const UniqueCursorPtr input_cursor_; const char *op_name_; - std::vector<VertexAccessor> current_batch_; - std::vector<VertexAccessor>::iterator current_vertex_it_; - State request_state_ = State::INITIALIZING; - std::optional<storage::v3::LabelId> label_; - std::optional<std::pair<storage::v3::PropertyId, Expression *>> property_expression_pair_; + storage::v3::LabelId label_; std::optional<std::vector<Expression *>> filter_expressions_; - std::optional<std::vector<Expression *>> primary_key_; + std::vector<Expression *> primary_key_; std::optional<MultiFrame> own_multi_frames_; std::optional<ValidFramesConsumer> valid_frames_consumer_; ValidFramesConsumer::Iterator valid_frames_it_; @@ -767,9 +659,9 @@ ACCEPT_WITH_INPUT(ScanAllByPrimaryKey) UniqueCursorPtr ScanAllByPrimaryKey::MakeCursor(utils::MemoryResource *mem) const { EventCounter::IncrementCounter(EventCounter::ScanAllByPrimaryKeyOperator); - return MakeUniqueCursorPtr<DistributedScanAllByPrimaryKeyCursor>( - mem, output_symbol_, input_->MakeCursor(mem), "ScanAllByPrimaryKey", label_, - std::nullopt /*property_expression_pair*/, std::nullopt /*filter_expressions*/, primary_key_); + return MakeUniqueCursorPtr<DistributedScanAllByPrimaryKeyCursor>(mem, output_symbol_, input_->MakeCursor(mem), + "ScanAllByPrimaryKey", label_, + std::nullopt /*filter_expressions*/, primary_key_); } Expand::Expand(const std::shared_ptr<LogicalOperator> &input, Symbol input_symbol, Symbol node_symbol, diff --git a/src/query/v2/plan/pretty_print.hpp b/src/query/v2/plan/pretty_print.hpp index 30ab87978..d1dad22b7 100644 --- a/src/query/v2/plan/pretty_print.hpp +++ b/src/query/v2/plan/pretty_print.hpp @@ -67,7 +67,7 @@ class PlanPrinter : public virtual HierarchicalLogicalOperatorVisitor { bool PreVisit(ScanAllByLabelPropertyValue &) override; bool PreVisit(ScanAllByLabelPropertyRange &) override; bool PreVisit(ScanAllByLabelProperty &) override; - bool PreVisit(ScanAllByPrimaryKey & /*unused*/) override; + bool PreVisit(ScanAllByPrimaryKey &) override; bool PreVisit(Expand &) override; bool PreVisit(ExpandVariable &) override; diff --git a/src/query/v2/plan/read_write_type_checker.cpp b/src/query/v2/plan/read_write_type_checker.cpp index ec1459f6a..d1e037d1f 100644 --- a/src/query/v2/plan/read_write_type_checker.cpp +++ b/src/query/v2/plan/read_write_type_checker.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -35,6 +35,7 @@ PRE_VISIT(ScanAllByLabel, RWType::R, true) PRE_VISIT(ScanAllByLabelPropertyRange, RWType::R, true) PRE_VISIT(ScanAllByLabelPropertyValue, RWType::R, true) PRE_VISIT(ScanAllByLabelProperty, RWType::R, true) +PRE_VISIT(ScanAllByPrimaryKey, RWType::R, true) PRE_VISIT(Expand, RWType::R, true) PRE_VISIT(ExpandVariable, RWType::R, true) diff --git a/src/query/v2/plan/read_write_type_checker.hpp b/src/query/v2/plan/read_write_type_checker.hpp index e5f429035..62d7af8b9 100644 --- a/src/query/v2/plan/read_write_type_checker.hpp +++ b/src/query/v2/plan/read_write_type_checker.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -59,6 +59,7 @@ class ReadWriteTypeChecker : public virtual HierarchicalLogicalOperatorVisitor { bool PreVisit(ScanAllByLabelPropertyValue &) override; bool PreVisit(ScanAllByLabelPropertyRange &) override; bool PreVisit(ScanAllByLabelProperty &) override; + bool PreVisit(ScanAllByPrimaryKey &) override; bool PreVisit(Expand &) override; bool PreVisit(ExpandVariable &) override; diff --git a/src/query/v2/plan/rewrite/index_lookup.hpp b/src/query/v2/plan/rewrite/index_lookup.hpp index 30f2c21f6..37c14129e 100644 --- a/src/query/v2/plan/rewrite/index_lookup.hpp +++ b/src/query/v2/plan/rewrite/index_lookup.hpp @@ -569,8 +569,8 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor { const auto &modified_symbols = scan.ModifiedSymbols(*symbol_table_); std::unordered_set<Symbol> bound_symbols(modified_symbols.begin(), modified_symbols.end()); - // Try to see if we can use label+property index. If not, try to use - // just the label index. + // Try to see if we can use label + primary-key or label + property index. + // If not, try to use just the label index. const auto labels = filters_.FilteredLabels(node_symbol); if (labels.empty()) { // Without labels, we cannot generate any indexed ScanAll. @@ -583,19 +583,57 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor { query::v2::LabelIx prim_label; std::vector<std::pair<query::v2::Expression *, query::v2::plan::FilterInfo>> primary_key; + auto extract_primary_key = [this](storage::v3::LabelId label, + std::vector<query::v2::plan::FilterInfo> property_filters) + -> std::vector<std::pair<query::v2::Expression *, query::v2::plan::FilterInfo>> { + std::vector<std::pair<query::v2::Expression *, query::v2::plan::FilterInfo>> pk_temp; + std::vector<std::pair<query::v2::Expression *, query::v2::plan::FilterInfo>> pk; + std::vector<memgraph::storage::v3::SchemaProperty> schema = db_->GetSchemaForLabel(label); + + std::vector<storage::v3::PropertyId> schema_properties; + schema_properties.reserve(schema.size()); + + std::transform(schema.begin(), schema.end(), std::back_inserter(schema_properties), + [](const auto &schema_elem) { return schema_elem.property_id; }); + + for (const auto &property_filter : property_filters) { + const auto &property_id = db_->NameToProperty(property_filter.property_filter->property_.name); + if (std::find(schema_properties.begin(), schema_properties.end(), property_id) != schema_properties.end()) { + pk_temp.emplace_back(std::make_pair(property_filter.expression, property_filter)); + } + } + + // Make sure pk is in the same order as schema_properties. + for (const auto &schema_prop : schema_properties) { + for (auto &pk_temp_prop : pk_temp) { + const auto &property_id = db_->NameToProperty(pk_temp_prop.second.property_filter->property_.name); + if (schema_prop == property_id) { + pk.push_back(pk_temp_prop); + } + } + } + MG_ASSERT(pk.size() == pk_temp.size(), + "The two vectors should represent the same primary key with a possibly different order of contained " + "elements."); + + return pk.size() == schema_properties.size() + ? pk + : std::vector<std::pair<query::v2::Expression *, query::v2::plan::FilterInfo>>{}; + }; + if (!property_filters.empty()) { for (const auto &label : labels) { - if (db_->LabelIndexExists(GetLabel(label))) { + if (db_->PrimaryLabelExists(GetLabel(label))) { prim_label = label; - primary_key = db_->ExtractPrimaryKey(GetLabel(prim_label), property_filters); + primary_key = extract_primary_key(GetLabel(prim_label), property_filters); break; } } if (!primary_key.empty()) { // Mark the expressions so they won't be used for an additional, unnecessary filter. for (const auto &primary_property : primary_key) { - filter_exprs_for_removal_.insert(pk.first); - filters_.EraseFilter(pk.second); + filter_exprs_for_removal_.insert(primary_property.first); + filters_.EraseFilter(primary_property.second); } EraseLabelFilters(node_symbol, prim_label); std::vector<query::v2::Expression *> pk_expressions; diff --git a/src/query/v2/plan/vertex_count_cache.hpp b/src/query/v2/plan/vertex_count_cache.hpp index a5f7b42b7..7e3fc8530 100644 --- a/src/query/v2/plan/vertex_count_cache.hpp +++ b/src/query/v2/plan/vertex_count_cache.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -22,6 +22,7 @@ #include "storage/v3/id_types.hpp" #include "storage/v3/property_value.hpp" #include "utils/bound.hpp" +#include "utils/exceptions.hpp" #include "utils/fnv.hpp" namespace memgraph::query::v2::plan { @@ -54,31 +55,16 @@ class VertexCountCache { return 1; } - bool LabelIndexExists(storage::v3::LabelId label) { return request_router_->IsPrimaryLabel(label); } + bool LabelIndexExists(storage::v3::LabelId label) { + throw utils::NotYetImplemented("Label indicies are yet to be implemented."); + } + + bool PrimaryLabelExists(storage::v3::LabelId label) { return request_router_->IsPrimaryLabel(label); } bool LabelPropertyIndexExists(storage::v3::LabelId /*label*/, storage::v3::PropertyId /*property*/) { return false; } - std::vector<std::pair<query::v2::Expression *, query::v2::plan::FilterInfo>> ExtractPrimaryKey( - storage::v3::LabelId label, std::vector<query::v2::plan::FilterInfo> property_filters) { - std::vector<std::pair<query::v2::Expression *, query::v2::plan::FilterInfo>> pk; - const auto schema = request_router_->GetSchemaForLabel(label); - - std::vector<storage::v3::PropertyId> schema_properties; - schema_properties.reserve(schema.size()); - - std::transform(schema.begin(), schema.end(), std::back_inserter(schema_properties), - [](const auto &schema_elem) { return schema_elem.property_id; }); - - for (const auto &property_filter : property_filters) { - const auto &property_id = NameToProperty(property_filter.property_filter->property_.name); - if (std::find(schema_properties.begin(), schema_properties.end(), property_id) != schema_properties.end()) { - pk.emplace_back(std::make_pair(property_filter.expression, property_filter)); - } - } - - return pk.size() == schema_properties.size() - ? pk - : std::vector<std::pair<query::v2::Expression *, query::v2::plan::FilterInfo>>{}; + std::vector<memgraph::storage::v3::SchemaProperty> GetSchemaForLabel(storage::v3::LabelId label) { + return request_router_->GetSchemaForLabel(label); } RequestRouterInterface *request_router_; diff --git a/src/storage/v3/shard_rsm.cpp b/src/storage/v3/shard_rsm.cpp index c23d31ebc..881796a70 100644 --- a/src/storage/v3/shard_rsm.cpp +++ b/src/storage/v3/shard_rsm.cpp @@ -539,12 +539,12 @@ msgs::ReadResponses ShardRsm::HandleRead(msgs::GetPropertiesRequest &&req) { const VertexAccessor &v_acc, const std::optional<EdgeAccessor> &e_acc) -> ShardResult<std::map<PropertyId, Value>> { if (!req.property_ids) { - const auto *schema = shard_->GetSchema(shard_->PrimaryLabel()); - MG_ASSERT(schema); - if (e_acc) { return CollectAllPropertiesFromAccessor(*e_acc, view); } + const auto *schema = shard_->GetSchema(shard_->PrimaryLabel()); + MG_ASSERT(schema); + return CollectAllPropertiesFromAccessor(v_acc, view, *schema); } diff --git a/tests/unit/query_common.hpp b/tests/unit/query_common.hpp index d497b76ba..784df5e21 100644 --- a/tests/unit/query_common.hpp +++ b/tests/unit/query_common.hpp @@ -486,7 +486,6 @@ 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<memgraph::query::Match>(true), {__VA_ARGS__}) diff --git a/tests/unit/query_plan_checker_v2.hpp b/tests/unit/query_plan_checker_v2.hpp index 1a971c4f0..0d7a6bc5f 100644 --- a/tests/unit/query_plan_checker_v2.hpp +++ b/tests/unit/query_plan_checker_v2.hpp @@ -17,6 +17,7 @@ #include "query/v2/plan/operator.hpp" #include "query/v2/plan/planner.hpp" #include "query/v2/plan/preprocess.hpp" +#include "utils/exceptions.hpp" namespace memgraph::query::v2::plan { @@ -289,7 +290,7 @@ class FakeDistributedDbAccessor { } bool LabelIndexExists(memgraph::storage::v3::LabelId label) const { - return label_index_.find(label) != label_index_.end(); + throw utils::NotYetImplemented("Label indicies are yet to be implemented."); } bool LabelPropertyIndexExists(memgraph::storage::v3::LabelId label, @@ -302,6 +303,8 @@ class FakeDistributedDbAccessor { return false; } + bool PrimaryLabelExists(storage::v3::LabelId label) { return label_index_.find(label) != label_index_.end(); } + 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) { @@ -382,13 +385,27 @@ class FakeDistributedDbAccessor { return memgraph::storage::v3::PropertyId::FromUint(0); } + std::vector<memgraph::storage::v3::SchemaProperty> GetSchemaForLabel(storage::v3::LabelId label) { + auto schema_properties = schemas_.at(label); + std::vector<memgraph::storage::v3::SchemaProperty> ret; + std::transform(schema_properties.begin(), schema_properties.end(), std::back_inserter(ret), [](const auto &prop) { + memgraph::storage::v3::SchemaProperty schema_prop = { + .property_id = prop, + // This should not be hardcoded, but for testing purposes it will suffice. + .type = memgraph::common::SchemaType::INT}; + + return schema_prop; + }); + return ret; + } + std::vector<std::pair<query::v2::Expression *, query::v2::plan::FilterInfo>> ExtractPrimaryKey( storage::v3::LabelId label, std::vector<query::v2::plan::FilterInfo> property_filters) { MG_ASSERT(schemas_.contains(label), "You did not specify the Schema for this label! Use FakeDistributedDbAccessor::CreateSchema(...)."); std::vector<std::pair<query::v2::Expression *, query::v2::plan::FilterInfo>> pk; - const auto schema = GetSchemaForLabel(label); + const auto schema = GetSchemaPropertiesForLabel(label); std::vector<storage::v3::PropertyId> schema_properties; schema_properties.reserve(schema.size()); @@ -408,7 +425,7 @@ class FakeDistributedDbAccessor { : std::vector<std::pair<query::v2::Expression *, query::v2::plan::FilterInfo>>{}; } - std::vector<memgraph::storage::v3::PropertyId> GetSchemaForLabel(storage::v3::LabelId label) { + std::vector<memgraph::storage::v3::PropertyId> GetSchemaPropertiesForLabel(storage::v3::LabelId label) { return schemas_.at(label); } From ea646e1803e3ff529fb3c4212c5d8f1fd38ab00d Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Mon, 23 Jan 2023 10:06:52 +0100 Subject: [PATCH 68/85] Add missing mock implementation to MockedRequestRouter --- tests/unit/mock_helpers.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/mock_helpers.hpp b/tests/unit/mock_helpers.hpp index c522b8602..0f4227a28 100644 --- a/tests/unit/mock_helpers.hpp +++ b/tests/unit/mock_helpers.hpp @@ -42,6 +42,7 @@ class MockedRequestRouter : public RequestRouterInterface { MOCK_METHOD(std::optional<storage::v3::LabelId>, MaybeNameToLabel, (const std::string &), (const)); MOCK_METHOD(bool, IsPrimaryLabel, (storage::v3::LabelId), (const)); MOCK_METHOD(bool, IsPrimaryKey, (storage::v3::LabelId, storage::v3::PropertyId), (const)); + MOCK_METHOD(std::vector<coordinator::SchemaProperty>, GetSchemaForLabel, (storage::v3::LabelId), (const)); }; class MockedLogicalOperator : public plan::LogicalOperator { From 69fa4e8c8de647bc147dc48a0f3228fadddc806a Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Mon, 23 Jan 2023 10:43:41 +0100 Subject: [PATCH 69/85] Return fals from unimplemented function, so the benchmark tests can run --- src/query/v2/plan/vertex_count_cache.hpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/query/v2/plan/vertex_count_cache.hpp b/src/query/v2/plan/vertex_count_cache.hpp index 7e3fc8530..995584f08 100644 --- a/src/query/v2/plan/vertex_count_cache.hpp +++ b/src/query/v2/plan/vertex_count_cache.hpp @@ -55,9 +55,7 @@ class VertexCountCache { return 1; } - bool LabelIndexExists(storage::v3::LabelId label) { - throw utils::NotYetImplemented("Label indicies are yet to be implemented."); - } + bool LabelIndexExists(storage::v3::LabelId /*label*/) { return false; } bool PrimaryLabelExists(storage::v3::LabelId label) { return request_router_->IsPrimaryLabel(label); } From fcbacdc80de068c72c09197f00d54259a7449de6 Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Mon, 23 Jan 2023 11:56:58 +0100 Subject: [PATCH 70/85] Rename ScanAllByPrimaryKey operator, fix e2e fail Rename ScanAllByPrimaryKey operator to ScanByPrimaryKey. Make the LabelIndexExist function use the same functionality as PrimaryKeyExists again, for now. Previously it was just returning false and before that it used the same implementation as PrimaryKeyExist. The change to false broke some existing e2e tests that relied on some label based indexing operator being instantiated. --- src/query/v2/plan/operator.cpp | 34 +++++++++---------- src/query/v2/plan/operator.lcp | 8 ++--- src/query/v2/plan/pretty_print.cpp | 10 +++--- src/query/v2/plan/pretty_print.hpp | 4 +-- src/query/v2/plan/read_write_type_checker.cpp | 2 +- src/query/v2/plan/read_write_type_checker.hpp | 2 +- src/query/v2/plan/rewrite/index_lookup.hpp | 6 ++-- src/query/v2/plan/vertex_count_cache.hpp | 2 +- src/utils/event_counter.cpp | 4 +-- tests/unit/query_plan_checker.hpp | 6 ++-- tests/unit/query_plan_checker_v2.hpp | 8 ++--- tests/unit/query_v2_plan.cpp | 4 +-- 12 files changed, 45 insertions(+), 45 deletions(-) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index 952f2bd94..fa20b4e48 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -93,7 +93,7 @@ extern const Event ScanAllByLabelOperator; extern const Event ScanAllByLabelPropertyRangeOperator; extern const Event ScanAllByLabelPropertyValueOperator; extern const Event ScanAllByLabelPropertyOperator; -extern const Event ScanAllByPrimaryKeyOperator; +extern const Event ScanByPrimaryKeyOperator; extern const Event ExpandOperator; extern const Event ExpandVariableOperator; extern const Event ConstructNamedPathOperator; @@ -519,12 +519,12 @@ class DistributedScanAllAndFilterCursor : public Cursor { std::optional<std::vector<Expression *>> filter_expressions_; }; -class DistributedScanAllByPrimaryKeyCursor : public Cursor { +class DistributedScanByPrimaryKeyCursor : public Cursor { public: - explicit DistributedScanAllByPrimaryKeyCursor(Symbol output_symbol, UniqueCursorPtr input_cursor, const char *op_name, - storage::v3::LabelId label, - std::optional<std::vector<Expression *>> filter_expressions, - std::vector<Expression *> primary_key) + explicit DistributedScanByPrimaryKeyCursor(Symbol output_symbol, UniqueCursorPtr input_cursor, const char *op_name, + storage::v3::LabelId label, + std::optional<std::vector<Expression *>> filter_expressions, + std::vector<Expression *> primary_key) : output_symbol_(output_symbol), input_cursor_(std::move(input_cursor)), op_name_(op_name), @@ -586,8 +586,8 @@ class DistributedScanAllByPrimaryKeyCursor : public Cursor { return false; } - void PullMultiple(MultiFrame &input_multi_frame, ExecutionContext &context) override { - throw utils::NotYetImplemented("Multiframe version of ScanAllByPrimaryKey is yet to be implemented."); + void PullMultiple(MultiFrame & /*input_multi_frame*/, ExecutionContext & /*context*/) override { + throw utils::NotYetImplemented("Multiframe version of ScanByPrimaryKey is yet to be implemented."); }; void Reset() override { input_cursor_->Reset(); } @@ -703,21 +703,21 @@ UniqueCursorPtr ScanAllByLabelProperty::MakeCursor(utils::MemoryResource *mem) c throw QueryRuntimeException("ScanAllByLabelProperty is not supported"); } -ScanAllByPrimaryKey::ScanAllByPrimaryKey(const std::shared_ptr<LogicalOperator> &input, Symbol output_symbol, - storage::v3::LabelId label, std::vector<query::v2::Expression *> primary_key, - storage::v3::View view) +ScanByPrimaryKey::ScanByPrimaryKey(const std::shared_ptr<LogicalOperator> &input, Symbol output_symbol, + storage::v3::LabelId label, std::vector<query::v2::Expression *> primary_key, + storage::v3::View view) : ScanAll(input, output_symbol, view), label_(label), primary_key_(primary_key) { MG_ASSERT(primary_key.front()); } -ACCEPT_WITH_INPUT(ScanAllByPrimaryKey) +ACCEPT_WITH_INPUT(ScanByPrimaryKey) -UniqueCursorPtr ScanAllByPrimaryKey::MakeCursor(utils::MemoryResource *mem) const { - EventCounter::IncrementCounter(EventCounter::ScanAllByPrimaryKeyOperator); +UniqueCursorPtr ScanByPrimaryKey::MakeCursor(utils::MemoryResource *mem) const { + EventCounter::IncrementCounter(EventCounter::ScanByPrimaryKeyOperator); - return MakeUniqueCursorPtr<DistributedScanAllByPrimaryKeyCursor>(mem, output_symbol_, input_->MakeCursor(mem), - "ScanAllByPrimaryKey", label_, - std::nullopt /*filter_expressions*/, primary_key_); + return MakeUniqueCursorPtr<DistributedScanByPrimaryKeyCursor>(mem, output_symbol_, input_->MakeCursor(mem), + "ScanByPrimaryKey", label_, + std::nullopt /*filter_expressions*/, primary_key_); } Expand::Expand(const std::shared_ptr<LogicalOperator> &input, Symbol input_symbol, Symbol node_symbol, diff --git a/src/query/v2/plan/operator.lcp b/src/query/v2/plan/operator.lcp index bc8b64e5d..d5a707c54 100644 --- a/src/query/v2/plan/operator.lcp +++ b/src/query/v2/plan/operator.lcp @@ -113,7 +113,7 @@ class ScanAllByLabel; class ScanAllByLabelPropertyRange; class ScanAllByLabelPropertyValue; class ScanAllByLabelProperty; -class ScanAllByPrimaryKey; +class ScanByPrimaryKey; class Expand; class ExpandVariable; class ConstructNamedPath; @@ -144,7 +144,7 @@ class Foreach; using LogicalOperatorCompositeVisitor = utils::CompositeVisitor< Once, CreateNode, CreateExpand, ScanAll, ScanAllByLabel, ScanAllByLabelPropertyRange, ScanAllByLabelPropertyValue, - ScanAllByLabelProperty, ScanAllByPrimaryKey, + ScanAllByLabelProperty, ScanByPrimaryKey, Expand, ExpandVariable, ConstructNamedPath, Filter, Produce, Delete, SetProperty, SetProperties, SetLabels, RemoveProperty, RemoveLabels, EdgeUniquenessFilter, Accumulate, Aggregate, Skip, Limit, OrderBy, Merge, @@ -855,8 +855,8 @@ given label and property. "ScanAll producing a single node with specified by the label and primary key") (:public #>cpp - ScanAllByPrimaryKey() {} - ScanAllByPrimaryKey(const std::shared_ptr<LogicalOperator> &input, + ScanByPrimaryKey() {} + ScanByPrimaryKey(const std::shared_ptr<LogicalOperator> &input, Symbol output_symbol, storage::v3::LabelId label, std::vector<query::v2::Expression*> primary_key, diff --git a/src/query/v2/plan/pretty_print.cpp b/src/query/v2/plan/pretty_print.cpp index 762a1b19a..7eb1dd5a9 100644 --- a/src/query/v2/plan/pretty_print.cpp +++ b/src/query/v2/plan/pretty_print.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -86,9 +86,9 @@ bool PlanPrinter::PreVisit(query::v2::plan::ScanAllByLabelProperty &op) { return true; } -bool PlanPrinter::PreVisit(query::v2::plan::ScanAllByPrimaryKey &op) { +bool PlanPrinter::PreVisit(query::v2::plan::ScanByPrimaryKey &op) { WithPrintLn([&](auto &out) { - out << "* ScanAllByPrimaryKey" + out << "* ScanByPrimaryKey" << " (" << op.output_symbol_.name() << " :" << request_router_->LabelToName(op.label_) << ")"; }); return true; @@ -487,9 +487,9 @@ bool PlanToJsonVisitor::PreVisit(ScanAllByLabelProperty &op) { return false; } -bool PlanToJsonVisitor::PreVisit(ScanAllByPrimaryKey &op) { +bool PlanToJsonVisitor::PreVisit(ScanByPrimaryKey &op) { json self; - self["name"] = "ScanAllByPrimaryKey"; + self["name"] = "ScanByPrimaryKey"; self["label"] = ToJson(op.label_, *request_router_); self["output_symbol"] = ToJson(op.output_symbol_); diff --git a/src/query/v2/plan/pretty_print.hpp b/src/query/v2/plan/pretty_print.hpp index d1dad22b7..44686f8dc 100644 --- a/src/query/v2/plan/pretty_print.hpp +++ b/src/query/v2/plan/pretty_print.hpp @@ -67,7 +67,7 @@ class PlanPrinter : public virtual HierarchicalLogicalOperatorVisitor { bool PreVisit(ScanAllByLabelPropertyValue &) override; bool PreVisit(ScanAllByLabelPropertyRange &) override; bool PreVisit(ScanAllByLabelProperty &) override; - bool PreVisit(ScanAllByPrimaryKey &) override; + bool PreVisit(ScanByPrimaryKey &) override; bool PreVisit(Expand &) override; bool PreVisit(ExpandVariable &) override; @@ -194,7 +194,7 @@ class PlanToJsonVisitor : public virtual HierarchicalLogicalOperatorVisitor { bool PreVisit(ScanAllByLabelPropertyRange &) override; bool PreVisit(ScanAllByLabelPropertyValue &) override; bool PreVisit(ScanAllByLabelProperty &) override; - bool PreVisit(ScanAllByPrimaryKey &) override; + bool PreVisit(ScanByPrimaryKey &) override; bool PreVisit(Produce &) override; bool PreVisit(Accumulate &) override; diff --git a/src/query/v2/plan/read_write_type_checker.cpp b/src/query/v2/plan/read_write_type_checker.cpp index d1e037d1f..3143cb1e0 100644 --- a/src/query/v2/plan/read_write_type_checker.cpp +++ b/src/query/v2/plan/read_write_type_checker.cpp @@ -35,7 +35,7 @@ PRE_VISIT(ScanAllByLabel, RWType::R, true) PRE_VISIT(ScanAllByLabelPropertyRange, RWType::R, true) PRE_VISIT(ScanAllByLabelPropertyValue, RWType::R, true) PRE_VISIT(ScanAllByLabelProperty, RWType::R, true) -PRE_VISIT(ScanAllByPrimaryKey, RWType::R, true) +PRE_VISIT(ScanByPrimaryKey, RWType::R, true) PRE_VISIT(Expand, RWType::R, true) PRE_VISIT(ExpandVariable, RWType::R, true) diff --git a/src/query/v2/plan/read_write_type_checker.hpp b/src/query/v2/plan/read_write_type_checker.hpp index 62d7af8b9..efab0267a 100644 --- a/src/query/v2/plan/read_write_type_checker.hpp +++ b/src/query/v2/plan/read_write_type_checker.hpp @@ -59,7 +59,7 @@ class ReadWriteTypeChecker : public virtual HierarchicalLogicalOperatorVisitor { bool PreVisit(ScanAllByLabelPropertyValue &) override; bool PreVisit(ScanAllByLabelPropertyRange &) override; bool PreVisit(ScanAllByLabelProperty &) override; - bool PreVisit(ScanAllByPrimaryKey &) override; + bool PreVisit(ScanByPrimaryKey &) override; bool PreVisit(Expand &) override; bool PreVisit(ExpandVariable &) override; diff --git a/src/query/v2/plan/rewrite/index_lookup.hpp b/src/query/v2/plan/rewrite/index_lookup.hpp index 37c14129e..5b67b464e 100644 --- a/src/query/v2/plan/rewrite/index_lookup.hpp +++ b/src/query/v2/plan/rewrite/index_lookup.hpp @@ -273,12 +273,12 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor { return true; } - bool PreVisit(ScanAllByPrimaryKey &op) override { + bool PreVisit(ScanByPrimaryKey &op) override { prev_ops_.push_back(&op); return true; } - bool PostVisit(ScanAllByPrimaryKey &) override { + bool PostVisit(ScanByPrimaryKey &) override { prev_ops_.pop_back(); return true; } @@ -639,7 +639,7 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor { std::vector<query::v2::Expression *> pk_expressions; std::transform(primary_key.begin(), primary_key.end(), std::back_inserter(pk_expressions), [](const auto &exp) { return exp.second.property_filter->value_; }); - return std::make_unique<ScanAllByPrimaryKey>(input, node_symbol, GetLabel(prim_label), pk_expressions); + return std::make_unique<ScanByPrimaryKey>(input, node_symbol, GetLabel(prim_label), pk_expressions); } } diff --git a/src/query/v2/plan/vertex_count_cache.hpp b/src/query/v2/plan/vertex_count_cache.hpp index 995584f08..bfe20ffe3 100644 --- a/src/query/v2/plan/vertex_count_cache.hpp +++ b/src/query/v2/plan/vertex_count_cache.hpp @@ -55,7 +55,7 @@ class VertexCountCache { return 1; } - bool LabelIndexExists(storage::v3::LabelId /*label*/) { return false; } + bool LabelIndexExists(storage::v3::LabelId label) { return PrimaryLabelExists(label); } bool PrimaryLabelExists(storage::v3::LabelId label) { return request_router_->IsPrimaryLabel(label); } diff --git a/src/utils/event_counter.cpp b/src/utils/event_counter.cpp index 8f7475ffb..2928027ee 100644 --- a/src/utils/event_counter.cpp +++ b/src/utils/event_counter.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -25,7 +25,7 @@ M(ScanAllByLabelPropertyValueOperator, "Number of times ScanAllByLabelPropertyValue operator was used.") \ M(ScanAllByLabelPropertyOperator, "Number of times ScanAllByLabelProperty operator was used.") \ M(ScanAllByIdOperator, "Number of times ScanAllById operator was used.") \ - M(ScanAllByPrimaryKeyOperator, "Number of times ScanAllByPrimaryKey operator was used.") \ + M(ScanByPrimaryKeyOperator, "Number of times ScanByPrimaryKey operator was used.") \ M(ExpandOperator, "Number of times Expand operator was used.") \ M(ExpandVariableOperator, "Number of times ExpandVariable operator was used.") \ M(ConstructNamedPathOperator, "Number of times ConstructNamedPath operator was used.") \ diff --git a/tests/unit/query_plan_checker.hpp b/tests/unit/query_plan_checker.hpp index da910ff99..7907f167f 100644 --- a/tests/unit/query_plan_checker.hpp +++ b/tests/unit/query_plan_checker.hpp @@ -337,12 +337,12 @@ class ExpectScanAllByLabelProperty : public OpChecker<ScanAllByLabelProperty> { memgraph::storage::PropertyId property_; }; -class ExpectScanAllByPrimaryKey : public OpChecker<v2::plan::ScanAllByPrimaryKey> { +class ExpectScanByPrimaryKey : public OpChecker<v2::plan::ScanByPrimaryKey> { public: - ExpectScanAllByPrimaryKey(memgraph::storage::v3::LabelId label, const std::vector<Expression *> &properties) + ExpectScanByPrimaryKey(memgraph::storage::v3::LabelId label, const std::vector<Expression *> &properties) : label_(label), properties_(properties) {} - void ExpectOp(v2::plan::ScanAllByPrimaryKey &scan_all, const SymbolTable &) override { + void ExpectOp(v2::plan::ScanByPrimaryKey &scan_all, const SymbolTable &) override { EXPECT_EQ(scan_all.label_, label_); bool primary_property_match = true; diff --git a/tests/unit/query_plan_checker_v2.hpp b/tests/unit/query_plan_checker_v2.hpp index 0d7a6bc5f..3604dcb25 100644 --- a/tests/unit/query_plan_checker_v2.hpp +++ b/tests/unit/query_plan_checker_v2.hpp @@ -62,7 +62,7 @@ class PlanChecker : public virtual HierarchicalLogicalOperatorVisitor { PRE_VISIT(ScanAllByLabelPropertyValue); PRE_VISIT(ScanAllByLabelPropertyRange); PRE_VISIT(ScanAllByLabelProperty); - PRE_VISIT(ScanAllByPrimaryKey); + PRE_VISIT(ScanByPrimaryKey); PRE_VISIT(Expand); PRE_VISIT(ExpandVariable); PRE_VISIT(Filter); @@ -175,12 +175,12 @@ class ExpectScanAllByLabelPropertyValue : public OpChecker<ScanAllByLabelPropert memgraph::query::v2::Expression *expression_; }; -class ExpectScanAllByPrimaryKey : public OpChecker<v2::plan::ScanAllByPrimaryKey> { +class ExpectScanByPrimaryKey : public OpChecker<v2::plan::ScanByPrimaryKey> { public: - ExpectScanAllByPrimaryKey(memgraph::storage::v3::LabelId label, const std::vector<Expression *> &properties) + ExpectScanByPrimaryKey(memgraph::storage::v3::LabelId label, const std::vector<Expression *> &properties) : label_(label), properties_(properties) {} - void ExpectOp(v2::plan::ScanAllByPrimaryKey &scan_all, const SymbolTable &) override { + void ExpectOp(v2::plan::ScanByPrimaryKey &scan_all, const SymbolTable &) override { EXPECT_EQ(scan_all.label_, label_); bool primary_property_match = true; diff --git a/tests/unit/query_v2_plan.cpp b/tests/unit/query_v2_plan.cpp index 215b6b8a3..8fc5e11c7 100644 --- a/tests/unit/query_v2_plan.cpp +++ b/tests/unit/query_v2_plan.cpp @@ -109,7 +109,7 @@ TYPED_TEST(TestPlanner, MatchFilterPropIsNotNull) { WHERE(EQ(PROPERTY_LOOKUP("n", prim_prop_one), LITERAL(1))), RETURN("n"))); auto symbol_table = (memgraph::expr::MakeSymbolTable(query)); auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); - CheckPlan(planner.plan(), symbol_table, ExpectScanAllByPrimaryKey(label, {expected_primary_key}), ExpectProduce()); + CheckPlan(planner.plan(), symbol_table, ExpectScanByPrimaryKey(label, {expected_primary_key}), ExpectProduce()); } // Exact primary key match, two elem as PK. { @@ -141,7 +141,7 @@ TYPED_TEST(TestPlanner, MatchFilterPropIsNotNull) { RETURN("n"))); auto symbol_table = (memgraph::expr::MakeSymbolTable(query)); auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); - CheckPlan(planner.plan(), symbol_table, ExpectScanAllByPrimaryKey(label, {expected_primary_key}), ExpectProduce()); + CheckPlan(planner.plan(), symbol_table, ExpectScanByPrimaryKey(label, {expected_primary_key}), ExpectProduce()); } // One elem is missing from PK, default to ScanAllByLabelPropertyValue. { From fa86d4c989b12234c8ae466143a653b6689d126d Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Mon, 23 Jan 2023 12:51:00 +0100 Subject: [PATCH 71/85] Offer sacrifice to the lisp gods --- src/query/v2/plan/operator.lcp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/query/v2/plan/operator.lcp b/src/query/v2/plan/operator.lcp index d5a707c54..e2d3eca56 100644 --- a/src/query/v2/plan/operator.lcp +++ b/src/query/v2/plan/operator.lcp @@ -845,7 +845,7 @@ given label and property. (:serialize (:slk)) (:clone)) -(lcp:define-class scan-all-by-primary-key (scan-all) +(lcp:define-class scan-by-primary-key (scan-all) ((label "::storage::v3::LabelId" :scope :public) (primary-key "std::vector<Expression*>" :scope :public) (expression "Expression *" :scope :public From e65f585fc60152e2ec3f5b63d181c886adec8c7a Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Mon, 23 Jan 2023 13:22:27 +0100 Subject: [PATCH 72/85] Appease clang-tidy --- src/query/v2/plan/pretty_print.hpp | 4 ++-- src/query/v2/plan/read_write_type_checker.hpp | 2 +- src/query/v2/plan/rewrite/index_lookup.hpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/query/v2/plan/pretty_print.hpp b/src/query/v2/plan/pretty_print.hpp index 44686f8dc..31ff17b18 100644 --- a/src/query/v2/plan/pretty_print.hpp +++ b/src/query/v2/plan/pretty_print.hpp @@ -67,7 +67,7 @@ class PlanPrinter : public virtual HierarchicalLogicalOperatorVisitor { bool PreVisit(ScanAllByLabelPropertyValue &) override; bool PreVisit(ScanAllByLabelPropertyRange &) override; bool PreVisit(ScanAllByLabelProperty &) override; - bool PreVisit(ScanByPrimaryKey &) override; + bool PreVisit(ScanByPrimaryKey & /*unused*/) override; bool PreVisit(Expand &) override; bool PreVisit(ExpandVariable &) override; @@ -194,7 +194,7 @@ class PlanToJsonVisitor : public virtual HierarchicalLogicalOperatorVisitor { bool PreVisit(ScanAllByLabelPropertyRange &) override; bool PreVisit(ScanAllByLabelPropertyValue &) override; bool PreVisit(ScanAllByLabelProperty &) override; - bool PreVisit(ScanByPrimaryKey &) override; + bool PreVisit(ScanByPrimaryKey & /*unused*/) override; bool PreVisit(Produce &) override; bool PreVisit(Accumulate &) override; diff --git a/src/query/v2/plan/read_write_type_checker.hpp b/src/query/v2/plan/read_write_type_checker.hpp index efab0267a..626cf7af3 100644 --- a/src/query/v2/plan/read_write_type_checker.hpp +++ b/src/query/v2/plan/read_write_type_checker.hpp @@ -59,7 +59,7 @@ class ReadWriteTypeChecker : public virtual HierarchicalLogicalOperatorVisitor { bool PreVisit(ScanAllByLabelPropertyValue &) override; bool PreVisit(ScanAllByLabelPropertyRange &) override; bool PreVisit(ScanAllByLabelProperty &) override; - bool PreVisit(ScanByPrimaryKey &) override; + bool PreVisit(ScanByPrimaryKey & /*unused*/) override; bool PreVisit(Expand &) override; bool PreVisit(ExpandVariable &) override; diff --git a/src/query/v2/plan/rewrite/index_lookup.hpp b/src/query/v2/plan/rewrite/index_lookup.hpp index 5b67b464e..17996d952 100644 --- a/src/query/v2/plan/rewrite/index_lookup.hpp +++ b/src/query/v2/plan/rewrite/index_lookup.hpp @@ -278,7 +278,7 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor { return true; } - bool PostVisit(ScanByPrimaryKey &) override { + bool PostVisit(ScanByPrimaryKey & /*unused*/) override { prev_ops_.pop_back(); return true; } From 1951d781d021d6e7afeb3a9a14adacf32b2e5a91 Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Mon, 23 Jan 2023 13:43:51 +0100 Subject: [PATCH 73/85] Appease clang-tidy --- src/query/v2/plan/read_write_type_checker.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/query/v2/plan/read_write_type_checker.cpp b/src/query/v2/plan/read_write_type_checker.cpp index 3143cb1e0..270a61c35 100644 --- a/src/query/v2/plan/read_write_type_checker.cpp +++ b/src/query/v2/plan/read_write_type_checker.cpp @@ -11,10 +11,10 @@ #include "query/v2/plan/read_write_type_checker.hpp" -#define PRE_VISIT(TOp, RWType, continue_visiting) \ - bool ReadWriteTypeChecker::PreVisit(TOp &op) { \ - UpdateType(RWType); \ - return continue_visiting; \ +#define PRE_VISIT(TOp, RWType, continue_visiting) \ + bool ReadWriteTypeChecker::PreVisit(TOp & /*op*/) { \ + UpdateType(RWType); \ + return continue_visiting; \ } namespace memgraph::query::v2::plan { From b7dbc7267be5866cd0cf580c40e1a269eaa16834 Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Mon, 23 Jan 2023 14:09:25 +0100 Subject: [PATCH 74/85] Appease clang-tidy bugprone-macro-parentheses --- src/query/v2/plan/read_write_type_checker.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/query/v2/plan/read_write_type_checker.cpp b/src/query/v2/plan/read_write_type_checker.cpp index 270a61c35..a8816f928 100644 --- a/src/query/v2/plan/read_write_type_checker.cpp +++ b/src/query/v2/plan/read_write_type_checker.cpp @@ -11,10 +11,10 @@ #include "query/v2/plan/read_write_type_checker.hpp" -#define PRE_VISIT(TOp, RWType, continue_visiting) \ - bool ReadWriteTypeChecker::PreVisit(TOp & /*op*/) { \ - UpdateType(RWType); \ - return continue_visiting; \ +#define PRE_VISIT(TOp, RWType, continue_visiting) \ + bool ReadWriteTypeChecker::PreVisit((TOp) & /*op*/) { \ + UpdateType(RWType); \ + return continue_visiting; \ } namespace memgraph::query::v2::plan { From 0aa7daf0026e66aa04b69a0ca51c7940d2a50090 Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Mon, 23 Jan 2023 14:34:16 +0100 Subject: [PATCH 75/85] NOLINTNEXTLINE(bugprone-macro-parentheses) --- src/query/v2/plan/read_write_type_checker.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/query/v2/plan/read_write_type_checker.cpp b/src/query/v2/plan/read_write_type_checker.cpp index a8816f928..c1cdbc857 100644 --- a/src/query/v2/plan/read_write_type_checker.cpp +++ b/src/query/v2/plan/read_write_type_checker.cpp @@ -11,10 +11,11 @@ #include "query/v2/plan/read_write_type_checker.hpp" -#define PRE_VISIT(TOp, RWType, continue_visiting) \ - bool ReadWriteTypeChecker::PreVisit((TOp) & /*op*/) { \ - UpdateType(RWType); \ - return continue_visiting; \ +#define PRE_VISIT(TOp, RWType, continue_visiting) \ + /*NOLINTNEXTLINE(bugprone-macro-parentheses)*/ \ + bool ReadWriteTypeChecker::PreVisit(TOp & /*op*/) { \ + UpdateType(RWType); \ + return continue_visiting; \ } namespace memgraph::query::v2::plan { From 27ff18733c9700364847900f0aa5f4c9d6876769 Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Mon, 23 Jan 2023 15:08:51 +0100 Subject: [PATCH 76/85] NOLINTNEXTLINE(cppcoreguidelines-macro-usage) --- src/query/v2/plan/read_write_type_checker.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/query/v2/plan/read_write_type_checker.cpp b/src/query/v2/plan/read_write_type_checker.cpp index c1cdbc857..28c1e8d0a 100644 --- a/src/query/v2/plan/read_write_type_checker.cpp +++ b/src/query/v2/plan/read_write_type_checker.cpp @@ -11,6 +11,7 @@ #include "query/v2/plan/read_write_type_checker.hpp" +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define PRE_VISIT(TOp, RWType, continue_visiting) \ /*NOLINTNEXTLINE(bugprone-macro-parentheses)*/ \ bool ReadWriteTypeChecker::PreVisit(TOp & /*op*/) { \ From b4ae8aea95298ffd2f875f78192fd0fa0fba9c1d Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Tue, 24 Jan 2023 15:59:50 +0100 Subject: [PATCH 77/85] Apply suggestions from code review --- src/io/local_transport/local_system.hpp | 4 +-- src/memgraph.cpp | 4 +-- src/query/v2/interpreter.hpp | 3 +- src/query/v2/request_router.hpp | 30 +++++-------------- .../cluster_property_test_cypher_queries.cpp | 2 +- 5 files changed, 13 insertions(+), 30 deletions(-) diff --git a/src/io/local_transport/local_system.hpp b/src/io/local_transport/local_system.hpp index feea44244..7b0cda537 100644 --- a/src/io/local_transport/local_system.hpp +++ b/src/io/local_transport/local_system.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -29,8 +29,6 @@ class LocalSystem { return Io{local_transport, address}; } - std::shared_ptr<LocalTransportHandle> GetTransportHandle() const { return local_transport_handle_; } - void ShutDown() { local_transport_handle_->ShutDown(); } }; diff --git a/src/memgraph.cpp b/src/memgraph.cpp index cdb1e63df..35fd20ad7 100644 --- a/src/memgraph.cpp +++ b/src/memgraph.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -640,7 +640,7 @@ int main(int argc, char **argv) { memgraph::machine_manager::MachineManager<memgraph::io::local_transport::LocalTransport> mm{io, config, coordinator}; std::jthread mm_thread([&mm] { mm.Run(); }); - auto rr_factory = std::make_unique<memgraph::query::v2::LocalRequestRouterFactory>(ls.GetTransportHandle()); + auto rr_factory = std::make_unique<memgraph::query::v2::LocalRequestRouterFactory>(io); memgraph::query::v2::InterpreterContext interpreter_context{ (memgraph::storage::v3::Shard *)(nullptr), diff --git a/src/query/v2/interpreter.hpp b/src/query/v2/interpreter.hpp index 8f754b84c..9c2bef3d2 100644 --- a/src/query/v2/interpreter.hpp +++ b/src/query/v2/interpreter.hpp @@ -191,6 +191,7 @@ struct InterpreterContext { IdAllocator edge_ids_alloc; coordinator::Address coordinator_address; + std::unique_ptr<RequestRouterFactory> request_router_factory_; storage::v3::LabelId NameToLabelId(std::string_view label_name) { return storage::v3::LabelId::FromUint(query_id_mapper_.NameToId(label_name)); @@ -204,8 +205,6 @@ struct InterpreterContext { return storage::v3::EdgeTypeId::FromUint(query_id_mapper_.NameToId(edge_type_name)); } - std::unique_ptr<RequestRouterFactory> request_router_factory_; - private: // TODO Replace with local map of labels, properties and edge type ids storage::v3::NameIdMapper query_id_mapper_; diff --git a/src/query/v2/request_router.hpp b/src/query/v2/request_router.hpp index 0515633c9..252278bb8 100644 --- a/src/query/v2/request_router.hpp +++ b/src/query/v2/request_router.hpp @@ -753,11 +753,8 @@ class RequestRouterFactory { using TransportHandleVariant = std::variant<LocalTransportHandlePtr, SimulatorTransportHandlePtr>; - TransportHandleVariant transport_handle_; - public: - explicit RequestRouterFactory(const TransportHandleVariant &transport_handle) : transport_handle_(transport_handle) {} - + RequestRouterFactory() = default; RequestRouterFactory(const RequestRouterFactory &) = delete; RequestRouterFactory &operator=(const RequestRouterFactory &) = delete; RequestRouterFactory(RequestRouterFactory &&) = delete; @@ -765,32 +762,22 @@ class RequestRouterFactory { virtual ~RequestRouterFactory() = default; - virtual TransportHandleVariant GetTransportHandle() { return transport_handle_; } - virtual std::unique_ptr<RequestRouterInterface> CreateRequestRouter( const coordinator::Address &coordinator_address) const = 0; }; class LocalRequestRouterFactory : public RequestRouterFactory { + io::Io<memgraph::io::local_transport::LocalTransport> &io_; + public: - explicit LocalRequestRouterFactory(const TransportHandleVariant &transport_handle) - : RequestRouterFactory(transport_handle) {} + explicit LocalRequestRouterFactory(io::Io<memgraph::io::local_transport::LocalTransport> &io) : io_(io) {} std::unique_ptr<RequestRouterInterface> CreateRequestRouter( const coordinator::Address &coordinator_address) const override { using TransportType = io::local_transport::LocalTransport; - auto actual_transport_handle = std::get<LocalTransportHandlePtr>(transport_handle_); - boost::uuids::uuid random_uuid; - io::Address unique_local_addr_query; - - random_uuid = boost::uuids::uuid{boost::uuids::random_generator()()}; - unique_local_addr_query = memgraph::coordinator::Address::UniqueLocalAddress(); - - TransportType local_transport(actual_transport_handle); - auto local_transport_io = io::Io<TransportType>(local_transport, unique_local_addr_query); - - auto query_io = local_transport_io.ForkLocal(random_uuid); + auto query_io = io_.ForkLocal(boost::uuids::uuid{boost::uuids::random_generator()()}); + auto local_transport_io = io_.ForkLocal(boost::uuids::uuid{boost::uuids::random_generator()()}); return std::make_unique<RequestRouter<TransportType>>( coordinator::CoordinatorClient<TransportType>(query_io, coordinator_address, {coordinator_address}), @@ -802,13 +789,12 @@ class SimulatedRequestRouterFactory : public RequestRouterFactory { io::simulator::Simulator *simulator_; public: - explicit SimulatedRequestRouterFactory(io::simulator::Simulator &simulator) - : RequestRouterFactory(simulator.GetSimulatorHandle()), simulator_(&simulator) {} + explicit SimulatedRequestRouterFactory(io::simulator::Simulator &simulator) : simulator_(&simulator) {} std::unique_ptr<RequestRouterInterface> CreateRequestRouter( const coordinator::Address &coordinator_address) const override { using TransportType = io::simulator::SimulatorTransport; - auto actual_transport_handle = std::get<SimulatorTransportHandlePtr>(transport_handle_); + auto actual_transport_handle = simulator_->GetSimulatorHandle(); boost::uuids::uuid random_uuid; io::Address unique_local_addr_query; diff --git a/tests/simulation/cluster_property_test_cypher_queries.cpp b/tests/simulation/cluster_property_test_cypher_queries.cpp index 4996b4c2f..e35edc033 100644 --- a/tests/simulation/cluster_property_test_cypher_queries.cpp +++ b/tests/simulation/cluster_property_test_cypher_queries.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 From fa8eee2043c0a465a4cfc2ce077fc6f993a842ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Tue, 24 Jan 2023 16:19:24 +0100 Subject: [PATCH 78/85] Use destination vertex as other end for out edges --- src/storage/v3/request_helper.cpp | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/storage/v3/request_helper.cpp b/src/storage/v3/request_helper.cpp index 6b889fe16..f13c5a82e 100644 --- a/src/storage/v3/request_helper.cpp +++ b/src/storage/v3/request_helper.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -321,12 +321,15 @@ EdgeFiller InitializeEdgeFillerFunction(const msgs::ExpandOneRequest &req) { value_properties.insert(std::make_pair(prop_key, FromPropertyValueToValue(std::move(prop_val)))); } using EdgeWithAllProperties = msgs::ExpandOneResultRow::EdgeWithAllProperties; - EdgeWithAllProperties edges{ToMsgsVertexId(edge.From()), msgs::EdgeType{edge.EdgeType()}, edge.Gid().AsUint(), - std::move(value_properties)}; + if (is_in_edge) { - result_row.in_edges_with_all_properties.push_back(std::move(edges)); + result_row.in_edges_with_all_properties.push_back( + EdgeWithAllProperties{ToMsgsVertexId(edge.From()), msgs::EdgeType{edge.EdgeType()}, edge.Gid().AsUint(), + std::move(value_properties)}); } else { - result_row.out_edges_with_all_properties.push_back(std::move(edges)); + result_row.out_edges_with_all_properties.push_back( + EdgeWithAllProperties{ToMsgsVertexId(edge.To()), msgs::EdgeType{edge.EdgeType()}, edge.Gid().AsUint(), + std::move(value_properties)}); } return {}; }; @@ -346,12 +349,15 @@ EdgeFiller InitializeEdgeFillerFunction(const msgs::ExpandOneRequest &req) { value_properties.emplace_back(FromPropertyValueToValue(std::move(property_result.GetValue()))); } using EdgeWithSpecificProperties = msgs::ExpandOneResultRow::EdgeWithSpecificProperties; - EdgeWithSpecificProperties edges{ToMsgsVertexId(edge.From()), msgs::EdgeType{edge.EdgeType()}, - edge.Gid().AsUint(), std::move(value_properties)}; + if (is_in_edge) { - result_row.in_edges_with_specific_properties.push_back(std::move(edges)); + result_row.in_edges_with_specific_properties.push_back( + EdgeWithSpecificProperties{ToMsgsVertexId(edge.From()), msgs::EdgeType{edge.EdgeType()}, + edge.Gid().AsUint(), std::move(value_properties)}); } else { - result_row.out_edges_with_specific_properties.push_back(std::move(edges)); + result_row.out_edges_with_specific_properties.push_back( + EdgeWithSpecificProperties{ToMsgsVertexId(edge.To()), msgs::EdgeType{edge.EdgeType()}, edge.Gid().AsUint(), + std::move(value_properties)}); } return {}; }; From 4908af5a188549f440506a06ebd62d0b91c3ed02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Tue, 24 Jan 2023 16:33:15 +0100 Subject: [PATCH 79/85] Make `DistributedExpand` operator handle repeated vertices --- src/query/v2/plan/operator.cpp | 132 ++++++++++++++++++++------------- 1 file changed, 79 insertions(+), 53 deletions(-) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index fdb89168c..0868f24a5 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -2842,7 +2842,7 @@ class DistributedExpandCursor : public Cursor { } } - void InitEdgesMultiple(ExecutionContext &context) { + void InitEdgesMultiple() { // This function won't work if any vertex id is duplicated in the input, because: // 1. vertex_id_to_result_row is not a multimap // 2. if self_.common_.existing_node is true, then we erase edges that might be necessary for the input vertex on a @@ -2851,49 +2851,28 @@ class DistributedExpandCursor : public Cursor { const auto &vertex_value = frame[self_.input_symbol_]; if (vertex_value.IsNull()) { + ResetMultiFrameEdgeIts(); return; } ExpectType(self_.input_symbol_, vertex_value, TypedValue::Type::Vertex); const auto &vertex = vertex_value.ValueVertex(); - const auto convert_edges = [&vertex, &context]( - std::vector<msgs::ExpandOneResultRow::EdgeWithSpecificProperties> &&edge_messages, - const EdgeAtom::Direction direction) { - std::vector<EdgeAccessor> edge_accessors; - edge_accessors.reserve(edge_messages.size()); + current_vertex_ = &vertex; - switch (direction) { - case EdgeAtom::Direction::IN: { - for (auto &edge : edge_messages) { - edge_accessors.emplace_back(msgs::Edge{std::move(edge.other_end), vertex.Id(), {}, {edge.gid}, edge.type}, - context.request_router); - } - break; - } - case EdgeAtom::Direction::OUT: { - for (auto &edge : edge_messages) { - edge_accessors.emplace_back(msgs::Edge{vertex.Id(), std::move(edge.other_end), {}, {edge.gid}, edge.type}, - context.request_router); - } - break; - } - case EdgeAtom::Direction::BOTH: { - LOG_FATAL("Must indicate exact expansion direction here"); - } - } - return edge_accessors; - }; + auto &ref_counted_result_row = vertex_id_to_result_row.at(vertex.Id()); + auto &result_row = *ref_counted_result_row.result_row; - auto *result_row = vertex_id_to_result_row[vertex.Id()]; - current_in_edges_.clear(); - current_in_edges_ = - convert_edges(std::move(result_row->in_edges_with_specific_properties), EdgeAtom::Direction::IN); - current_in_edge_it_ = current_in_edges_.begin(); - current_out_edges_ = - convert_edges(std::move(result_row->out_edges_with_specific_properties), EdgeAtom::Direction::OUT); - current_out_edge_it_ = current_out_edges_.begin(); - vertex_id_to_result_row.erase(vertex.Id()); + current_in_edge_mf_it_ = result_row.in_edges_with_specific_properties.begin(); + in_edges_end_it_ = result_row.in_edges_with_specific_properties.end(); + current_out_edge_mf_it_ = result_row.out_edges_with_specific_properties.begin(); + out_edges_end_it_ = result_row.out_edges_with_specific_properties.end(); + + if (ref_counted_result_row.ref_count == 1) { + vertex_id_to_result_row.erase(vertex.Id()); + } else { + ref_counted_result_row.ref_count--; + } } bool PullInputFrames(ExecutionContext &context) { @@ -2906,6 +2885,8 @@ class DistributedExpandCursor : public Cursor { return false; } + vertex_id_to_result_row.clear(); + msgs::ExpandOneRequest request; request.direction = DirectionToMsgsDirection(self_.common_.direction); // to not fetch any properties of the edges @@ -2918,16 +2899,21 @@ class DistributedExpandCursor : public Cursor { ExpectType(self_.input_symbol_, vertex_value, TypedValue::Type::Vertex); const auto &vertex = vertex_value.ValueVertex(); - request.src_vertices.push_back(vertex.Id()); + auto [it, inserted] = vertex_id_to_result_row.try_emplace(vertex.Id(), RefCountedResultRow{1U, nullptr}); + + if (inserted) { + request.src_vertices.push_back(vertex.Id()); + } else { + it->second.ref_count++; + } } result_rows_ = std::invoke([&context, &request]() mutable { SCOPED_REQUEST_WAIT_PROFILE; return context.request_router->ExpandOne(std::move(request)); }); - vertex_id_to_result_row.clear(); for (auto &row : result_rows_) { - vertex_id_to_result_row[row.src_vertex.id] = &row; + vertex_id_to_result_row[row.src_vertex.id].result_row = &row; } return true; @@ -2956,7 +2942,7 @@ class DistributedExpandCursor : public Cursor { if (own_frames_it_ == own_frames_consumer_->end()) { state_ = State::PullInputAndEdges; } else { - InitEdgesMultiple(context); + InitEdgesMultiple(); state_ = State::PopulateOutput; } break; @@ -2965,28 +2951,45 @@ class DistributedExpandCursor : public Cursor { if (!output_multi_frame.HasInvalidFrame()) { return populated_any; } - if (current_in_edge_it_ == current_in_edges_.end() && current_out_edge_it_ == current_out_edges_.end()) { + if (current_in_edge_mf_it_ == in_edges_end_it_ && current_out_edge_mf_it_ == out_edges_end_it_) { own_frames_it_->MakeInvalid(); ++own_frames_it_; state_ = State::InitInOutEdgesIt; continue; } auto populate_edges = [this, &context, &output_frames_populator, &populated_any]( - const EdgeAtom::Direction direction, std::vector<EdgeAccessor>::iterator ¤t, - const std::vector<EdgeAccessor>::iterator &end) { + const EdgeAtom::Direction direction, EdgesIterator ¤t, + const EdgesIterator &end) { for (auto output_frame_it = output_frames_populator.begin(); output_frame_it != output_frames_populator.end() && current != end; ++output_frame_it) { auto &edge = *current; + // auto &asd = edge.other_end.second[0]; ++current; auto &output_frame = *output_frame_it; output_frame = *own_frames_it_; - output_frame[self_.common_.edge_symbol] = edge; + switch (direction) { + case EdgeAtom::Direction::IN: { + output_frame[self_.common_.edge_symbol] = + EdgeAccessor{msgs::Edge{edge.other_end, current_vertex_->Id(), {}, {edge.gid}, edge.type}, + context.request_router}; + break; + } + case EdgeAtom::Direction::OUT: { + output_frame[self_.common_.edge_symbol] = + EdgeAccessor{msgs::Edge{current_vertex_->Id(), edge.other_end, {}, {edge.gid}, edge.type}, + context.request_router}; + break; + } + case EdgeAtom::Direction::BOTH: { + LOG_FATAL("Must indicate exact expansion direction here"); + } + }; PullDstVertex(output_frame, context, direction); populated_any = true; } }; - populate_edges(EdgeAtom::Direction::IN, current_in_edge_it_, current_in_edges_.end()); - populate_edges(EdgeAtom::Direction::OUT, current_out_edge_it_, current_out_edges_.end()); + populate_edges(EdgeAtom::Direction::IN, current_in_edge_mf_it_, in_edges_end_it_); + populate_edges(EdgeAtom::Direction::OUT, current_out_edge_mf_it_, out_edges_end_it_); break; } case State::Exhausted: { @@ -3017,28 +3020,51 @@ class DistributedExpandCursor : public Cursor { own_frames_consumer_.reset(); own_multi_frame_->MakeAllFramesInvalid(); state_ = State::PullInputAndEdges; + current_in_edges_.clear(); current_out_edges_.clear(); current_in_edge_it_ = current_in_edges_.end(); current_out_edge_it_ = current_out_edges_.end(); + + ResetMultiFrameEdgeIts(); } private: + void ResetMultiFrameEdgeIts() { + in_edges_end_it_ = EdgesIterator{}; + current_in_edge_mf_it_ = in_edges_end_it_; + out_edges_end_it_ = EdgesIterator{}; + current_out_edge_mf_it_ = out_edges_end_it_; + } enum class State { PullInputAndEdges, InitInOutEdgesIt, PopulateOutput, Exhausted }; + struct RefCountedResultRow { + size_t ref_count{0U}; + msgs::ExpandOneResultRow *result_row{nullptr}; + }; + const Expand &self_; const UniqueCursorPtr input_cursor_; + using EdgesVector = std::vector<msgs::ExpandOneResultRow::EdgeWithSpecificProperties>; + using EdgesIterator = EdgesVector::iterator; + EdgesIterator current_in_edge_mf_it_; + EdgesIterator in_edges_end_it_; + EdgesIterator current_out_edge_mf_it_; + EdgesIterator out_edges_end_it_; + State state_{State::PullInputAndEdges}; + std::optional<MultiFrame> own_multi_frame_; + std::optional<ValidFramesConsumer> own_frames_consumer_; + const VertexAccessor *current_vertex_{nullptr}; + ValidFramesConsumer::Iterator own_frames_it_; + std::vector<msgs::ExpandOneResultRow> result_rows_; + // This won't work if any vertex id is duplicated in the input + std::unordered_map<msgs::VertexId, RefCountedResultRow> vertex_id_to_result_row; + + // TODO(antaljanosbenjamin): Remove when single frame approach is removed std::vector<EdgeAccessor> current_in_edges_; std::vector<EdgeAccessor> current_out_edges_; std::vector<EdgeAccessor>::iterator current_in_edge_it_; std::vector<EdgeAccessor>::iterator current_out_edge_it_; - State state_{State::PullInputAndEdges}; - std::optional<MultiFrame> own_multi_frame_; - std::optional<ValidFramesConsumer> own_frames_consumer_; - ValidFramesConsumer::Iterator own_frames_it_; - std::vector<msgs::ExpandOneResultRow> result_rows_; - // This won't work if any vertex id is duplicated in the input - std::unordered_map<msgs::VertexId, msgs::ExpandOneResultRow *> vertex_id_to_result_row; }; } // namespace memgraph::query::v2::plan From 36fccc32c2655a72a6e740b990aecbe6f863fd9b Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Tue, 24 Jan 2023 16:59:38 +0100 Subject: [PATCH 80/85] Address PR comments --- src/query/v2/plan/operator.cpp | 20 +++++++------------- src/query/v2/plan/vertex_count_cache.hpp | 2 +- src/query/v2/request_router.hpp | 4 ++-- tests/unit/mock_helpers.hpp | 2 +- tests/unit/query_plan_checker_v2.hpp | 2 +- tests/unit/query_v2_expression_evaluator.cpp | 5 +++-- 6 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index fa20b4e48..c07d8d4aa 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -573,15 +573,13 @@ class DistributedScanByPrimaryKeyCursor : public Cursor { throw HintedAbortError(); } - if (!input_cursor_->Pull(frame, context)) { - return false; - } - - auto &request_router = *context.request_router; - auto vertex = MakeRequestSingleFrame(frame, request_router, context); - if (vertex) { - frame[output_symbol_] = TypedValue(std::move(*vertex)); - return true; + while (input_cursor_->Pull(frame, context)) { + auto &request_router = *context.request_router; + auto vertex = MakeRequestSingleFrame(frame, request_router, context); + if (vertex) { + frame[output_symbol_] = TypedValue(std::move(*vertex)); + return true; + } } return false; } @@ -601,10 +599,6 @@ class DistributedScanByPrimaryKeyCursor : public Cursor { storage::v3::LabelId label_; std::optional<std::vector<Expression *>> filter_expressions_; std::vector<Expression *> primary_key_; - std::optional<MultiFrame> own_multi_frames_; - std::optional<ValidFramesConsumer> valid_frames_consumer_; - ValidFramesConsumer::Iterator valid_frames_it_; - std::queue<FrameWithValidity> frames_buffer_; }; ScanAll::ScanAll(const std::shared_ptr<LogicalOperator> &input, Symbol output_symbol, storage::v3::View view) diff --git a/src/query/v2/plan/vertex_count_cache.hpp b/src/query/v2/plan/vertex_count_cache.hpp index bfe20ffe3..ae6cdad31 100644 --- a/src/query/v2/plan/vertex_count_cache.hpp +++ b/src/query/v2/plan/vertex_count_cache.hpp @@ -61,7 +61,7 @@ class VertexCountCache { bool LabelPropertyIndexExists(storage::v3::LabelId /*label*/, storage::v3::PropertyId /*property*/) { return false; } - std::vector<memgraph::storage::v3::SchemaProperty> GetSchemaForLabel(storage::v3::LabelId label) { + const std::vector<memgraph::storage::v3::SchemaProperty> &GetSchemaForLabel(storage::v3::LabelId label) { return request_router_->GetSchemaForLabel(label); } diff --git a/src/query/v2/request_router.hpp b/src/query/v2/request_router.hpp index 3b313e225..c175ba413 100644 --- a/src/query/v2/request_router.hpp +++ b/src/query/v2/request_router.hpp @@ -114,7 +114,7 @@ class RequestRouterInterface { virtual std::optional<storage::v3::LabelId> MaybeNameToLabel(const std::string &name) const = 0; virtual bool IsPrimaryLabel(storage::v3::LabelId label) const = 0; virtual bool IsPrimaryKey(storage::v3::LabelId primary_label, storage::v3::PropertyId property) const = 0; - virtual std::vector<coordinator::SchemaProperty> GetSchemaForLabel(storage::v3::LabelId label) const = 0; + virtual const std::vector<coordinator::SchemaProperty> &GetSchemaForLabel(storage::v3::LabelId label) const = 0; }; // TODO(kostasrim)rename this class template @@ -233,7 +233,7 @@ class RequestRouter : public RequestRouterInterface { }) != schema_it->second.end(); } - std::vector<coordinator::SchemaProperty> GetSchemaForLabel(storage::v3::LabelId label) const override { + const std::vector<coordinator::SchemaProperty> &GetSchemaForLabel(storage::v3::LabelId label) const override { return shards_map_.schemas.at(label); } diff --git a/tests/unit/mock_helpers.hpp b/tests/unit/mock_helpers.hpp index 0f4227a28..b7d764ac8 100644 --- a/tests/unit/mock_helpers.hpp +++ b/tests/unit/mock_helpers.hpp @@ -42,7 +42,7 @@ class MockedRequestRouter : public RequestRouterInterface { MOCK_METHOD(std::optional<storage::v3::LabelId>, MaybeNameToLabel, (const std::string &), (const)); MOCK_METHOD(bool, IsPrimaryLabel, (storage::v3::LabelId), (const)); MOCK_METHOD(bool, IsPrimaryKey, (storage::v3::LabelId, storage::v3::PropertyId), (const)); - MOCK_METHOD(std::vector<coordinator::SchemaProperty>, GetSchemaForLabel, (storage::v3::LabelId), (const)); + MOCK_METHOD(const std::vector<coordinator::SchemaProperty> &, GetSchemaForLabel, (storage::v3::LabelId), (const)); }; class MockedLogicalOperator : public plan::LogicalOperator { diff --git a/tests/unit/query_plan_checker_v2.hpp b/tests/unit/query_plan_checker_v2.hpp index 3604dcb25..014f1c2bb 100644 --- a/tests/unit/query_plan_checker_v2.hpp +++ b/tests/unit/query_plan_checker_v2.hpp @@ -381,7 +381,7 @@ class FakeDistributedDbAccessor { return find_in_secondary_properties->second; } - MG_ASSERT(false, "The property does not exist as a primary or a secondary property."); + LOG_FATAL("The property does not exist as a primary or a secondary property."); return memgraph::storage::v3::PropertyId::FromUint(0); } diff --git a/tests/unit/query_v2_expression_evaluator.cpp b/tests/unit/query_v2_expression_evaluator.cpp index ffbebb4ba..b941d3463 100644 --- a/tests/unit/query_v2_expression_evaluator.cpp +++ b/tests/unit/query_v2_expression_evaluator.cpp @@ -125,8 +125,9 @@ class MockedRequestRouter : public RequestRouterInterface { bool IsPrimaryKey(LabelId primary_label, PropertyId property) const override { return true; } - std::vector<coordinator::SchemaProperty> GetSchemaForLabel(storage::v3::LabelId /*label*/) const override { - return std::vector<coordinator::SchemaProperty>{}; + const std::vector<coordinator::SchemaProperty> &GetSchemaForLabel(storage::v3::LabelId /*label*/) const override { + static std::vector<coordinator::SchemaProperty> schema; + return schema; }; private: From 7cb07672ffcfcd3b4b73563efdebeea5c2165305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Tue, 24 Jan 2023 17:17:47 +0100 Subject: [PATCH 81/85] Make `DistributedExpandCursor` handle existing nodes with `MultiFrame` --- src/query/v2/plan/operator.cpp | 42 +++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index 0868f24a5..64a8c6f8c 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -2865,8 +2865,10 @@ class DistributedExpandCursor : public Cursor { current_in_edge_mf_it_ = result_row.in_edges_with_specific_properties.begin(); in_edges_end_it_ = result_row.in_edges_with_specific_properties.end(); + AdvanceUntilSuitableEdge(current_in_edge_mf_it_, in_edges_end_it_); current_out_edge_mf_it_ = result_row.out_edges_with_specific_properties.begin(); out_edges_end_it_ = result_row.out_edges_with_specific_properties.end(); + AdvanceUntilSuitableEdge(current_out_edge_mf_it_, out_edges_end_it_); if (ref_counted_result_row.ref_count == 1) { vertex_id_to_result_row.erase(vertex.Id()); @@ -2921,7 +2923,6 @@ class DistributedExpandCursor : public Cursor { bool PullMultiple(MultiFrame &output_multi_frame, ExecutionContext &context) override { SCOPED_PROFILE_OP("DistributedExpandMF"); - MG_ASSERT(!self_.common_.existing_node); EnsureOwnMultiFrameIsGood(output_multi_frame); // A helper function for expanding a node from an edge. @@ -2963,8 +2964,6 @@ class DistributedExpandCursor : public Cursor { for (auto output_frame_it = output_frames_populator.begin(); output_frame_it != output_frames_populator.end() && current != end; ++output_frame_it) { auto &edge = *current; - // auto &asd = edge.other_end.second[0]; - ++current; auto &output_frame = *output_frame_it; output_frame = *own_frames_it_; switch (direction) { @@ -2985,6 +2984,8 @@ class DistributedExpandCursor : public Cursor { } }; PullDstVertex(output_frame, context, direction); + ++current; + AdvanceUntilSuitableEdge(current, end); populated_any = true; } }; @@ -3030,12 +3031,6 @@ class DistributedExpandCursor : public Cursor { } private: - void ResetMultiFrameEdgeIts() { - in_edges_end_it_ = EdgesIterator{}; - current_in_edge_mf_it_ = in_edges_end_it_; - out_edges_end_it_ = EdgesIterator{}; - current_out_edge_mf_it_ = out_edges_end_it_; - } enum class State { PullInputAndEdges, InitInOutEdgesIt, PopulateOutput, Exhausted }; struct RefCountedResultRow { @@ -3043,10 +3038,35 @@ class DistributedExpandCursor : public Cursor { msgs::ExpandOneResultRow *result_row{nullptr}; }; + using EdgeWithSpecificProperties = msgs::ExpandOneResultRow::EdgeWithSpecificProperties; + using EdgesVector = std::vector<EdgeWithSpecificProperties>; + using EdgesIterator = EdgesVector::iterator; + + void ResetMultiFrameEdgeIts() { + in_edges_end_it_ = EdgesIterator{}; + current_in_edge_mf_it_ = in_edges_end_it_; + out_edges_end_it_ = EdgesIterator{}; + current_out_edge_mf_it_ = out_edges_end_it_; + } + + void AdvanceUntilSuitableEdge(EdgesIterator ¤t, const EdgesIterator &end) { + if (!self_.common_.existing_node) { + return; + } + + const auto &existing_node_value = (*own_frames_it_)[self_.common_.node_symbol]; + if (existing_node_value.IsNull()) { + current = end; + return; + } + const auto &existing_node = existing_node_value.ValueVertex(); + current = std::find_if(current, end, [&existing_node](const EdgeWithSpecificProperties &edge) { + return edge.other_end == existing_node.Id(); + }); + } + const Expand &self_; const UniqueCursorPtr input_cursor_; - using EdgesVector = std::vector<msgs::ExpandOneResultRow::EdgeWithSpecificProperties>; - using EdgesIterator = EdgesVector::iterator; EdgesIterator current_in_edge_mf_it_; EdgesIterator in_edges_end_it_; EdgesIterator current_out_edge_mf_it_; From e2a1029120faf4d44301da46ab67d7ed29ef61d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Tue, 24 Jan 2023 17:24:10 +0100 Subject: [PATCH 82/85] Fix simulation test --- tests/simulation/shard_rsm.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/simulation/shard_rsm.cpp b/tests/simulation/shard_rsm.cpp index 8a79cb23b..5c35b822a 100644 --- a/tests/simulation/shard_rsm.cpp +++ b/tests/simulation/shard_rsm.cpp @@ -1305,7 +1305,7 @@ void TestGetProperties(ShardClient &client) { MG_ASSERT(!result.error); MG_ASSERT(result.result_row.size() == 2); for (const auto &elem : result.result_row) { - MG_ASSERT(elem.props.size() == 3); + MG_ASSERT(elem.props.size() == 4); } } { @@ -1316,7 +1316,7 @@ void TestGetProperties(ShardClient &client) { MG_ASSERT(!result.result_row.empty()); MG_ASSERT(result.result_row.size() == 3); for (const auto &elem : result.result_row) { - MG_ASSERT(elem.props.size() == 4); + MG_ASSERT(elem.props.size() == 3); } } { From 3bc9c571a03aefaff159bbdfcc0dcbdaaf91e3f6 Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Wed, 25 Jan 2023 07:02:03 +0100 Subject: [PATCH 83/85] Make the use RequestRouter more restricted --- src/query/v2/interpreter.hpp | 5 ++++- src/query/v2/request_router.hpp | 2 +- tests/simulation/simulation_interpreter.hpp | 10 +--------- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/query/v2/interpreter.hpp b/src/query/v2/interpreter.hpp index 9c2bef3d2..4bffba2fa 100644 --- a/src/query/v2/interpreter.hpp +++ b/src/query/v2/interpreter.hpp @@ -296,7 +296,10 @@ class Interpreter final { */ void Abort(); - RequestRouterInterface *GetRequestRouter() { return request_router_.get(); } + const RequestRouterInterface *GetRequestRouter() const { return request_router_.get(); } + void InstallSimulatorTicker(std::function<bool()> &&tick_simulator) { + request_router_->InstallSimulatorTicker(tick_simulator); + } private: struct QueryExecution { diff --git a/src/query/v2/request_router.hpp b/src/query/v2/request_router.hpp index 252278bb8..7ccb0a22b 100644 --- a/src/query/v2/request_router.hpp +++ b/src/query/v2/request_router.hpp @@ -12,7 +12,6 @@ #pragma once #include <algorithm> -#include <boost/uuid/uuid.hpp> #include <chrono> #include <deque> #include <iostream> @@ -28,6 +27,7 @@ #include <variant> #include <vector> +#include "boost/uuid/uuid.hpp" #include "coordinator/coordinator.hpp" #include "coordinator/coordinator_client.hpp" #include "coordinator/coordinator_rsm.hpp" diff --git a/tests/simulation/simulation_interpreter.hpp b/tests/simulation/simulation_interpreter.hpp index 497ee7103..e83980787 100644 --- a/tests/simulation/simulation_interpreter.hpp +++ b/tests/simulation/simulation_interpreter.hpp @@ -43,9 +43,7 @@ class SimulatedInterpreter { ~SimulatedInterpreter() = default; void InstallSimulatorTicker(Simulator &simulator) { - std::function<bool()> tick_simulator = simulator.GetSimulatorTickClosure(); - auto *request_router = interpreter_->GetRequestRouter(); - request_router->InstallSimulatorTicker(tick_simulator); + interpreter_->InstallSimulatorTicker(simulator.GetSimulatorTickClosure()); } std::vector<ResultStream> RunQueries(const std::vector<std::string> &queries) { @@ -65,14 +63,8 @@ class SimulatedInterpreter { std::map<std::string, memgraph::storage::v3::PropertyValue> params; const std::string *username = nullptr; - interpreter_->BeginTransaction(); - - auto *rr = interpreter_->GetRequestRouter(); - rr->StartTransaction(); - interpreter_->Prepare(query, params, username); interpreter_->PullAll(&stream); - interpreter_->CommitTransaction(); return stream; } From e24a6a86e44d328f813ec22e1429bfc09fb7044a Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Wed, 25 Jan 2023 12:42:44 +0100 Subject: [PATCH 84/85] Apply changes from code-review --- src/query/v2/interpreter.hpp | 2 -- src/query/v2/request_router.hpp | 17 +++++------------ 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/query/v2/interpreter.hpp b/src/query/v2/interpreter.hpp index 4bffba2fa..4efc85c22 100644 --- a/src/query/v2/interpreter.hpp +++ b/src/query/v2/interpreter.hpp @@ -16,8 +16,6 @@ #include "coordinator/coordinator.hpp" #include "coordinator/coordinator_client.hpp" -#include "io/local_transport/local_transport.hpp" -#include "io/simulator/simulator_transport.hpp" #include "io/transport.hpp" #include "query/v2/auth_checker.hpp" #include "query/v2/bindings/cypher_main_visitor.hpp" diff --git a/src/query/v2/request_router.hpp b/src/query/v2/request_router.hpp index 7ccb0a22b..2604fb13e 100644 --- a/src/query/v2/request_router.hpp +++ b/src/query/v2/request_router.hpp @@ -27,7 +27,8 @@ #include <variant> #include <vector> -#include "boost/uuid/uuid.hpp" +#include <boost/uuid/uuid.hpp> + #include "coordinator/coordinator.hpp" #include "coordinator/coordinator_client.hpp" #include "coordinator/coordinator_rsm.hpp" @@ -744,15 +745,6 @@ class RequestRouter : public RequestRouterInterface { }; class RequestRouterFactory { - protected: - using LocalTransport = io::Io<io::local_transport::LocalTransport>; - using SimulatorTransport = io::Io<io::simulator::SimulatorTransport>; - - using LocalTransportHandlePtr = std::shared_ptr<io::local_transport::LocalTransportHandle>; - using SimulatorTransportHandlePtr = std::shared_ptr<io::simulator::SimulatorHandle>; - - using TransportHandleVariant = std::variant<LocalTransportHandlePtr, SimulatorTransportHandlePtr>; - public: RequestRouterFactory() = default; RequestRouterFactory(const RequestRouterFactory &) = delete; @@ -767,10 +759,11 @@ class RequestRouterFactory { }; class LocalRequestRouterFactory : public RequestRouterFactory { - io::Io<memgraph::io::local_transport::LocalTransport> &io_; + using LocalTransportIo = io::Io<io::local_transport::LocalTransport>; + LocalTransportIo &io_; public: - explicit LocalRequestRouterFactory(io::Io<memgraph::io::local_transport::LocalTransport> &io) : io_(io) {} + explicit LocalRequestRouterFactory(LocalTransportIo &io) : io_(io) {} std::unique_ptr<RequestRouterInterface> CreateRequestRouter( const coordinator::Address &coordinator_address) const override { From 23297c2afb0c9a9787f03b90493b96d789dffc35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= <benjamin.antal@memgraph.io> Date: Thu, 26 Jan 2023 19:47:56 +0100 Subject: [PATCH 85/85] Remove unnecessary function --- src/query/v2/plan/operator.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index ee166e3cc..50e5d4fca 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -655,10 +655,6 @@ class DistributedScanByPrimaryKeyCursor : public Cursor { return false; } - void PullMultiple(MultiFrame & /*input_multi_frame*/, ExecutionContext & /*context*/) override { - throw utils::NotYetImplemented("Multiframe version of ScanByPrimaryKey is yet to be implemented."); - }; - void Reset() override { input_cursor_->Reset(); } void Shutdown() override { input_cursor_->Shutdown(); }