diff --git a/src/query/plan/operator.cpp b/src/query/plan/operator.cpp index 4700ed82d..f3ce67ca7 100644 --- a/src/query/plan/operator.cpp +++ b/src/query/plan/operator.cpp @@ -172,7 +172,10 @@ inline void AbortCheck(ExecutionContext const &context) { } // namespace +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define SCOPED_PROFILE_OP(name) ScopedProfile profile{ComputeProfilingKey(this), name, &context}; +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define SCOPED_PROFILE_OP_BY_REF(ref) ScopedProfile profile{ComputeProfilingKey(this), ref, &context}; bool Once::OnceCursor::Pull(Frame &, ExecutionContext &context) { SCOPED_PROFILE_OP("Once"); @@ -352,7 +355,7 @@ EdgeAccessor CreateEdge(const EdgeCreationInfo &edge_info, DbAccessor *dba, Vert } // namespace bool CreateExpand::CreateExpandCursor::Pull(Frame &frame, ExecutionContext &context) { - SCOPED_PROFILE_OP("CreateExpand"); + SCOPED_PROFILE_OP_BY_REF(self_); if (!input_cursor_->Pull(frame, context)) return false; @@ -431,16 +434,17 @@ VertexAccessor &CreateExpand::CreateExpandCursor::OtherVertex(Frame &frame, Exec template class ScanAllCursor : public Cursor { public: - explicit ScanAllCursor(Symbol output_symbol, UniqueCursorPtr input_cursor, storage::View view, + explicit ScanAllCursor(const ScanAll &self, Symbol output_symbol, UniqueCursorPtr input_cursor, storage::View view, TVerticesFun get_vertices, const char *op_name) - : output_symbol_(output_symbol), + : self_(self), + output_symbol_(output_symbol), input_cursor_(std::move(input_cursor)), view_(view), get_vertices_(std::move(get_vertices)), op_name_(op_name) {} bool Pull(Frame &frame, ExecutionContext &context) override { - SCOPED_PROFILE_OP(op_name_); + SCOPED_PROFILE_OP_BY_REF(self_); AbortCheck(context); @@ -491,6 +495,7 @@ class ScanAllCursor : public Cursor { } private: + const ScanAll &self_; const Symbol output_symbol_; const UniqueCursorPtr input_cursor_; storage::View view_; @@ -513,8 +518,8 @@ UniqueCursorPtr ScanAll::MakeCursor(utils::MemoryResource *mem) const { auto *db = context.db_accessor; return std::make_optional(db->Vertices(view_)); }; - return MakeUniqueCursorPtr>(mem, output_symbol_, input_->MakeCursor(mem), view_, - std::move(vertices), "ScanAll"); + return MakeUniqueCursorPtr>(mem, *this, output_symbol_, input_->MakeCursor(mem), + view_, std::move(vertices), "ScanAll"); } std::vector ScanAll::ModifiedSymbols(const SymbolTable &table) const { @@ -536,8 +541,8 @@ UniqueCursorPtr ScanAllByLabel::MakeCursor(utils::MemoryResource *mem) const { auto *db = context.db_accessor; return std::make_optional(db->Vertices(view_, label_)); }; - return MakeUniqueCursorPtr>(mem, output_symbol_, input_->MakeCursor(mem), view_, - std::move(vertices), "ScanAllByLabel"); + return MakeUniqueCursorPtr>(mem, *this, output_symbol_, input_->MakeCursor(mem), + view_, std::move(vertices), "ScanAllByLabel"); } // TODO(buda): Implement ScanAllByLabelProperty operator to iterate over @@ -601,8 +606,8 @@ UniqueCursorPtr ScanAllByLabelPropertyRange::MakeCursor(utils::MemoryResource *m if (maybe_upper && maybe_upper->value().IsNull()) return std::nullopt; return std::make_optional(db->Vertices(view_, label_, property_, maybe_lower, maybe_upper)); }; - return MakeUniqueCursorPtr>(mem, output_symbol_, input_->MakeCursor(mem), view_, - std::move(vertices), "ScanAllByLabelPropertyRange"); + return MakeUniqueCursorPtr>( + mem, *this, output_symbol_, input_->MakeCursor(mem), view_, std::move(vertices), "ScanAllByLabelPropertyRange"); } ScanAllByLabelPropertyValue::ScanAllByLabelPropertyValue(const std::shared_ptr &input, @@ -633,8 +638,8 @@ UniqueCursorPtr ScanAllByLabelPropertyValue::MakeCursor(utils::MemoryResource *m } return std::make_optional(db->Vertices(view_, label_, property_, storage::PropertyValue(value))); }; - return MakeUniqueCursorPtr>(mem, output_symbol_, input_->MakeCursor(mem), view_, - std::move(vertices), "ScanAllByLabelPropertyValue"); + return MakeUniqueCursorPtr>( + mem, *this, output_symbol_, input_->MakeCursor(mem), view_, std::move(vertices), "ScanAllByLabelPropertyValue"); } ScanAllByLabelProperty::ScanAllByLabelProperty(const std::shared_ptr &input, Symbol output_symbol, @@ -651,8 +656,8 @@ UniqueCursorPtr ScanAllByLabelProperty::MakeCursor(utils::MemoryResource *mem) c auto *db = context.db_accessor; return std::make_optional(db->Vertices(view_, label_, property_)); }; - return MakeUniqueCursorPtr>(mem, output_symbol_, input_->MakeCursor(mem), view_, - std::move(vertices), "ScanAllByLabelProperty"); + return MakeUniqueCursorPtr>(mem, *this, output_symbol_, input_->MakeCursor(mem), + view_, std::move(vertices), "ScanAllByLabelProperty"); } ScanAllById::ScanAllById(const std::shared_ptr &input, Symbol output_symbol, Expression *expression, @@ -677,8 +682,8 @@ UniqueCursorPtr ScanAllById::MakeCursor(utils::MemoryResource *mem) const { if (!maybe_vertex) return std::nullopt; return std::vector{*maybe_vertex}; }; - return MakeUniqueCursorPtr>(mem, output_symbol_, input_->MakeCursor(mem), view_, - std::move(vertices), "ScanAllById"); + return MakeUniqueCursorPtr>(mem, *this, output_symbol_, input_->MakeCursor(mem), + view_, std::move(vertices), "ScanAllById"); } namespace { @@ -742,7 +747,7 @@ Expand::ExpandCursor::ExpandCursor(const Expand &self, int64_t input_degree, int prev_existing_degree_(existing_node_degree) {} bool Expand::ExpandCursor::Pull(Frame &frame, ExecutionContext &context) { - SCOPED_PROFILE_OP("Expand"); + SCOPED_PROFILE_OP_BY_REF(self_); // A helper function for expanding a node from an edge. auto pull_node = [this, &frame](const EdgeAccessor &new_edge, EdgeAtom::Direction direction) { @@ -1031,7 +1036,7 @@ class ExpandVariableCursor : public Cursor { : self_(self), input_cursor_(self.input_->MakeCursor(mem)), edges_(mem), edges_it_(mem) {} bool Pull(Frame &frame, ExecutionContext &context) override { - SCOPED_PROFILE_OP("ExpandVariable"); + SCOPED_PROFILE_OP_BY_REF(self_); ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor, storage::View::OLD); @@ -2522,7 +2527,7 @@ Produce::ProduceCursor::ProduceCursor(const Produce &self, utils::MemoryResource : self_(self), input_cursor_(self_.input_->MakeCursor(mem)) {} bool Produce::ProduceCursor::Pull(Frame &frame, ExecutionContext &context) { - SCOPED_PROFILE_OP("Produce"); + SCOPED_PROFILE_OP_BY_REF(self_); if (input_cursor_->Pull(frame, context)) { // Produce should always yield the latest results. @@ -3403,7 +3408,7 @@ class AggregateCursor : public Cursor { : self_(self), input_cursor_(self_.input_->MakeCursor(mem)), aggregation_(mem) {} bool Pull(Frame &frame, ExecutionContext &context) override { - SCOPED_PROFILE_OP("Aggregate"); + SCOPED_PROFILE_OP_BY_REF(self_); if (!pulled_all_input_) { ProcessAll(&frame, &context); @@ -3867,7 +3872,7 @@ class OrderByCursor : public Cursor { : self_(self), input_cursor_(self_.input_->MakeCursor(mem)), cache_(mem) {} bool Pull(Frame &frame, ExecutionContext &context) override { - SCOPED_PROFILE_OP("OrderBy"); + SCOPED_PROFILE_OP_BY_REF(self_); if (!did_pull_all_) { ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor, @@ -4269,7 +4274,7 @@ Union::UnionCursor::UnionCursor(const Union &self, utils::MemoryResource *mem) : self_(self), left_cursor_(self.left_op_->MakeCursor(mem)), right_cursor_(self.right_op_->MakeCursor(mem)) {} bool Union::UnionCursor::Pull(Frame &frame, ExecutionContext &context) { - SCOPED_PROFILE_OP("Union"); + SCOPED_PROFILE_OP_BY_REF(self_); utils::pmr::unordered_map results(context.evaluation_context.memory); if (left_cursor_->Pull(frame, context)) { @@ -4343,7 +4348,7 @@ class CartesianCursor : public Cursor { } bool Pull(Frame &frame, ExecutionContext &context) override { - SCOPED_PROFILE_OP("Cartesian"); + SCOPED_PROFILE_OP_BY_REF(self_); if (!cartesian_pull_initialized_) { // Pull all left_op frames. @@ -4632,7 +4637,7 @@ class CallProcedureCursor : public Cursor { } bool Pull(Frame &frame, ExecutionContext &context) override { - SCOPED_PROFILE_OP("CallProcedure"); + SCOPED_PROFILE_OP_BY_REF(*self_); AbortCheck(context); @@ -4911,7 +4916,7 @@ class LoadCsvCursor : public Cursor { : self_(self), input_cursor_(self_->input_->MakeCursor(mem)), did_pull_{false} {} bool Pull(Frame &frame, ExecutionContext &context) override { - SCOPED_PROFILE_OP("LoadCsv"); + SCOPED_PROFILE_OP_BY_REF(*self_); AbortCheck(context); diff --git a/src/query/plan/operator.hpp b/src/query/plan/operator.hpp index e38d6ddeb..c3cf80042 100644 --- a/src/query/plan/operator.hpp +++ b/src/query/plan/operator.hpp @@ -152,12 +152,19 @@ class HierarchicalLogicalOperatorVisitor : public LogicalOperatorCompositeVisito using typename LogicalOperatorLeafVisitor::ReturnType; }; +class NamedLogicalOperator { + public: + mutable const DbAccessor *dba_{nullptr}; + virtual std::string ToString() const = 0; +}; + /// Base class for logical operators. /// /// Each operator describes an operation, which is to be performed on the /// database. Operators are iterated over using a @c Cursor. Various operators /// can serve as inputs to others and thus a sequence of operations is formed. -class LogicalOperator : public utils::Visitable { +class LogicalOperator : public utils::Visitable, + public memgraph::query::plan::NamedLogicalOperator { public: static const utils::TypeInfo kType; virtual const utils::TypeInfo &GetTypeInfo() const { return kType; } @@ -232,6 +239,8 @@ class LogicalOperator : public utils::Visitable> loaded_ops; }; + std::string ToString() const override { return GetTypeInfo().name; } + virtual std::unique_ptr Clone(AstStorage *storage) const = 0; }; @@ -464,6 +473,13 @@ class CreateExpand : public memgraph::query::plan::LogicalOperator { /// if the given node atom refers to an existing node (either matched or created) bool existing_node_; + std::string ToString() const override { + return fmt::format("CreateExpand ({}){}[{}:{}]{}({})", input_symbol_.name(), + edge_info_.direction == query::EdgeAtom::Direction::IN ? "<-" : "-", edge_info_.symbol.name(), + dba_->EdgeTypeToName(edge_info_.edge_type), + edge_info_.direction == query::EdgeAtom::Direction::OUT ? "->" : "-", node_info_.symbol.name()); + } + std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->node_info_ = node_info_.Clone(storage); @@ -530,6 +546,8 @@ class ScanAll : public memgraph::query::plan::LogicalOperator { /// transaction sees along with their modifications. storage::View view_; + std::string ToString() const override { return fmt::format("ScanAll ({})", output_symbol_.name()); } + std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; @@ -558,6 +576,10 @@ class ScanAllByLabel : public memgraph::query::plan::ScanAll { storage::LabelId label_; + std::string ToString() const override { + return fmt::format("ScanAllByLabel ({} :{})", output_symbol_.name(), dba_->LabelToName(label_)); + } + std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; @@ -610,6 +632,11 @@ class ScanAllByLabelPropertyRange : public memgraph::query::plan::ScanAll { std::optional lower_bound_; std::optional upper_bound_; + std::string ToString() const override { + return fmt::format("ScanAllByLabelPropertyRange ({0} :{1} {{{2}}})", output_symbol_.name(), + dba_->LabelToName(label_), dba_->PropertyToName(property_)); + } + std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; @@ -668,6 +695,11 @@ class ScanAllByLabelPropertyValue : public memgraph::query::plan::ScanAll { std::string property_name_; Expression *expression_; + std::string ToString() const override { + return fmt::format("ScanAllByLabelPropertyValue ({0} :{1} {{{2}}})", output_symbol_.name(), + dba_->LabelToName(label_), dba_->PropertyToName(property_)); + } + std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; @@ -704,6 +736,11 @@ class ScanAllByLabelProperty : public memgraph::query::plan::ScanAll { std::string property_name_; Expression *expression_; + std::string ToString() const override { + return fmt::format("ScanAllByLabelProperty ({0} :{1} {{{2}}})", output_symbol_.name(), dba_->LabelToName(label_), + dba_->PropertyToName(property_)); + } + std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; @@ -732,6 +769,8 @@ class ScanAllById : public memgraph::query::plan::ScanAll { Expression *expression_; + std::string ToString() const override { return fmt::format("ScanAllById ({})", output_symbol_.name()); } + std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; @@ -848,6 +887,15 @@ class Expand : public memgraph::query::plan::LogicalOperator { /// State from which the input node should get expanded. storage::View view_; + std::string ToString() const override { + return fmt::format( + "Expand ({}){}[{}{}]{}({})", input_symbol_.name(), + common_.direction == query::EdgeAtom::Direction::IN ? "<-" : "-", common_.edge_symbol.name(), + utils::IterableToString(common_.edge_types, "|", + [this](const auto &edge_type) { return ":" + dba_->EdgeTypeToName(edge_type); }), + common_.direction == query::EdgeAtom::Direction::OUT ? "->" : "-", common_.node_symbol.name()); + } + std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; @@ -955,6 +1003,37 @@ class ExpandVariable : public memgraph::query::plan::LogicalOperator { std::optional weight_lambda_; std::optional total_weight_; + std::string OperatorName() const { + using Type = query::EdgeAtom::Type; + switch (type_) { + case Type::DEPTH_FIRST: + return "ExpandVariable"; + break; + case Type::BREADTH_FIRST: + return (common_.existing_node ? "STShortestPath" : "BFSExpand"); + break; + case Type::WEIGHTED_SHORTEST_PATH: + return "WeightedShortestPath"; + break; + case Type::ALL_SHORTEST_PATHS: + return "AllShortestPaths"; + break; + case Type::SINGLE: + LOG_FATAL("Unexpected ExpandVariable::type_"); + default: + LOG_FATAL("Unexpected ExpandVariable::type_"); + } + } + + std::string ToString() const override { + return fmt::format( + "{} ({}){}[{}{}]{}({})", OperatorName(), input_symbol_.name(), + common_.direction == query::EdgeAtom::Direction::IN ? "<-" : "-", common_.edge_symbol.name(), + utils::IterableToString(common_.edge_types, "|", + [this](const auto &edge_type) { return ":" + dba_->EdgeTypeToName(edge_type); }), + common_.direction == query::EdgeAtom::Direction::OUT ? "->" : "-", common_.node_symbol.name()); + } + std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; @@ -1097,6 +1176,11 @@ class Produce : public memgraph::query::plan::LogicalOperator { std::shared_ptr input_; std::vector named_expressions_; + std::string ToString() const override { + return fmt::format("Produce {{{}}}", utils::IterableToString(named_expressions_, ", ", + [](const auto &nexpr) { return nexpr->name_; })); + } + std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; @@ -1628,6 +1712,13 @@ class Aggregate : public memgraph::query::plan::LogicalOperator { std::vector group_by_; std::vector remember_; + std::string ToString() const override { + return fmt::format( + "Aggregate {{{0}}} {{{1}}}", + utils::IterableToString(aggregations_, ", ", [](const auto &aggr) { return aggr.output_sym.name(); }), + utils::IterableToString(remember_, ", ", [](const auto &sym) { return sym.name(); })); + } + std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; @@ -1833,6 +1924,11 @@ class OrderBy : public memgraph::query::plan::LogicalOperator { std::vector order_by_; std::vector output_symbols_; + std::string ToString() const override { + return fmt::format("OrderBy {{{}}}", + utils::IterableToString(output_symbols_, ", ", [](const auto &sym) { return sym.name(); })); + } + std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; @@ -2064,6 +2160,12 @@ class Union : public memgraph::query::plan::LogicalOperator { std::vector left_symbols_; std::vector right_symbols_; + std::string ToString() const override { + return fmt::format("Union {{{0} : {1}}}", + utils::IterableToString(left_symbols_, ", ", [](const auto &sym) { return sym.name(); }), + utils::IterableToString(right_symbols_, ", ", [](const auto &sym) { return sym.name(); })); + } + std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->left_op_ = left_op_ ? left_op_->Clone(storage) : nullptr; @@ -2226,6 +2328,11 @@ class CallProcedure : public memgraph::query::plan::LogicalOperator { mutable utils::MonotonicBufferResource monotonic_memory{1024UL * 1024UL}; utils::MemoryResource *memory_resource = &monotonic_memory; + std::string ToString() const override { + return fmt::format("CallProcedure<{0}> {{{1}}}", procedure_name_, + utils::IterableToString(result_symbols_, ", ", [](const auto &sym) { return sym.name(); })); + } + std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; @@ -2273,6 +2380,8 @@ class LoadCsv : public memgraph::query::plan::LogicalOperator { Expression *nullif_{nullptr}; Symbol row_var_; + std::string ToString() const override { return fmt::format("LoadCsv {{{}}}", row_var_.name()); } + std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; diff --git a/src/query/plan/pretty_print.cpp b/src/query/plan/pretty_print.cpp index 3c23a2e8c..47fb8dc9d 100644 --- a/src/query/plan/pretty_print.cpp +++ b/src/query/plan/pretty_print.cpp @@ -30,121 +30,68 @@ PlanPrinter::PlanPrinter(const DbAccessor *dba, std::ostream *out) : dba_(dba), PRE_VISIT(CreateNode); bool PlanPrinter::PreVisit(CreateExpand &op) { - WithPrintLn([&](auto &out) { - out << "* CreateExpand (" << op.input_symbol_.name() << ")" - << (op.edge_info_.direction == query::EdgeAtom::Direction::IN ? "<-" : "-") << "[" - << op.edge_info_.symbol.name() << ":" << dba_->EdgeTypeToName(op.edge_info_.edge_type) << "]" - << (op.edge_info_.direction == query::EdgeAtom::Direction::OUT ? "->" : "-") << "(" - << op.node_info_.symbol.name() << ")"; - }); + op.dba_ = dba_; + WithPrintLn([&](auto &out) { out << "* " << op.ToString(); }); + op.dba_ = nullptr; return true; } PRE_VISIT(Delete); bool PlanPrinter::PreVisit(query::plan::ScanAll &op) { - WithPrintLn([&](auto &out) { - out << "* ScanAll" - << " (" << op.output_symbol_.name() << ")"; - }); + WithPrintLn([&](auto &out) { out << "* " << op.ToString(); }); return true; } bool PlanPrinter::PreVisit(query::plan::ScanAllByLabel &op) { - WithPrintLn([&](auto &out) { - out << "* ScanAllByLabel" - << " (" << op.output_symbol_.name() << " :" << dba_->LabelToName(op.label_) << ")"; - }); + op.dba_ = dba_; + WithPrintLn([&](auto &out) { out << "* " << op.ToString(); }); + op.dba_ = nullptr; return true; } bool PlanPrinter::PreVisit(query::plan::ScanAllByLabelPropertyValue &op) { - WithPrintLn([&](auto &out) { - out << "* ScanAllByLabelPropertyValue" - << " (" << op.output_symbol_.name() << " :" << dba_->LabelToName(op.label_) << " {" - << dba_->PropertyToName(op.property_) << "})"; - }); + op.dba_ = dba_; + WithPrintLn([&](auto &out) { out << "* " << op.ToString(); }); + op.dba_ = nullptr; return true; } bool PlanPrinter::PreVisit(query::plan::ScanAllByLabelPropertyRange &op) { - WithPrintLn([&](auto &out) { - out << "* ScanAllByLabelPropertyRange" - << " (" << op.output_symbol_.name() << " :" << dba_->LabelToName(op.label_) << " {" - << dba_->PropertyToName(op.property_) << "})"; - }); + op.dba_ = dba_; + WithPrintLn([&](auto &out) { out << "* " << op.ToString(); }); + op.dba_ = nullptr; return true; } bool PlanPrinter::PreVisit(query::plan::ScanAllByLabelProperty &op) { - WithPrintLn([&](auto &out) { - out << "* ScanAllByLabelProperty" - << " (" << op.output_symbol_.name() << " :" << dba_->LabelToName(op.label_) << " {" - << dba_->PropertyToName(op.property_) << "})"; - }); + op.dba_ = dba_; + WithPrintLn([&](auto &out) { out << "* " << op.ToString(); }); + op.dba_ = nullptr; return true; } bool PlanPrinter::PreVisit(ScanAllById &op) { - WithPrintLn([&](auto &out) { - out << "* ScanAllById" - << " (" << op.output_symbol_.name() << ")"; - }); + WithPrintLn([&](auto &out) { out << "* " << op.ToString(); }); return true; } bool PlanPrinter::PreVisit(query::plan::Expand &op) { - WithPrintLn([&](auto &out) { - *out_ << "* Expand (" << op.input_symbol_.name() << ")" - << (op.common_.direction == query::EdgeAtom::Direction::IN ? "<-" : "-") << "[" - << op.common_.edge_symbol.name(); - utils::PrintIterable(*out_, op.common_.edge_types, "|", [this](auto &stream, const auto &edge_type) { - stream << ":" << dba_->EdgeTypeToName(edge_type); - }); - *out_ << "]" << (op.common_.direction == query::EdgeAtom::Direction::OUT ? "->" : "-") << "(" - << op.common_.node_symbol.name() << ")"; - }); + op.dba_ = dba_; + WithPrintLn([&](auto &out) { out << "* " << op.ToString(); }); + op.dba_ = nullptr; return true; } bool PlanPrinter::PreVisit(query::plan::ExpandVariable &op) { - using Type = query::EdgeAtom::Type; - WithPrintLn([&](auto &out) { - *out_ << "* "; - switch (op.type_) { - case Type::DEPTH_FIRST: - *out_ << "ExpandVariable"; - break; - case Type::BREADTH_FIRST: - *out_ << (op.common_.existing_node ? "STShortestPath" : "BFSExpand"); - break; - case Type::WEIGHTED_SHORTEST_PATH: - *out_ << "WeightedShortestPath"; - break; - case Type::ALL_SHORTEST_PATHS: - *out_ << "AllShortestPaths"; - break; - case Type::SINGLE: - LOG_FATAL("Unexpected ExpandVariable::type_"); - } - *out_ << " (" << op.input_symbol_.name() << ")" - << (op.common_.direction == query::EdgeAtom::Direction::IN ? "<-" : "-") << "[" - << op.common_.edge_symbol.name(); - utils::PrintIterable(*out_, op.common_.edge_types, "|", [this](auto &stream, const auto &edge_type) { - stream << ":" << dba_->EdgeTypeToName(edge_type); - }); - *out_ << "]" << (op.common_.direction == query::EdgeAtom::Direction::OUT ? "->" : "-") << "(" - << op.common_.node_symbol.name() << ")"; - }); + op.dba_ = dba_; + WithPrintLn([&](auto &out) { out << "* " << op.ToString(); }); + op.dba_ = nullptr; return true; } bool PlanPrinter::PreVisit(query::plan::Produce &op) { - WithPrintLn([&](auto &out) { - out << "* Produce {"; - utils::PrintIterable(out, op.named_expressions_, ", ", [](auto &out, const auto &nexpr) { out << nexpr->name_; }); - out << "}"; - }); + WithPrintLn([&](auto &out) { out << "* " << op.ToString(); }); return true; } @@ -160,14 +107,7 @@ PRE_VISIT(EmptyResult); PRE_VISIT(EvaluatePatternFilter); bool PlanPrinter::PreVisit(query::plan::Aggregate &op) { - WithPrintLn([&](auto &out) { - out << "* Aggregate {"; - utils::PrintIterable(out, op.aggregations_, ", ", - [](auto &out, const auto &aggr) { out << aggr.output_sym.name(); }); - out << "} {"; - utils::PrintIterable(out, op.remember_, ", ", [](auto &out, const auto &sym) { out << sym.name(); }); - out << "}"; - }); + WithPrintLn([&](auto &out) { out << "* " << op.ToString(); }); return true; } @@ -175,11 +115,7 @@ PRE_VISIT(Skip); PRE_VISIT(Limit); bool PlanPrinter::PreVisit(query::plan::OrderBy &op) { - WithPrintLn([&op](auto &out) { - out << "* OrderBy {"; - utils::PrintIterable(out, op.output_symbols_, ", ", [](auto &out, const auto &sym) { out << sym.name(); }); - out << "}"; - }); + WithPrintLn([&](auto &out) { out << "* " << op.ToString(); }); return true; } @@ -202,29 +138,19 @@ PRE_VISIT(Unwind); PRE_VISIT(Distinct); bool PlanPrinter::PreVisit(query::plan::Union &op) { - WithPrintLn([&op](auto &out) { - out << "* Union {"; - utils::PrintIterable(out, op.left_symbols_, ", ", [](auto &out, const auto &sym) { out << sym.name(); }); - out << " : "; - utils::PrintIterable(out, op.right_symbols_, ", ", [](auto &out, const auto &sym) { out << sym.name(); }); - out << "}"; - }); + WithPrintLn([&](auto &out) { out << "* " << op.ToString(); }); Branch(*op.right_op_); op.left_op_->Accept(*this); return false; } bool PlanPrinter::PreVisit(query::plan::CallProcedure &op) { - WithPrintLn([&op](auto &out) { - out << "* CallProcedure<" << op.procedure_name_ << "> {"; - utils::PrintIterable(out, op.result_symbols_, ", ", [](auto &out, const auto &sym) { out << sym.name(); }); - out << "}"; - }); + WithPrintLn([&](auto &out) { out << "* " << op.ToString(); }); return true; } bool PlanPrinter::PreVisit(query::plan::LoadCsv &op) { - WithPrintLn([&op](auto &out) { out << "* LoadCsv {" << op.row_var_.name() << "}"; }); + WithPrintLn([&](auto &out) { out << "* " << op.ToString(); }); return true; } diff --git a/src/query/plan/profile.cpp b/src/query/plan/profile.cpp index 1800f661d..1be54277e 100644 --- a/src/query/plan/profile.cpp +++ b/src/query/plan/profile.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 @@ -57,7 +57,7 @@ class ProfilingStatsToTableHelper { auto cycles = IndividualCycles(cumulative_stats); rows_.emplace_back(std::vector{ - TypedValue(FormatOperator(cumulative_stats.name)), TypedValue(cumulative_stats.actual_hits), + TypedValue(FormatOperator(cumulative_stats.name.c_str())), TypedValue(cumulative_stats.actual_hits), TypedValue(FormatRelativeTime(cycles)), TypedValue(FormatAbsoluteTime(cycles))}); for (size_t i = 1; i < cumulative_stats.children.size(); ++i) { @@ -137,7 +137,7 @@ class ProfilingStatsToJsonHelper { void Output(const ProfilingStats &cumulative_stats, json *obj) { auto cycles = IndividualCycles(cumulative_stats); - obj->emplace("name", cumulative_stats.name); + obj->emplace("name", cumulative_stats.name.c_str()); obj->emplace("actual_hits", cumulative_stats.actual_hits); obj->emplace("relative_time", RelativeTime(cycles, total_cycles_)); obj->emplace("absolute_time", AbsoluteTime(cycles, total_cycles_, total_time_)); diff --git a/src/query/plan/profile.hpp b/src/query/plan/profile.hpp index b314fd0ef..041a34ac9 100644 --- a/src/query/plan/profile.hpp +++ b/src/query/plan/profile.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,7 +29,7 @@ struct ProfilingStats { int64_t actual_hits{0}; unsigned long long num_cycles{0}; uint64_t key{0}; - const char *name{nullptr}; + std::string name; // TODO: This should use the allocator for query execution std::vector children; }; diff --git a/src/query/plan/scoped_profile.hpp b/src/query/plan/scoped_profile.hpp index e825384d1..ed29795f6 100644 --- a/src/query/plan/scoped_profile.hpp +++ b/src/query/plan/scoped_profile.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 @@ -28,6 +28,43 @@ namespace memgraph::query::plan { */ class ScopedProfile { public: + ScopedProfile(uint64_t key, const query::plan::NamedLogicalOperator &op, query::ExecutionContext *context) noexcept + : context_(context) { + if (UNLIKELY(context_->is_profile_query)) { + root_ = context_->stats_root; + + // Are we the root logical operator? + if (!root_) { + stats_ = &context_->stats; + stats_->key = key; + op.dba_ = context->db_accessor; + stats_->name = op.ToString(); + op.dba_ = nullptr; + } else { + stats_ = nullptr; + + // Was this logical operator already hit on one of the previous pulls? + auto it = std::find_if(root_->children.begin(), root_->children.end(), + [key](auto &stats) { return stats.key == key; }); + + if (it == root_->children.end()) { + root_->children.emplace_back(); + stats_ = &root_->children.back(); + stats_->key = key; + op.dba_ = context->db_accessor; + stats_->name = op.ToString(); + op.dba_ = nullptr; + } else { + stats_ = &(*it); + } + } + + context_->stats_root = stats_; + stats_->actual_hits++; + start_time_ = utils::ReadTSC(); + } + } + ScopedProfile(uint64_t key, const char *name, query::ExecutionContext *context) noexcept : context_(context) { if (UNLIKELY(context_->is_profile_query)) { root_ = context_->stats_root; diff --git a/src/utils/algorithm.hpp b/src/utils/algorithm.hpp index b1b7dbc51..cac7ccc9b 100644 --- a/src/utils/algorithm.hpp +++ b/src/utils/algorithm.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,55 @@ namespace memgraph::utils { +/** + * Outputs a collection of items as a string, separating them with the given delimiter. + * + * @param first Starting iterator of collection which items are going to be + * printed. + * @param last Ending iterator of the collection. + * @param delim Delimiter that is put between items. + * @param transformation Function which accepts an item and returns a derived value. + */ +template +inline std::string IterableToString(TIterator first, TIterator last, const std::string_view delim = ", ", + TTransformation transformation = {}) { + std::string representation; + if (first != last) { + representation.append(transformation(*first)); + ++first; + } + for (; first != last; ++first) { + representation.append(delim); + representation.append(transformation(*first)); + } + + return representation; +} + +/** + * Outputs a collection of items as a string, separating them with the given delimiter. + * + * @param iterable An iterable collection of items. + * @param delim Delimiter that is put between items. + * @param transformation Function which accepts an item and returns a derived value. + */ +template +inline std::string IterableToString(const TIterable &iterable, const std::string_view delim = ", ", + TTransformation transformation = {}) { + return IterableToString(iterable.begin(), iterable.end(), delim, transformation); +} + +/** + * Outputs a collection of items as a string, separating them with the given delimiter. + * + * @param iterable An iterable collection of items. + * @param delim Delimiter that is put between items. + */ +template +inline std::string IterableToString(const TIterable &iterable, const std::string_view delim = ", ") { + return IterableToString(iterable, delim, [](const auto &item) { return item; }); +} + /** * Outputs a collection of items to the given stream, separating them with the * given delimiter. diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index b08e3a7e4..75a090fa6 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -111,6 +111,9 @@ target_link_libraries(${test_prefix}query_plan_edge_cases mg-communication mg-qu add_unit_test(query_plan_match_filter_return.cpp) target_link_libraries(${test_prefix}query_plan_match_filter_return mg-query mg-query mg-glue) +add_unit_test(query_plan_operator_to_string.cpp) +target_link_libraries(${test_prefix}query_plan_operator_to_string mg-query) + add_unit_test(query_plan_read_write_typecheck.cpp ${CMAKE_SOURCE_DIR}/src/query/plan/read_write_type_checker.cpp) target_link_libraries(${test_prefix}query_plan_read_write_typecheck mg-query) diff --git a/tests/unit/interpreter.cpp b/tests/unit/interpreter.cpp index 4e94b987c..8db925609 100644 --- a/tests/unit/interpreter.cpp +++ b/tests/unit/interpreter.cpp @@ -740,7 +740,7 @@ TYPED_TEST(InterpreterTest, ProfileQuery) { auto stream = this->Interpret("PROFILE MATCH (n) RETURN *;"); std::vector expected_header{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"}; EXPECT_EQ(stream.GetHeader(), expected_header); - std::vector expected_rows{"* Produce", "* ScanAll", "* Once"}; + std::vector expected_rows{"* Produce {n}", "* ScanAll (n)", "* Once"}; ASSERT_EQ(stream.GetResults().size(), expected_rows.size()); auto expected_it = expected_rows.begin(); for (const auto &row : stream.GetResults()) { @@ -764,7 +764,7 @@ TYPED_TEST(InterpreterTest, ProfileQueryMultiplePulls) { std::vector expected_header{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"}; EXPECT_EQ(stream.GetHeader(), expected_header); - std::vector expected_rows{"* Produce", "* ScanAll", "* Once"}; + std::vector expected_rows{"* Produce {n}", "* ScanAll (n)", "* Once"}; auto expected_it = expected_rows.begin(); this->Pull(&stream, 1); @@ -806,7 +806,7 @@ TYPED_TEST(InterpreterTest, ProfileQueryWithParams) { this->Interpret("PROFILE MATCH (n) WHERE n.id = $id RETURN *;", {{"id", memgraph::storage::PropertyValue(42)}}); std::vector expected_header{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"}; EXPECT_EQ(stream.GetHeader(), expected_header); - std::vector expected_rows{"* Produce", "* Filter", "* ScanAll", "* Once"}; + std::vector expected_rows{"* Produce {n}", "* Filter", "* ScanAll (n)", "* Once"}; ASSERT_EQ(stream.GetResults().size(), expected_rows.size()); auto expected_it = expected_rows.begin(); for (const auto &row : stream.GetResults()) { diff --git a/tests/unit/query_plan_operator_to_string.cpp b/tests/unit/query_plan_operator_to_string.cpp new file mode 100644 index 000000000..cc442f535 --- /dev/null +++ b/tests/unit/query_plan_operator_to_string.cpp @@ -0,0 +1,497 @@ +// 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 +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include + +#include "disk_test_utils.hpp" +#include "query/frontend/semantic/symbol_table.hpp" +#include "query/plan/operator.hpp" +#include "query/plan/pretty_print.hpp" + +#include "query_common.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" + +using namespace memgraph::query; +using namespace memgraph::query::plan; + +// The JSON formatted plan is consumed (or will be) by Memgraph Lab, and +// therefore should not be changed before synchronizing with whoever is +// maintaining Memgraph Lab. Hopefully, one day integration tests will exist and +// there will be no need to be super careful. + +template +class OperatorToStringTest : public ::testing::Test { + protected: + const std::string testSuite = "plan_operator_to_string"; + + OperatorToStringTest() + : config(disk_test_utils::GenerateOnDiskConfig(testSuite)), + db(new StorageType(config)), + dba_storage(db->Access()), + dba(dba_storage.get()) {} + + ~OperatorToStringTest() { + if (std::is_same::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + } + + AstStorage storage; + SymbolTable symbol_table; + + memgraph::storage::Config config; + std::unique_ptr db; + std::unique_ptr dba_storage; + memgraph::query::DbAccessor dba; + + Symbol GetSymbol(std::string name) { return symbol_table.CreateSymbol(name, true); } +}; + +using StorageTypes = ::testing::Types; +TYPED_TEST_CASE(OperatorToStringTest, StorageTypes); + +TYPED_TEST(OperatorToStringTest, Once) { + std::shared_ptr last_op; + last_op = std::make_shared(); + + std::string expected_string{"Once"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, CreateNode) { + std::shared_ptr last_op; + last_op = std::make_shared( + nullptr, NodeCreationInfo{this->GetSymbol("node"), + {this->dba.NameToLabel("Label1"), this->dba.NameToLabel("Label2")}, + {{this->dba.NameToProperty("prop1"), LITERAL(5)}, + {this->dba.NameToProperty("prop2"), LITERAL("some cool stuff")}}}); + + std::string expected_string{"CreateNode"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, CreateExpand) { + Symbol node1_sym = this->GetSymbol("node1"); + std::shared_ptr last_op = std::make_shared(nullptr, this->GetSymbol("node1")); + last_op = std::make_shared( + NodeCreationInfo{this->GetSymbol("node2"), + {this->dba.NameToLabel("Label1"), this->dba.NameToLabel("Label2")}, + {{this->dba.NameToProperty("prop1"), LITERAL(5)}, + {this->dba.NameToProperty("prop2"), LITERAL("some cool stuff")}}}, + EdgeCreationInfo{this->GetSymbol("edge"), + {{this->dba.NameToProperty("weight"), LITERAL(5.32)}}, + this->dba.NameToEdgeType("edge_type"), + EdgeAtom::Direction::OUT}, + last_op, node1_sym, false); + last_op->dba_ = &this->dba; + + std::string expected_string{"CreateExpand (node1)-[edge:edge_type]->(node2)"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, ScanAll) { + std::shared_ptr last_op; + last_op = std::make_shared(nullptr, this->GetSymbol("node")); + + std::string expected_string{"ScanAll (node)"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, ScanAllByLabel) { + std::shared_ptr last_op; + last_op = std::make_shared(nullptr, this->GetSymbol("node"), this->dba.NameToLabel("Label")); + last_op->dba_ = &this->dba; + + std::string expected_string{"ScanAllByLabel (node :Label)"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, ScanAllByLabelPropertyRange) { + std::shared_ptr last_op; + last_op = std::make_shared( + nullptr, this->GetSymbol("node"), this->dba.NameToLabel("Label"), this->dba.NameToProperty("prop"), "prop", + memgraph::utils::MakeBoundInclusive(LITERAL(1)), + memgraph::utils::MakeBoundExclusive(LITERAL(20))); + last_op->dba_ = &this->dba; + + std::string expected_string{"ScanAllByLabelPropertyRange (node :Label {prop})"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, ScanAllByLabelPropertyValue) { + std::shared_ptr last_op; + last_op = std::make_shared( + nullptr, this->GetSymbol("node"), this->dba.NameToLabel("Label"), this->dba.NameToProperty("prop"), "prop", + ADD(LITERAL(21), LITERAL(21))); + last_op->dba_ = &this->dba; + + std::string expected_string{"ScanAllByLabelPropertyValue (node :Label {prop})"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, ScanAllByLabelProperty) { + std::shared_ptr last_op; + last_op = std::make_shared(nullptr, this->GetSymbol("node"), this->dba.NameToLabel("Label"), + this->dba.NameToProperty("prop"), "prop"); + last_op->dba_ = &this->dba; + + std::string expected_string{"ScanAllByLabelProperty (node :Label {prop})"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, ScanAllById) { + std::shared_ptr last_op; + last_op = std::make_shared(nullptr, this->GetSymbol("node"), ADD(LITERAL(21), LITERAL(21))); + last_op->dba_ = &this->dba; + + std::string expected_string{"ScanAllById (node)"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, Expand) { + auto node1_sym = this->GetSymbol("node1"); + std::shared_ptr last_op = std::make_shared(nullptr, node1_sym); + last_op = std::make_shared(last_op, node1_sym, this->GetSymbol("node2"), this->GetSymbol("edge"), + EdgeAtom::Direction::BOTH, + std::vector{this->dba.NameToEdgeType("EdgeType1"), + this->dba.NameToEdgeType("EdgeType2")}, + false, memgraph::storage::View::OLD); + last_op->dba_ = &this->dba; + + std::string expected_string{"Expand (node1)-[edge:EdgeType1|:EdgeType2]-(node2)"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, ExpandVariable) { + auto node1_sym = this->GetSymbol("node1"); + std::shared_ptr last_op = std::make_shared(nullptr, node1_sym); + last_op = std::make_shared( + last_op, node1_sym, this->GetSymbol("node2"), this->GetSymbol("edge"), EdgeAtom::Type::BREADTH_FIRST, + EdgeAtom::Direction::OUT, + std::vector{this->dba.NameToEdgeType("EdgeType1"), + this->dba.NameToEdgeType("EdgeType2")}, + false, LITERAL(2), LITERAL(5), false, + ExpansionLambda{this->GetSymbol("inner_node"), this->GetSymbol("inner_edge"), + PROPERTY_LOOKUP(this->dba, "inner_node", this->dba.NameToProperty("unblocked"))}, + std::nullopt, std::nullopt); + last_op->dba_ = &this->dba; + + std::string expected_string{"BFSExpand (node1)-[edge:EdgeType1|:EdgeType2]->(node2)"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, ConstructNamedPath) { + auto node1_sym = this->GetSymbol("node1"); + auto edge1_sym = this->GetSymbol("edge1"); + auto node2_sym = this->GetSymbol("node2"); + auto edge2_sym = this->GetSymbol("edge2"); + auto node3_sym = this->GetSymbol("node3"); + + std::shared_ptr last_op = std::make_shared(nullptr, node1_sym); + last_op = std::make_shared(last_op, node1_sym, node2_sym, edge1_sym, EdgeAtom::Direction::OUT, + std::vector{}, false, memgraph::storage::View::OLD); + last_op = std::make_shared(last_op, node2_sym, node3_sym, edge2_sym, EdgeAtom::Direction::OUT, + std::vector{}, false, memgraph::storage::View::OLD); + last_op = std::make_shared( + last_op, this->GetSymbol("path"), std::vector{node1_sym, edge1_sym, node2_sym, edge2_sym, node3_sym}); + + std::string expected_string{"ConstructNamedPath"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, Filter) { + std::shared_ptr last_op = std::make_shared(nullptr, this->GetSymbol("node1")); + last_op = + std::make_shared(last_op, std::vector>{}, + EQ(PROPERTY_LOOKUP(this->dba, "node1", this->dba.NameToProperty("prop")), LITERAL(5))); + + std::string expected_string{"Filter"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, Produce) { + std::shared_ptr last_op = std::make_shared( + nullptr, std::vector{NEXPR("pet", LITERAL(5)), NEXPR("string", LITERAL("string"))}); + + std::string expected_string{"Produce {pet, string}"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, Delete) { + auto node_sym = this->GetSymbol("node1"); + std::shared_ptr last_op = std::make_shared(nullptr, node_sym); + last_op = std::make_shared(last_op, node_sym, this->GetSymbol("node2"), this->GetSymbol("edge"), + EdgeAtom::Direction::BOTH, std::vector{}, false, + memgraph::storage::View::OLD); + last_op = std::make_shared(last_op, std::vector{IDENT("node2")}, true); + + std::string expected_string{"Delete"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, SetProperty) { + memgraph::storage::PropertyId prop = this->dba.NameToProperty("prop"); + + std::shared_ptr last_op = std::make_shared(nullptr, this->GetSymbol("node")); + last_op = std::make_shared(last_op, prop, PROPERTY_LOOKUP(this->dba, "node", prop), + ADD(PROPERTY_LOOKUP(this->dba, "node", prop), LITERAL(1))); + + std::string expected_string{"SetProperty"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, SetProperties) { + auto node_sym = this->GetSymbol("node"); + std::shared_ptr last_op = std::make_shared(nullptr, node_sym); + last_op = std::make_shared(last_op, node_sym, + MAP({{this->storage.GetPropertyIx("prop1"), LITERAL(1)}, + {this->storage.GetPropertyIx("prop2"), LITERAL("propko")}}), + plan::SetProperties::Op::REPLACE); + + std::string expected_string{"SetProperties"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, SetLabels) { + auto node_sym = this->GetSymbol("node"); + std::shared_ptr last_op = std::make_shared(nullptr, node_sym); + last_op = std::make_shared( + last_op, node_sym, + std::vector{this->dba.NameToLabel("label1"), this->dba.NameToLabel("label2")}); + + std::string expected_string{"SetLabels"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, RemoveProperty) { + auto node_sym = this->GetSymbol("node"); + std::shared_ptr last_op = std::make_shared(nullptr, node_sym); + last_op = std::make_shared( + last_op, this->dba.NameToProperty("prop"), PROPERTY_LOOKUP(this->dba, "node", this->dba.NameToProperty("prop"))); + + std::string expected_string{"RemoveProperty"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, RemoveLabels) { + auto node_sym = this->GetSymbol("node"); + std::shared_ptr last_op = std::make_shared(nullptr, node_sym); + last_op = std::make_shared( + last_op, node_sym, + std::vector{this->dba.NameToLabel("label1"), this->dba.NameToLabel("label2")}); + + std::string expected_string{"RemoveLabels"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, EdgeUniquenessFilter) { + auto node1_sym = this->GetSymbol("node1"); + auto node2_sym = this->GetSymbol("node2"); + auto node3_sym = this->GetSymbol("node3"); + auto node4_sym = this->GetSymbol("node4"); + + auto edge1_sym = this->GetSymbol("edge1"); + auto edge2_sym = this->GetSymbol("edge2"); + + std::shared_ptr last_op = std::make_shared(nullptr, node1_sym); + last_op = std::make_shared(last_op, node1_sym, node2_sym, edge1_sym, EdgeAtom::Direction::IN, + std::vector{}, false, memgraph::storage::View::OLD); + last_op = std::make_shared(last_op, node3_sym); + last_op = std::make_shared(last_op, node3_sym, node4_sym, edge2_sym, EdgeAtom::Direction::OUT, + std::vector{}, false, memgraph::storage::View::OLD); + last_op = std::make_shared(last_op, edge2_sym, std::vector{edge1_sym}); + + std::string expected_string{"EdgeUniquenessFilter"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, Accumulate) { + memgraph::storage::PropertyId prop = this->dba.NameToProperty("prop"); + auto node_sym = this->GetSymbol("node"); + std::shared_ptr last_op = std::make_shared(nullptr, node_sym); + last_op = std::make_shared(last_op, prop, PROPERTY_LOOKUP(this->dba, "node", prop), + ADD(PROPERTY_LOOKUP(this->dba, "node", prop), LITERAL(1))); + last_op = std::make_shared(last_op, std::vector{node_sym}, true); + + std::string expected_string{"Accumulate"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, Aggregate) { + memgraph::storage::PropertyId value = this->dba.NameToProperty("value"); + memgraph::storage::PropertyId color = this->dba.NameToProperty("color"); + memgraph::storage::PropertyId type = this->dba.NameToProperty("type"); + auto node_sym = this->GetSymbol("node"); + std::shared_ptr last_op; + last_op = std::make_shared( + nullptr, + std::vector{ + {PROPERTY_LOOKUP(this->dba, "node", value), nullptr, Aggregation::Op::SUM, this->GetSymbol("sum")}, + {PROPERTY_LOOKUP(this->dba, "node", value), PROPERTY_LOOKUP(this->dba, "node", color), + Aggregation::Op::COLLECT_MAP, this->GetSymbol("map")}, + {nullptr, nullptr, Aggregation::Op::COUNT, this->GetSymbol("count")}}, + std::vector{PROPERTY_LOOKUP(this->dba, "node", type)}, std::vector{node_sym}); + + std::string expected_string{"Aggregate {sum, map, count} {node}"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, Skip) { + std::shared_ptr last_op = std::make_shared(nullptr, this->GetSymbol("node")); + last_op = std::make_shared(last_op, LITERAL(42)); + + std::string expected_string{"Skip"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, Limit) { + std::shared_ptr last_op = std::make_shared(nullptr, this->GetSymbol("node")); + last_op = std::make_shared(last_op, LITERAL(42)); + + std::string expected_string{"Limit"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, OrderBy) { + Symbol person_sym = this->GetSymbol("person"); + Symbol pet_sym = this->GetSymbol("pet"); + memgraph::storage::PropertyId name = this->dba.NameToProperty("name"); + memgraph::storage::PropertyId age = this->dba.NameToProperty("age"); + std::shared_ptr last_op; + last_op = std::make_shared(nullptr, + std::vector{{Ordering::ASC, PROPERTY_LOOKUP(this->dba, "person", name)}, + {Ordering::DESC, PROPERTY_LOOKUP(this->dba, "pet", age)}}, + std::vector{person_sym, pet_sym}); + + std::string expected_string{"OrderBy {person, pet}"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, Merge) { + Symbol node_sym = this->GetSymbol("node"); + memgraph::storage::LabelId label = this->dba.NameToLabel("label"); + + std::shared_ptr match = std::make_shared(nullptr, node_sym, label); + + std::shared_ptr create = + std::make_shared(nullptr, NodeCreationInfo{node_sym, {label}, {}}); + + std::shared_ptr last_op = std::make_shared(nullptr, match, create); + + std::string expected_string{"Merge"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, Optional) { + Symbol node1_sym = this->GetSymbol("node1"); + Symbol node2_sym = this->GetSymbol("node2"); + Symbol edge_sym = this->GetSymbol("edge"); + + std::shared_ptr input = std::make_shared(nullptr, node1_sym); + + std::shared_ptr expand = + std::make_shared(nullptr, node1_sym, node2_sym, edge_sym, EdgeAtom::Direction::OUT, + std::vector{}, false, memgraph::storage::View::OLD); + + std::shared_ptr last_op = + std::make_shared(input, expand, std::vector{node2_sym, edge_sym}); + + std::string expected_string{"Optional"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, Unwind) { + std::shared_ptr last_op = + std::make_shared(nullptr, LIST(LITERAL(1), LITERAL(2), LITERAL(3)), this->GetSymbol("x")); + + std::string expected_string{"Unwind"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, Distinct) { + Symbol x = this->GetSymbol("x"); + std::shared_ptr last_op = + std::make_shared(nullptr, LIST(LITERAL(2), LITERAL(3), LITERAL(2)), x); + last_op = std::make_shared(last_op, std::vector{x}); + + std::string expected_string{"Distinct"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, Union) { + Symbol x = this->GetSymbol("x"); + std::shared_ptr lhs = + std::make_shared(nullptr, LIST(LITERAL(2), LITERAL(3), LITERAL(2)), x); + + Symbol node = this->GetSymbol("x"); + std::shared_ptr rhs = std::make_shared(nullptr, node); + + std::shared_ptr last_op = std::make_shared( + lhs, rhs, std::vector{this->GetSymbol("x")}, std::vector{x}, std::vector{node}); + + std::string expected_string{"Union {x : x}"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, CallProcedure) { + memgraph::query::plan::CallProcedure call_op; + call_op.input_ = std::make_shared(); + call_op.procedure_name_ = "mg.procedures"; + call_op.arguments_ = {}; + call_op.result_fields_ = {"is_editable", "is_write", "name", "path", "signature"}; + call_op.result_symbols_ = {this->GetSymbol("is_editable"), this->GetSymbol("is_write"), this->GetSymbol("name"), + this->GetSymbol("path"), this->GetSymbol("signature")}; + + std::string expected_string{"CallProcedure {is_editable, is_write, name, path, signature}"}; + EXPECT_EQ(call_op.ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, LoadCsv) { + memgraph::query::plan::LoadCsv last_op; + last_op.input_ = std::make_shared(); + last_op.row_var_ = this->GetSymbol("transaction"); + + std::string expected_string{"LoadCsv {transaction}"}; + EXPECT_EQ(last_op.ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, Foreach) { + Symbol x = this->GetSymbol("x"); + std::shared_ptr create = std::make_shared( + nullptr, NodeCreationInfo{this->GetSymbol("node"), {this->dba.NameToLabel("Label1")}, {}}); + std::shared_ptr foreach = + std::make_shared(nullptr, std::move(create), LIST(LITERAL(1)), x); + + std::string expected_string{"Foreach"}; + EXPECT_EQ(foreach->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, EmptyResult) { + std::shared_ptr last_op = std::make_shared(nullptr); + + std::string expected_string{"EmptyResult"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, EvaluatePatternFilter) { + std::shared_ptr last_op = std::make_shared(nullptr, this->GetSymbol("node")); + + std::string expected_string{"EvaluatePatternFilter"}; + EXPECT_EQ(last_op->ToString(), expected_string); +} + +TYPED_TEST(OperatorToStringTest, Apply) { + memgraph::query::plan::Apply last_op(nullptr, nullptr, false); + + std::string expected_string{"Apply"}; + EXPECT_EQ(last_op.ToString(), expected_string); +}