// 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. #pragma once #include #include #include #include #include #include #include #include "query/common.hpp" #include "query/frontend/ast/ast.hpp" #include "query/frontend/semantic/symbol.hpp" #include "query/plan/preprocess.hpp" #include "query/typed_value.hpp" #include "storage/v2/id_types.hpp" #include "utils/bound.hpp" #include "utils/fnv.hpp" #include "utils/logging.hpp" #include "utils/memory.hpp" #include "utils/synchronized.hpp" #include "utils/visitor.hpp" namespace memgraph { namespace query { struct ExecutionContext; class ExpressionEvaluator; class Frame; class SymbolTable; namespace plan { /// Base class for iteration cursors of @c LogicalOperator classes. /// /// Each @c LogicalOperator must produce a concrete @c Cursor, which provides /// the iteration mechanism. class Cursor { public: /// Run an iteration of a @c LogicalOperator. /// /// Since operators may be chained, the iteration may pull results from /// multiple operators. /// /// @param Frame 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. /// /// @throws QueryRuntimeException if something went wrong with execution virtual bool Pull(Frame &, ExecutionContext &) = 0; /// Resets the Cursor to its initial state. virtual void Reset() = 0; /// Perform cleanup which may throw an exception virtual void Shutdown() = 0; virtual ~Cursor() {} }; /// unique_ptr to Cursor managed with a custom deleter. /// This allows us to use utils::MemoryResource for allocation. using UniqueCursorPtr = std::unique_ptr>; template std::unique_ptr> MakeUniqueCursorPtr(utils::Allocator allocator, TArgs &&...args) { auto *ptr = allocator.allocate(1); try { auto *cursor = new (ptr) TCursor(std::forward(args)...); return std::unique_ptr>(cursor, [allocator](Cursor *base_ptr) mutable { auto *p = static_cast(base_ptr); p->~TCursor(); allocator.deallocate(p, 1); }); } catch (...) { allocator.deallocate(ptr, 1); throw; } } class Once; class CreateNode; class CreateExpand; class ScanAll; class ScanAllByLabel; class ScanAllByLabelPropertyRange; class ScanAllByLabelPropertyValue; class ScanAllByLabelProperty; class ScanAllById; class Expand; class ExpandVariable; class ConstructNamedPath; class Filter; class Produce; class Delete; class SetProperty; class SetProperties; class SetLabels; class RemoveProperty; class RemoveLabels; class EdgeUniquenessFilter; class Accumulate; class Aggregate; class Skip; class Limit; class OrderBy; class Merge; class Optional; class Unwind; class Distinct; class Union; class Cartesian; class CallProcedure; class LoadCsv; class Foreach; class EmptyResult; class EvaluatePatternFilter; class Apply; class IndexedJoin; class HashJoin; using LogicalOperatorCompositeVisitor = utils::CompositeVisitor; using LogicalOperatorLeafVisitor = utils::LeafVisitor; /** * @brief Base class for hierarchical visitors of @c LogicalOperator class * hierarchy. */ class HierarchicalLogicalOperatorVisitor : public LogicalOperatorCompositeVisitor, public LogicalOperatorLeafVisitor { public: using LogicalOperatorCompositeVisitor::PostVisit; using LogicalOperatorCompositeVisitor::PreVisit; using LogicalOperatorLeafVisitor::Visit; 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, public memgraph::query::plan::NamedLogicalOperator { public: static const utils::TypeInfo kType; virtual const utils::TypeInfo &GetTypeInfo() const { return kType; } virtual ~LogicalOperator() {} /** Construct a @c Cursor which is used to run this operator. * * @param utils::MemoryResource Memory resource used for allocations during * the lifetime of the returned Cursor. */ virtual UniqueCursorPtr MakeCursor(utils::MemoryResource *) const = 0; /** Return @c Symbol vector where the query results will be stored. * * Currently, output symbols are generated in @c Produce @c Union and * @c CallProcedure operators. @c Skip, @c Limit, @c OrderBy and @c Distinct * propagate the symbols from @c Produce (if it exists as input operator). * * @param SymbolTable used to find symbols for expressions. * @return std::vector used for results. */ virtual std::vector OutputSymbols(const SymbolTable &) const { return std::vector(); } /** * Symbol vector whose values are modified by this operator sub-tree. * * This is different than @c OutputSymbols, because it returns all of the * modified symbols, including those that may not be returned as the * result of the query. Note that the modified symbols will not contain * those that should not be read after the operator is processed. * * For example, `MATCH (n)-[e]-(m) RETURN n AS l` will generate `ScanAll (n) > * Expand (e, m) > Produce (l)`. The modified symbols on Produce sub-tree will * be `l`, the same as output symbols, because it isn't valid to read `n`, `e` * nor `m` after Produce. On the other hand, modified symbols from Expand * contain `e` and `m`, as well as `n`, while output symbols are empty. * Modified symbols from ScanAll contain only `n`, while output symbols are * also empty. */ virtual std::vector ModifiedSymbols(const SymbolTable &) const = 0; /** * Returns true if the operator takes only one input operator. * NOTE: When this method returns true, you may use `input` and `set_input` * methods. */ virtual bool HasSingleInput() const = 0; /** * Returns the input operator if it has any. * NOTE: This should only be called if `HasSingleInput() == true`. */ virtual std::shared_ptr input() const = 0; /** * Set a different input on this operator. * NOTE: This should only be called if `HasSingleInput() == true`. */ virtual void set_input(std::shared_ptr) = 0; struct SaveHelper { std::vector saved_ops; }; struct LoadHelper { AstStorage ast_storage; std::vector>> loaded_ops; }; struct SlkLoadHelper { AstStorage ast_storage; std::vector> loaded_ops; }; std::string ToString() const override { return GetTypeInfo().name; } virtual std::unique_ptr Clone(AstStorage *storage) const = 0; }; /// A logical operator whose Cursor returns true on the first Pull /// and false on every following Pull. class Once : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } Once(std::vector symbols = {}) : symbols_{std::move(symbols)} {} DEFVISITABLE(HierarchicalLogicalOperatorVisitor); UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector ModifiedSymbols(const SymbolTable &) const override { return symbols_; } bool HasSingleInput() const override; std::shared_ptr input() const override; void set_input(std::shared_ptr) override; std::vector symbols_; std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->symbols_ = symbols_; return object; } private: class OnceCursor : public Cursor { public: OnceCursor() {} bool Pull(Frame &, ExecutionContext &) override; void Shutdown() override; void Reset() override; private: bool did_pull_{false}; }; }; using PropertiesMapList = std::vector>; struct NodeCreationInfo { static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const { return kType; } NodeCreationInfo() = default; NodeCreationInfo(Symbol symbol, std::vector labels, std::variant properties) : symbol{std::move(symbol)}, labels{std::move(labels)}, properties{std::move(properties)} {}; NodeCreationInfo(Symbol symbol, std::vector labels, PropertiesMapList properties) : symbol{std::move(symbol)}, labels{std::move(labels)}, properties{std::move(properties)} {}; NodeCreationInfo(Symbol symbol, std::vector labels, ParameterLookup *properties) : symbol{std::move(symbol)}, labels{std::move(labels)}, properties{properties} {}; Symbol symbol; std::vector labels; std::variant properties; NodeCreationInfo Clone(AstStorage *storage) const { NodeCreationInfo object; object.symbol = symbol; object.labels = labels; if (const auto *props = std::get_if(&properties)) { auto &destination_props = std::get(object.properties); destination_props.resize(props->size()); for (auto i0 = 0; i0 < props->size(); ++i0) { { storage::PropertyId first1 = (*props)[i0].first; Expression *second2; second2 = (*props)[i0].second ? (*props)[i0].second->Clone(storage) : nullptr; destination_props[i0] = std::make_pair(std::move(first1), std::move(second2)); } } } else { object.properties = std::get(properties)->Clone(storage); } return object; } }; /// Operator for creating a node. /// /// This op is used both for creating a single node (`CREATE` statement without /// a preceding `MATCH`), or multiple nodes (`MATCH ... CREATE` or /// `CREATE (), () ...`). /// /// @sa CreateExpand class CreateNode : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } CreateNode() {} /** * @param input Optional. If @c nullptr, then a single node will be * created (a single successful @c Cursor::Pull from this op's @c Cursor). * If a valid input, then a node will be created for each * successful pull from the given input. * @param node_info @c NodeCreationInfo */ CreateNode(const std::shared_ptr &input, const NodeCreationInfo &node_info); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = input; } std::shared_ptr input_; memgraph::query::plan::NodeCreationInfo node_info_; std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; object->node_info_ = node_info_.Clone(storage); return object; } private: class CreateNodeCursor : public Cursor { public: CreateNodeCursor(const CreateNode &, utils::MemoryResource *); bool Pull(Frame &, ExecutionContext &) override; void Shutdown() override; void Reset() override; private: const CreateNode &self_; const UniqueCursorPtr input_cursor_; }; }; struct EdgeCreationInfo { static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const { return kType; } EdgeCreationInfo() = default; EdgeCreationInfo(Symbol symbol, std::variant properties, storage::EdgeTypeId edge_type, EdgeAtom::Direction direction) : symbol{std::move(symbol)}, properties{std::move(properties)}, edge_type{edge_type}, direction{direction} {}; EdgeCreationInfo(Symbol symbol, PropertiesMapList properties, storage::EdgeTypeId edge_type, EdgeAtom::Direction direction) : symbol{std::move(symbol)}, properties{std::move(properties)}, edge_type{edge_type}, direction{direction} {}; EdgeCreationInfo(Symbol symbol, ParameterLookup *properties, storage::EdgeTypeId edge_type, EdgeAtom::Direction direction) : symbol{std::move(symbol)}, properties{properties}, edge_type{edge_type}, direction{direction} {}; Symbol symbol; std::variant properties; storage::EdgeTypeId edge_type; EdgeAtom::Direction direction{EdgeAtom::Direction::BOTH}; EdgeCreationInfo Clone(AstStorage *storage) const { EdgeCreationInfo object; object.symbol = symbol; if (const auto *props = std::get_if(&properties)) { auto &destination_props = std::get(object.properties); destination_props.resize(props->size()); for (auto i0 = 0; i0 < props->size(); ++i0) { { storage::PropertyId first1 = (*props)[i0].first; Expression *second2; second2 = (*props)[i0].second ? (*props)[i0].second->Clone(storage) : nullptr; destination_props[i0] = std::make_pair(std::move(first1), std::move(second2)); } } } else { object.properties = std::get(properties)->Clone(storage); } object.edge_type = edge_type; object.direction = direction; return object; } }; /// Operator for creating edges and destination nodes. /// /// This operator extends already created nodes with an edge. If the other node /// on the edge does not exist, it will be created. For example, in `MATCH (n) /// CREATE (n) -[r:r]-> (n)` query, this operator will create just the edge `r`. /// In `MATCH (n) CREATE (n) -[r:r]-> (m)` query, the operator will create both /// the edge `r` and the node `m`. In case of `CREATE (n) -[r:r]-> (m)` the /// first node `n` is created by @c CreateNode operator, while @c CreateExpand /// will create the edge `r` and `m`. Similarly, multiple @c CreateExpand are /// chained in cases when longer paths need creating. /// /// @sa CreateNode class CreateExpand : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } CreateExpand() {} /** @brief Construct @c CreateExpand. * * @param node_info @c NodeCreationInfo at the end of the edge. * Used to create a node, unless it refers to an existing one. * @param edge_info @c EdgeCreationInfo for the edge to be created. * @param input Optional. Previous @c LogicalOperator which will be pulled. * For each successful @c Cursor::Pull, this operator will create an * expansion. * @param input_symbol @c Symbol for the node at the start of the edge. * @param existing_node @c bool indicating whether the @c node_atom refers to * an existing node. If @c false, the operator will also create the node. */ CreateExpand(const NodeCreationInfo &node_info, const EdgeCreationInfo &edge_info, const std::shared_ptr &input, Symbol input_symbol, bool existing_node); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = input; } memgraph::query::plan::NodeCreationInfo node_info_; memgraph::query::plan::EdgeCreationInfo edge_info_; std::shared_ptr input_; Symbol input_symbol_; /// 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); object->edge_info_ = edge_info_.Clone(storage); object->input_ = input_ ? input_->Clone(storage) : nullptr; object->input_symbol_ = input_symbol_; object->existing_node_ = existing_node_; return object; } private: class CreateExpandCursor : public Cursor { public: CreateExpandCursor(const CreateExpand &, utils::MemoryResource *); bool Pull(Frame &, ExecutionContext &) override; void Shutdown() override; void Reset() override; private: const CreateExpand &self_; const UniqueCursorPtr input_cursor_; // Get the existing node (if existing_node_ == true), or create a new node VertexAccessor &OtherVertex(Frame &frame, ExecutionContext &context); }; }; /// Operator which iterates over all the nodes currently in the database. /// When given an input (optional), does a cartesian product. /// /// It accepts an optional input. If provided then this op scans all the nodes /// currently in the database for each successful Pull from it's input, thereby /// producing a cartesian product of input Pulls and database elements. /// /// ScanAll can either iterate over the previous graph state (state before /// the current transacton+command) or over current state. This is controlled /// with a constructor argument. /// /// @sa ScanAllByLabel /// @sa ScanAllByLabelPropertyRange /// @sa ScanAllByLabelPropertyValue class ScanAll : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } ScanAll() {} ScanAll(const std::shared_ptr &input, Symbol output_symbol, storage::View view = storage::View::OLD); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = input; } std::shared_ptr input_; Symbol output_symbol_; /// Controls which graph state is used to produce vertices. /// /// If @c storage::View::OLD, @c ScanAll will produce vertices visible in the /// previous graph state, before modifications done by current transaction & /// command. With @c storage::View::NEW, all vertices will be produced the current /// 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; object->output_symbol_ = output_symbol_; object->view_ = view_; return object; } }; /// Behaves like @c ScanAll, but this operator produces only vertices with /// given label. /// /// @sa ScanAll /// @sa ScanAllByLabelPropertyRange /// @sa ScanAllByLabelPropertyValue class ScanAllByLabel : public memgraph::query::plan::ScanAll { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } ScanAllByLabel() {} ScanAllByLabel(const std::shared_ptr &input, Symbol output_symbol, storage::LabelId label, storage::View view = storage::View::OLD); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; 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; object->output_symbol_ = output_symbol_; object->view_ = view_; object->label_ = label_; return object; } }; /// Behaves like @c ScanAll, but produces only vertices with given label and /// property value which is inside a range (inclusive or exlusive). /// /// @sa ScanAll /// @sa ScanAllByLabel /// @sa ScanAllByLabelPropertyValue class ScanAllByLabelPropertyRange : public memgraph::query::plan::ScanAll { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } /** Bound with expression which when evaluated produces the bound value. */ using Bound = utils::Bound; ScanAllByLabelPropertyRange() {} /** * Constructs the operator for given label and property value in range * (inclusive). * * Range bounds are optional, but only one bound can be left out. * * @param input Preceding operator which will serve as the input. * @param output_symbol Symbol where the vertices will be stored. * @param label Label which the vertex must have. * @param property Property from which the value will be looked up from. * @param lower_bound Optional lower @c Bound. * @param upper_bound Optional upper @c Bound. * @param view storage::View used when obtaining vertices. */ ScanAllByLabelPropertyRange(const std::shared_ptr &input, Symbol output_symbol, storage::LabelId label, storage::PropertyId property, const std::string &property_name, std::optional lower_bound, std::optional upper_bound, storage::View view = storage::View::OLD); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; storage::LabelId label_; storage::PropertyId property_; std::string property_name_; 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; object->output_symbol_ = output_symbol_; object->view_ = view_; object->label_ = label_; object->property_ = property_; object->property_name_ = property_name_; if (lower_bound_) { object->lower_bound_.emplace( utils::Bound(lower_bound_->value()->Clone(storage), lower_bound_->type())); } else { object->lower_bound_ = std::nullopt; } if (upper_bound_) { object->upper_bound_.emplace( utils::Bound(upper_bound_->value()->Clone(storage), upper_bound_->type())); } else { object->upper_bound_ = std::nullopt; } return object; } }; /// Behaves like @c ScanAll, but produces only vertices with given label and /// property value. /// /// @sa ScanAll /// @sa ScanAllByLabel /// @sa ScanAllByLabelPropertyRange class ScanAllByLabelPropertyValue : public memgraph::query::plan::ScanAll { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } ScanAllByLabelPropertyValue() {} /** * Constructs the operator for given label and property value. * * @param input Preceding operator which will serve as the input. * @param output_symbol Symbol where the vertices will be stored. * @param label Label which the vertex must have. * @param property Property from which the value will be looked up from. * @param expression Expression producing the value of the vertex property. * @param view storage::View used when obtaining vertices. */ ScanAllByLabelPropertyValue(const std::shared_ptr &input, Symbol output_symbol, storage::LabelId label, storage::PropertyId property, const std::string &property_name, Expression *expression, storage::View view = storage::View::OLD); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; storage::LabelId label_; storage::PropertyId property_; 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; object->output_symbol_ = output_symbol_; object->view_ = view_; object->label_ = label_; object->property_ = property_; object->property_name_ = property_name_; object->expression_ = expression_ ? expression_->Clone(storage) : nullptr; return object; } }; /// Behaves like @c ScanAll, but this operator produces only vertices with /// given label and property. /// /// @sa ScanAll /// @sa ScanAllByLabelPropertyRange /// @sa ScanAllByLabelPropertyValue class ScanAllByLabelProperty : public memgraph::query::plan::ScanAll { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } ScanAllByLabelProperty() {} ScanAllByLabelProperty(const std::shared_ptr &input, Symbol output_symbol, storage::LabelId label, storage::PropertyId property, const std::string &property_name, storage::View view = storage::View::OLD); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; storage::LabelId label_; storage::PropertyId property_; 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; object->output_symbol_ = output_symbol_; object->view_ = view_; object->label_ = label_; object->property_ = property_; object->property_name_ = property_name_; object->expression_ = expression_ ? expression_->Clone(storage) : nullptr; return object; } }; /// ScanAll producing a single node with ID equal to evaluated expression class ScanAllById : public memgraph::query::plan::ScanAll { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } ScanAllById() {} ScanAllById(const std::shared_ptr &input, Symbol output_symbol, Expression *expression, storage::View view = storage::View::OLD); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; 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; object->output_symbol_ = output_symbol_; object->view_ = view_; object->expression_ = expression_ ? expression_->Clone(storage) : nullptr; return object; } }; struct ExpandCommon { static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const { return kType; } /// Symbol pointing to the node to be expanded. /// This is where the new node will be stored. Symbol node_symbol; /// Symbol for the edges to be expanded. /// This is where a TypedValue containing a list of expanded edges will be stored. Symbol edge_symbol; /// EdgeAtom::Direction determining the direction of edge /// expansion. The direction is relative to the starting vertex for each expansion. EdgeAtom::Direction direction; /// storage::EdgeTypeId specifying which edges we want /// to expand. If empty, all edges are valid. If not empty, only edges with one of /// the given types are valid. std::vector edge_types; /// If the given node atom refer to a symbol /// that has already been expanded and should be just validated in the frame. bool existing_node; }; struct ExpansionInfo { std::optional input_node; EdgeAtom::Direction direction; std::optional existing_node; bool reversed{false}; }; /// Expansion operator. For a node existing in the frame it /// expands one edge and one node and places them on the frame. /// /// This class does not handle node/edge filtering based on /// properties, labels and edge types. However, it does handle /// filtering on existing node / edge. /// /// Filtering on existing means that for a pattern that references /// an already declared node or edge (for example in /// MATCH (a) MATCH (a)--(b)), /// only expansions that match defined equalities are successfully /// pulled. class Expand : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } /** * Creates an expansion. All parameters except input and input_symbol are * forwarded to @c ExpandCommon and are documented there. * * @param input Optional logical operator that preceeds this one. * @param input_symbol Symbol that points to a VertexAccessor in the frame * that expansion should emanate from. */ Expand(const std::shared_ptr &input, Symbol input_symbol, Symbol node_symbol, Symbol edge_symbol, EdgeAtom::Direction direction, const std::vector &edge_types, bool existing_node, storage::View view); Expand() {} bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = input; } class ExpandCursor : public Cursor { public: ExpandCursor(const Expand &, utils::MemoryResource *); ExpandCursor(const Expand &, int64_t input_degree, int64_t existing_node_degree, utils::MemoryResource *); bool Pull(Frame &, ExecutionContext &) override; void Shutdown() override; void Reset() override; ExpansionInfo GetExpansionInfo(Frame &); private: using InEdgeT = std::vector; using InEdgeIteratorT = decltype(std::declval().begin()); using OutEdgeT = std::vector; using OutEdgeIteratorT = decltype(std::declval().begin()); const Expand &self_; const UniqueCursorPtr input_cursor_; // The iterable over edges and the current edge iterator are referenced via // optional because they can not be initialized in the constructor of // this class. They are initialized once for each pull from the input. std::optional in_edges_; std::optional in_edges_it_; std::optional out_edges_; std::optional out_edges_it_; ExpansionInfo expansion_info_; int64_t prev_input_degree_{-1}; int64_t prev_existing_degree_{-1}; bool InitEdges(Frame &, ExecutionContext &); }; std::shared_ptr input_; Symbol input_symbol_; memgraph::query::plan::ExpandCommon common_; /// 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; object->input_symbol_ = input_symbol_; object->common_ = common_; object->view_ = view_; return object; } }; struct ExpansionLambda { static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const { return kType; } /// Currently expanded edge symbol. Symbol inner_edge_symbol; /// Currently expanded node symbol. Symbol inner_node_symbol; /// Expression used in lambda during expansion. Expression *expression; ExpansionLambda Clone(AstStorage *storage) const { ExpansionLambda object; object.inner_edge_symbol = inner_edge_symbol; object.inner_node_symbol = inner_node_symbol; object.expression = expression ? expression->Clone(storage) : nullptr; return object; } }; /// Variable-length expansion operator. For a node existing in /// the frame it expands a variable number of edges and places them /// (in a list-type TypedValue), as well as the final destination node, /// on the frame. /// /// This class does not handle node/edge filtering based on /// properties, labels and edge types. However, it does handle /// filtering on existing node / edge. Additionally it handles's /// edge-uniquess (cyphermorphism) because it's not feasable to do /// later. /// /// Filtering on existing means that for a pattern that references /// an already declared node or edge (for example in /// MATCH (a) MATCH (a)--(b)), /// only expansions that match defined equalities are succesfully /// pulled. class ExpandVariable : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } ExpandVariable() {} /** * Creates a variable-length expansion. Most params are forwarded * to the @c ExpandCommon constructor, and are documented there. * * Expansion length bounds are both inclusive (as in Neo's Cypher * implementation). * * @param input Optional logical operator that preceeds this one. * @param input_symbol Symbol that points to a VertexAccessor in the frame * that expansion should emanate from. * @param type - Either Type::DEPTH_FIRST (default variable-length expansion), * or Type::BREADTH_FIRST. * @param is_reverse Set to `true` if the edges written on frame should expand * from `node_symbol` to `input_symbol`. Opposed to the usual expanding * from `input_symbol` to `node_symbol`. * @param lower_bound An optional indicator of the minimum number of edges * that get expanded (inclusive). * @param upper_bound An optional indicator of the maximum number of edges * that get expanded (inclusive). * @param inner_edge_symbol Like `inner_node_symbol` * @param inner_node_symbol For each expansion the node expanded into is * assigned to this symbol so it can be evaulated by the 'where' * expression. * @param filter_ The filter that must be satisfied for an expansion to * succeed. Can use inner(node/edge) symbols. If nullptr, it is ignored. */ ExpandVariable(const std::shared_ptr &input, Symbol input_symbol, Symbol node_symbol, Symbol edge_symbol, EdgeAtom::Type type, EdgeAtom::Direction direction, const std::vector &edge_types, bool is_reverse, Expression *lower_bound, Expression *upper_bound, bool existing_node, ExpansionLambda filter_lambda, std::optional weight_lambda, std::optional total_weight); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = input; } std::shared_ptr input_; Symbol input_symbol_; memgraph::query::plan::ExpandCommon common_; EdgeAtom::Type type_; /// True if the path should be written as expanding from node_symbol to input_symbol. bool is_reverse_; /// Optional lower bound of the variable length expansion, defaults are (1, inf) Expression *lower_bound_; /// Optional upper bound of the variable length expansion, defaults are (1, inf) Expression *upper_bound_; memgraph::query::plan::ExpansionLambda filter_lambda_; 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; object->input_symbol_ = input_symbol_; object->common_ = common_; object->type_ = type_; object->is_reverse_ = is_reverse_; object->lower_bound_ = lower_bound_ ? lower_bound_->Clone(storage) : nullptr; object->upper_bound_ = upper_bound_ ? upper_bound_->Clone(storage) : nullptr; object->filter_lambda_ = filter_lambda_.Clone(storage); if (weight_lambda_) { memgraph::query::plan::ExpansionLambda value0; value0 = (*weight_lambda_).Clone(storage); object->weight_lambda_.emplace(std::move(value0)); } else { object->weight_lambda_ = std::nullopt; } object->total_weight_ = total_weight_; return object; } private: // the Cursors are not declared in the header because // it's edges_ and edges_it_ are decltyped using a helper function // that should be inaccessible (private class function won't compile) friend class ExpandVariableCursor; friend class ExpandWeightedShortestPathCursor; friend class ExpandAllShortestPathCursor; }; /// Constructs a named path from its elements and places it on the frame. class ConstructNamedPath : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } ConstructNamedPath() {} ConstructNamedPath(const std::shared_ptr &input, Symbol path_symbol, const std::vector &path_elements) : input_(input), path_symbol_(path_symbol), path_elements_(path_elements) {} bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = input; } std::shared_ptr input_; Symbol path_symbol_; std::vector path_elements_; std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; object->path_symbol_ = path_symbol_; object->path_elements_ = path_elements_; return object; } }; /// Filter whose Pull returns true only when the given expression /// evaluates into true. /// /// The given expression is assumed to return either NULL (treated as false) or /// a boolean value. class Filter : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } Filter() {} Filter(const std::shared_ptr &input, const std::vector> &pattern_filters, Expression *expression); Filter(const std::shared_ptr &input, const std::vector> &pattern_filters, Expression *expression, const Filters &all_filters); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = input; } std::shared_ptr input_; std::vector> pattern_filters_; Expression *expression_; const memgraph::query::plan::Filters all_filters_; static std::string SingleFilterName(const query::plan::FilterInfo &single_filter) { using Type = query::plan::FilterInfo::Type; if (single_filter.type == Type::Generic) { std::set symbol_names; for (const auto &symbol : single_filter.used_symbols) { symbol_names.insert(symbol.name()); } return fmt::format("Generic {{{}}}", utils::IterableToString(symbol_names, ", ", [](const auto &name) { return name; })); } else if (single_filter.type == Type::Id) { return fmt::format("id({})", single_filter.id_filter->symbol_.name()); } else if (single_filter.type == Type::Label) { if (single_filter.expression->GetTypeInfo() != LabelsTest::kType) { LOG_FATAL("Label filters not using LabelsTest are not supported for query inspection!"); } auto filter_expression = static_cast(single_filter.expression); std::set label_names; for (const auto &label : filter_expression->labels_) { label_names.insert(label.name); } if (filter_expression->expression_->GetTypeInfo() != Identifier::kType) { return fmt::format("(:{})", utils::IterableToString(label_names, ":", [](const auto &name) { return name; })); } auto identifier_expression = static_cast(filter_expression->expression_); return fmt::format("({} :{})", identifier_expression->name_, utils::IterableToString(label_names, ":", [](const auto &name) { return name; })); } else if (single_filter.type == Type::Pattern) { return "Pattern"; } else if (single_filter.type == Type::Property) { return fmt::format("{{{}.{}}}", single_filter.property_filter->symbol_.name(), single_filter.property_filter->property_.name); } else { LOG_FATAL("Unexpected FilterInfo::Type"); } } std::string ToString() const override { std::set filter_names; for (const auto &filter : all_filters_) { filter_names.insert(Filter::SingleFilterName(filter)); } return fmt::format("Filter {}", utils::IterableToString(filter_names, ", ", [](const auto &name) { return name; })); } std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; object->pattern_filters_.resize(pattern_filters_.size()); for (auto i1 = 0; i1 < pattern_filters_.size(); ++i1) { object->pattern_filters_[i1] = pattern_filters_[i1] ? pattern_filters_[i1]->Clone(storage) : nullptr; } object->expression_ = expression_ ? expression_->Clone(storage) : nullptr; return object; } private: class FilterCursor : public Cursor { public: FilterCursor(const Filter &, utils::MemoryResource *); bool Pull(Frame &, ExecutionContext &) override; void Shutdown() override; void Reset() override; private: const Filter &self_; const UniqueCursorPtr input_cursor_; const std::vector pattern_filter_cursors_; }; }; /// A logical operator that places an arbitrary number /// of named expressions on the frame (the logical operator /// for the RETURN clause). /// /// Supports optional input. When the input is provided, /// it is Pulled from and the Produce succeeds once for /// every input Pull (typically a MATCH/RETURN query). /// When the input is not provided (typically a standalone /// RETURN clause) the Produce's pull succeeds exactly once. class Produce : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } Produce() {} Produce(const std::shared_ptr &input, const std::vector &named_expressions); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector OutputSymbols(const SymbolTable &) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = input; } 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; object->named_expressions_.resize(named_expressions_.size()); for (auto i2 = 0; i2 < named_expressions_.size(); ++i2) { object->named_expressions_[i2] = named_expressions_[i2] ? named_expressions_[i2]->Clone(storage) : nullptr; } return object; } private: class ProduceCursor : public Cursor { public: ProduceCursor(const Produce &, utils::MemoryResource *); bool Pull(Frame &, ExecutionContext &) override; void Shutdown() override; void Reset() override; private: const Produce &self_; const UniqueCursorPtr input_cursor_; }; }; struct DeleteBuffer { std::vector nodes{}; std::vector edges{}; }; /// Operator for deleting vertices and edges. /// /// Has a flag for using DETACH DELETE when deleting vertices. class Delete : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } Delete() {} Delete(const std::shared_ptr &input_, const std::vector &expressions, bool detach_); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = input; } std::shared_ptr input_; std::vector expressions_; /// Whether the vertex should be detached before deletion. If not detached, /// and has connections, an error is raised when deleting edges. bool detach_; std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; object->expressions_.resize(expressions_.size()); for (auto i3 = 0; i3 < expressions_.size(); ++i3) { object->expressions_[i3] = expressions_[i3] ? expressions_[i3]->Clone(storage) : nullptr; } object->detach_ = detach_; return object; } private: class DeleteCursor : public Cursor { public: DeleteCursor(const Delete &, utils::MemoryResource *); bool Pull(Frame &, ExecutionContext &) override; void Shutdown() override; void Reset() override; private: const Delete &self_; const UniqueCursorPtr input_cursor_; DeleteBuffer buffer_; bool delete_executed_{false}; void UpdateDeleteBuffer(Frame &, ExecutionContext &); }; }; /// Logical operator for setting a single property on a single vertex or edge. /// /// The property value is an expression that must evaluate to some type that /// can be stored (a TypedValue that can be converted to PropertyValue). class SetProperty : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } SetProperty() {} SetProperty(const std::shared_ptr &input, storage::PropertyId property, PropertyLookup *lhs, Expression *rhs); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = input; } std::shared_ptr input_; storage::PropertyId property_; PropertyLookup *lhs_; Expression *rhs_; std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; object->property_ = property_; object->lhs_ = lhs_ ? lhs_->Clone(storage) : nullptr; object->rhs_ = rhs_ ? rhs_->Clone(storage) : nullptr; return object; } private: class SetPropertyCursor : public Cursor { public: SetPropertyCursor(const SetProperty &, utils::MemoryResource *); bool Pull(Frame &, ExecutionContext &) override; void Shutdown() override; void Reset() override; private: const SetProperty &self_; const UniqueCursorPtr input_cursor_; }; }; /// Logical operator for setting the whole property set on a vertex or an edge. /// /// The value being set is an expression that must evaluate to a vertex, edge or /// map (literal or parameter). /// /// Supports setting (replacing the whole properties set with another) and /// updating. class SetProperties : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } /// Defines how setting the properties works. /// /// @c UPDATE means that the current property set is augmented with additional /// ones (existing props of the same name are replaced), while @c REPLACE means /// that the old properties are discarded and replaced with new ones. enum class Op { UPDATE, REPLACE }; SetProperties() {} SetProperties(const std::shared_ptr &input, Symbol input_symbol, Expression *rhs, Op op); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = input; } std::shared_ptr input_; Symbol input_symbol_; Expression *rhs_; memgraph::query::plan::SetProperties::Op op_; std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; object->input_symbol_ = input_symbol_; object->rhs_ = rhs_ ? rhs_->Clone(storage) : nullptr; object->op_ = op_; return object; } private: class SetPropertiesCursor : public Cursor { public: SetPropertiesCursor(const SetProperties &, utils::MemoryResource *); bool Pull(Frame &, ExecutionContext &) override; void Shutdown() override; void Reset() override; private: const SetProperties &self_; const UniqueCursorPtr input_cursor_; std::unordered_map cached_name_id_{}; }; }; /// Logical operator for setting an arbitrary number of labels on a Vertex. /// /// It does NOT remove labels that are already set on that Vertex. class SetLabels : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } SetLabels() {} SetLabels(const std::shared_ptr &input, Symbol input_symbol, const std::vector &labels); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = input; } std::shared_ptr input_; Symbol input_symbol_; std::vector labels_; std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; object->input_symbol_ = input_symbol_; object->labels_ = labels_; return object; } private: class SetLabelsCursor : public Cursor { public: SetLabelsCursor(const SetLabels &, utils::MemoryResource *); bool Pull(Frame &, ExecutionContext &) override; void Shutdown() override; void Reset() override; private: const SetLabels &self_; const UniqueCursorPtr input_cursor_; }; }; /// Logical operator for removing a property from an edge or a vertex. class RemoveProperty : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } RemoveProperty() {} RemoveProperty(const std::shared_ptr &input, storage::PropertyId property, PropertyLookup *lhs); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = input; } std::shared_ptr input_; storage::PropertyId property_; PropertyLookup *lhs_; std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; object->property_ = property_; object->lhs_ = lhs_ ? lhs_->Clone(storage) : nullptr; return object; } private: class RemovePropertyCursor : public Cursor { public: RemovePropertyCursor(const RemoveProperty &, utils::MemoryResource *); bool Pull(Frame &, ExecutionContext &) override; void Shutdown() override; void Reset() override; private: const RemoveProperty &self_; const UniqueCursorPtr input_cursor_; }; }; /// Logical operator for removing an arbitrary number of labels on a Vertex. /// /// If a label does not exist on a Vertex, nothing happens. class RemoveLabels : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } RemoveLabels() {} RemoveLabels(const std::shared_ptr &input, Symbol input_symbol, const std::vector &labels); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = input; } std::shared_ptr input_; Symbol input_symbol_; std::vector labels_; std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; object->input_symbol_ = input_symbol_; object->labels_ = labels_; return object; } private: class RemoveLabelsCursor : public Cursor { public: RemoveLabelsCursor(const RemoveLabels &, utils::MemoryResource *); bool Pull(Frame &, ExecutionContext &) override; void Shutdown() override; void Reset() override; private: const RemoveLabels &self_; const UniqueCursorPtr input_cursor_; }; }; /// Filter whose Pull returns true only when the given expand_symbol frame /// value (the latest expansion) is not equal to any of the previous_symbols frame /// values. /// /// Used for implementing Cyphermorphism. /// Isomorphism is vertex-uniqueness. It means that two different vertices in a /// pattern can not map to the same data vertex. /// Cyphermorphism is edge-uniqueness (the above explanation applies). By default /// Neo4j uses Cyphermorphism (that's where the name stems from, it is not a valid /// graph-theory term). /// /// Supports variable-length-edges (uniqueness comparisons between edges and an /// edge lists). class EdgeUniquenessFilter : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } EdgeUniquenessFilter() {} EdgeUniquenessFilter(const std::shared_ptr &input, Symbol expand_symbol, const std::vector &previous_symbols); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = input; } std::string ToString() const override { return fmt::format("EdgeUniquenessFilter {{{0} : {1}}}", utils::IterableToString(previous_symbols_, ", ", [](const auto &sym) { return sym.name(); }), expand_symbol_.name()); } std::shared_ptr input_; Symbol expand_symbol_; std::vector previous_symbols_; std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; object->expand_symbol_ = expand_symbol_; object->previous_symbols_ = previous_symbols_; return object; } private: class EdgeUniquenessFilterCursor : public Cursor { public: EdgeUniquenessFilterCursor(const EdgeUniquenessFilter &, utils::MemoryResource *); bool Pull(Frame &, ExecutionContext &) override; void Shutdown() override; void Reset() override; private: const EdgeUniquenessFilter &self_; const UniqueCursorPtr input_cursor_; }; }; /// Pulls everything from the input and discards it. /// /// On the first Pull from this operator's Cursor the input Cursor will be Pulled /// until it is empty. The results won't be accumulated in the temporary cache. /// /// This technique is used for ensuring that the cursor has been exhausted after /// a WriteHandleClause. A typical use case is a `MATCH--SET` query with RETURN statement /// missing. /// @param input Input @c LogicalOperator. class EmptyResult : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } EmptyResult() {} EmptyResult(const std::shared_ptr &input); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector OutputSymbols(const SymbolTable &) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = input; } std::shared_ptr input_; std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; return object; } }; /// Pulls everything from the input before passing it through. /// Optionally advances the command after accumulation and before emitting. /// /// On the first Pull from this operator's Cursor the input Cursor will be Pulled /// until it is empty. The results will be accumulated in the temporary cache. Once /// the input Cursor is empty, this operator's Cursor will start returning cached /// stuff from its Pull. /// /// This technique is used for ensuring all the operations from the /// previous logical operator have been performed before exposing data /// to the next. A typical use case is a `MATCH--SET--RETURN` /// query in which every SET iteration must be performed before /// RETURN starts iterating (see Memgraph Wiki for detailed reasoning). /// /// IMPORTANT: This operator does not cache all the results but only those /// elements from the frame whose symbols (frame positions) it was given. /// All other frame positions will contain undefined junk after this /// operator has executed, and should not be used. /// /// This operator can also advance the command after the accumulation and /// before emitting. If the command gets advanced, every value that /// has been cached will be reconstructed before Pull returns. /// /// @param input Input @c LogicalOperator. /// @param symbols A vector of Symbols that need to be accumulated /// and exposed to the next op. class Accumulate : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } Accumulate() {} Accumulate(const std::shared_ptr &input, const std::vector &symbols, bool advance_command = false); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = input; } std::shared_ptr input_; std::vector symbols_; bool advance_command_; std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; object->symbols_ = symbols_; object->advance_command_ = advance_command_; return object; } }; /// Performs an arbitrary number of aggregations of data /// from the given input grouped by the given criteria. /// /// Aggregations are defined by triples that define /// (input data expression, type of aggregation, output symbol). /// Input data is grouped based on the given set of named /// expressions. Grouping is done on unique values. /// /// IMPORTANT: /// Operators taking their input from an aggregation are only /// allowed to use frame values that are either aggregation /// outputs or group-by named-expressions. All other frame /// elements are in an undefined state after aggregation. class Aggregate : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } /// An aggregation element, contains: /// (input data expression, key expression - only used in COLLECT_MAP, type of /// aggregation, output symbol). struct Element { static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const { return kType; } Expression *value; Expression *key; Aggregation::Op op; Symbol output_sym; bool distinct{false}; Element Clone(AstStorage *storage) const { Element object; object.value = value ? value->Clone(storage) : nullptr; object.key = key ? key->Clone(storage) : nullptr; object.op = op; object.output_sym = output_sym; object.distinct = distinct; return object; } }; Aggregate() = default; Aggregate(const std::shared_ptr &input, const std::vector &aggregations, const std::vector &group_by, const std::vector &remember); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = input; } std::shared_ptr input_; std::vector aggregations_; 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; object->aggregations_.resize(aggregations_.size()); for (auto i4 = 0; i4 < aggregations_.size(); ++i4) { object->aggregations_[i4] = aggregations_[i4].Clone(storage); } object->group_by_.resize(group_by_.size()); for (auto i5 = 0; i5 < group_by_.size(); ++i5) { object->group_by_[i5] = group_by_[i5] ? group_by_[i5]->Clone(storage) : nullptr; } object->remember_ = remember_; return object; } }; /// Skips a number of Pulls from the input op. /// /// The given expression determines how many Pulls from the input /// should be skipped (ignored). /// All other successful Pulls from the /// input are simply passed through. /// /// The given expression is evaluated after the first Pull from /// the input, and only once. Neo does not allow this expression /// to contain identifiers, and neither does Memgraph, but this /// operator's implementation does not expect this. class Skip : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } Skip() {} Skip(const std::shared_ptr &input, Expression *expression); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector OutputSymbols(const SymbolTable &) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = input; } std::shared_ptr input_; Expression *expression_; std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; object->expression_ = expression_ ? expression_->Clone(storage) : nullptr; return object; } private: class SkipCursor : public Cursor { public: SkipCursor(const Skip &, utils::MemoryResource *); bool Pull(Frame &, ExecutionContext &) override; void Shutdown() override; void Reset() override; private: const Skip &self_; const UniqueCursorPtr input_cursor_; // init to_skip_ to -1, indicating // that it's still unknown (input has not been Pulled yet) int64_t to_skip_{-1}; int64_t skipped_{0}; }; }; /// Applies the pattern filter by putting the value of the input cursor to the frame. class EvaluatePatternFilter : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } EvaluatePatternFilter() {} EvaluatePatternFilter(const std::shared_ptr &input, Symbol output_symbol); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = input; } std::shared_ptr input_; Symbol output_symbol_; std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; object->output_symbol_ = output_symbol_; return object; } private: class EvaluatePatternFilterCursor : public Cursor { public: EvaluatePatternFilterCursor(const EvaluatePatternFilter &, utils::MemoryResource *); bool Pull(Frame &, ExecutionContext &) override; void Shutdown() override; void Reset() override; private: const EvaluatePatternFilter &self_; UniqueCursorPtr input_cursor_; }; }; /// Limits the number of Pulls from the input op. /// /// The given expression determines how many /// input Pulls should be passed through. The input is not /// Pulled once this limit is reached. Note that this has /// implications: the out-of-bounds input Pulls are never /// evaluated. /// /// The limit expression must NOT use anything from the /// Frame. It is evaluated before the first Pull from the /// input. This is consistent with Neo (they don't allow /// identifiers in limit expressions), and it's necessary /// when limit evaluates to 0 (because 0 Pulls from the /// input should be performed). class Limit : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } Limit() {} Limit(const std::shared_ptr &input, Expression *expression); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector OutputSymbols(const SymbolTable &) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = input; } std::shared_ptr input_; Expression *expression_; std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; object->expression_ = expression_ ? expression_->Clone(storage) : nullptr; return object; } private: class LimitCursor : public Cursor { public: LimitCursor(const Limit &, utils::MemoryResource *); bool Pull(Frame &, ExecutionContext &) override; void Shutdown() override; void Reset() override; private: const Limit &self_; UniqueCursorPtr input_cursor_; // init limit_ to -1, indicating // that it's still unknown (Cursor has not been Pulled yet) int64_t limit_{-1}; int64_t pulled_{0}; }; }; /// Logical operator for ordering (sorting) results. /// /// Sorts the input rows based on an arbitrary number of /// Expressions. Ascending or descending ordering can be chosen /// for each independently (not providing enough orderings /// results in a runtime error). /// /// For each row an arbitrary number of Frame elements can be /// remembered. Only these elements (defined by their Symbols) /// are valid for usage after the OrderBy operator. class OrderBy : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } OrderBy() {} OrderBy(const std::shared_ptr &input, const std::vector &order_by, const std::vector &output_symbols); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector OutputSymbols(const SymbolTable &) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = input; } std::shared_ptr input_; TypedValueVectorCompare compare_; 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; object->compare_ = compare_; object->order_by_.resize(order_by_.size()); for (auto i6 = 0; i6 < order_by_.size(); ++i6) { object->order_by_[i6] = order_by_[i6] ? order_by_[i6]->Clone(storage) : nullptr; } object->output_symbols_ = output_symbols_; return object; } }; /// Merge operator. For every sucessful Pull from the /// input operator a Pull from the merge_match is attempted. All /// successfull Pulls from the merge_match are passed on as output. /// If merge_match Pull does not yield any elements, a single Pull /// from the merge_create op is performed. /// /// The input logical op is optional. If false (nullptr) /// it will be replaced by a Once op. /// /// For an argumentation of this implementation see the wiki /// documentation. class Merge : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } Merge() {} Merge(const std::shared_ptr &input, const std::shared_ptr &merge_match, const std::shared_ptr &merge_create); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; // TODO: Consider whether we want to treat Merge as having single input. It // makes sense that we do, because other branches are executed depending on // the input. bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = input; } std::shared_ptr input_; std::shared_ptr merge_match_; std::shared_ptr merge_create_; std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; object->merge_match_ = merge_match_ ? merge_match_->Clone(storage) : nullptr; object->merge_create_ = merge_create_ ? merge_create_->Clone(storage) : nullptr; return object; } private: class MergeCursor : public Cursor { public: MergeCursor(const Merge &, utils::MemoryResource *); bool Pull(Frame &, ExecutionContext &) override; void Shutdown() override; void Reset() override; private: const UniqueCursorPtr input_cursor_; const UniqueCursorPtr merge_match_cursor_; const UniqueCursorPtr merge_create_cursor_; // indicates if the next Pull from this cursor // should perform a pull from input_cursor_ // this is true when: // - first Pulling from this cursor // - previous Pull from this cursor exhausted the merge_match_cursor bool pull_input_{true}; }; }; /// Optional operator. Used for optional match. For every /// successful Pull from the input branch a Pull from the optional /// branch is attempted (and Pulled from till exhausted). If zero /// Pulls succeed from the optional branch, the Optional operator /// sets the optional symbols to TypedValue::Null on the Frame /// and returns true, once. class Optional : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } Optional() {} Optional(const std::shared_ptr &input, const std::shared_ptr &optional, const std::vector &optional_symbols); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = input; } std::shared_ptr input_; std::shared_ptr optional_; std::vector optional_symbols_; std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; object->optional_ = optional_ ? optional_->Clone(storage) : nullptr; object->optional_symbols_ = optional_symbols_; return object; } private: class OptionalCursor : public Cursor { public: OptionalCursor(const Optional &, utils::MemoryResource *); bool Pull(Frame &, ExecutionContext &) override; void Shutdown() override; void Reset() override; private: const Optional &self_; const UniqueCursorPtr input_cursor_; const UniqueCursorPtr optional_cursor_; // indicates if the next Pull from this cursor should // perform a Pull from the input_cursor_ // this is true when: // - first pulling from this Cursor // - previous Pull from this cursor exhausted the optional_cursor_ bool pull_input_{true}; }; }; /// Takes a list TypedValue as it's input and yields each /// element as it's output. /// /// Input is optional (unwind can be the first clause in a query). class Unwind : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } Unwind() {} Unwind(const std::shared_ptr &input, Expression *input_expression_, Symbol output_symbol); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = input; } std::shared_ptr input_; Expression *input_expression_; Symbol output_symbol_; std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; object->input_expression_ = input_expression_ ? input_expression_->Clone(storage) : nullptr; object->output_symbol_ = output_symbol_; return object; } }; /// Ensures that only distinct rows are yielded. /// This implementation accepts a vector of Symbols /// which define a row. Only those Symbols are valid /// for use in operators following Distinct. /// /// This implementation maintains input ordering. class Distinct : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } Distinct() {} Distinct(const std::shared_ptr &input, const std::vector &value_symbols); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector OutputSymbols(const SymbolTable &) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = input; } std::shared_ptr input_; std::vector value_symbols_; std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; object->value_symbols_ = value_symbols_; return object; } }; /// A logical operator that applies UNION operator on inputs and places the /// result on the frame. /// /// This operator takes two inputs, a vector of symbols for the result, and vectors /// of symbols used by each of the inputs. class Union : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } Union() {} Union(const std::shared_ptr &left_op, const std::shared_ptr &right_op, const std::vector &union_symbols, const std::vector &left_symbols, const std::vector &right_symbols); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector OutputSymbols(const SymbolTable &) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override; std::shared_ptr input() const override; void set_input(std::shared_ptr) override; std::shared_ptr left_op_; std::shared_ptr right_op_; std::vector union_symbols_; 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; object->right_op_ = right_op_ ? right_op_->Clone(storage) : nullptr; object->union_symbols_ = union_symbols_; object->left_symbols_ = left_symbols_; object->right_symbols_ = right_symbols_; return object; } private: class UnionCursor : public Cursor { public: UnionCursor(const Union &, utils::MemoryResource *); bool Pull(Frame &, ExecutionContext &) override; void Shutdown() override; void Reset() override; private: const Union &self_; const UniqueCursorPtr left_cursor_, right_cursor_; }; }; /// Operator for producing a Cartesian product from 2 input branches class Cartesian : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } Cartesian() {} /** Construct the operator with left input branch and right input branch. */ Cartesian(const std::shared_ptr &left_op, const std::vector &left_symbols, const std::shared_ptr &right_op, const std::vector &right_symbols) : left_op_(left_op), left_symbols_(left_symbols), right_op_(right_op), right_symbols_(right_symbols) {} bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override; std::shared_ptr input() const override; void set_input(std::shared_ptr) override; std::shared_ptr left_op_; std::vector left_symbols_; std::shared_ptr right_op_; std::vector right_symbols_; std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->left_op_ = left_op_ ? left_op_->Clone(storage) : nullptr; object->left_symbols_ = left_symbols_; object->right_op_ = right_op_ ? right_op_->Clone(storage) : nullptr; object->right_symbols_ = right_symbols_; return object; } }; /// An operator that outputs a table, producing a single row on each pull class OutputTable : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } OutputTable() {} OutputTable(std::vector output_symbols, std::function>(Frame *, ExecutionContext *)> callback); OutputTable(std::vector output_symbols, std::vector> rows); bool Accept(HierarchicalLogicalOperatorVisitor &) override { LOG_FATAL("OutputTable operator should not be visited!"); } UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector OutputSymbols(const SymbolTable &) const override { return output_symbols_; } std::vector ModifiedSymbols(const SymbolTable &) const override { return output_symbols_; } bool HasSingleInput() const override; std::shared_ptr input() const override; void set_input(std::shared_ptr input) override; std::vector output_symbols_; std::function>(Frame *, ExecutionContext *)> callback_; std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->output_symbols_ = output_symbols_; object->callback_ = callback_; return object; } }; /// An operator that outputs a table, producing a single row on each pull. /// This class is different from @c OutputTable in that its callback doesn't fetch all rows /// at once. Instead, each call of the callback should return a single row of the table. class OutputTableStream : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } OutputTableStream() {} OutputTableStream(std::vector output_symbols, std::function>(Frame *, ExecutionContext *)> callback); bool Accept(HierarchicalLogicalOperatorVisitor &) override { LOG_FATAL("OutputTableStream operator should not be visited!"); } UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector OutputSymbols(const SymbolTable &) const override { return output_symbols_; } std::vector ModifiedSymbols(const SymbolTable &) const override { return output_symbols_; } bool HasSingleInput() const override; std::shared_ptr input() const override; void set_input(std::shared_ptr input) override; std::vector output_symbols_; std::function>(Frame *, ExecutionContext *)> callback_; std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->output_symbols_ = output_symbols_; object->callback_ = callback_; return object; } }; class CallProcedure : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } CallProcedure() = default; CallProcedure(std::shared_ptr input, std::string name, std::vector arguments, std::vector fields, std::vector symbols, Expression *memory_limit, size_t memory_scale, bool is_write, int64_t procedure_id, bool void_procedure = false); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector OutputSymbols(const SymbolTable &) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = input; } static void IncrementCounter(const std::string &procedure_name); static std::unordered_map GetAndResetCounters(); std::shared_ptr input_; std::string procedure_name_; std::vector arguments_; std::vector result_fields_; std::vector result_symbols_; Expression *memory_limit_{nullptr}; size_t memory_scale_{1024U}; bool is_write_; int64_t procedure_id_; bool void_procedure_; 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; object->procedure_name_ = procedure_name_; object->arguments_.resize(arguments_.size()); for (auto i7 = 0; i7 < arguments_.size(); ++i7) { object->arguments_[i7] = arguments_[i7] ? arguments_[i7]->Clone(storage) : nullptr; } object->result_fields_ = result_fields_; object->result_symbols_ = result_symbols_; object->memory_limit_ = memory_limit_ ? memory_limit_->Clone(storage) : nullptr; object->memory_scale_ = memory_scale_; object->is_write_ = is_write_; object->procedure_id_ = procedure_id_; object->void_procedure_ = void_procedure_; return object; } private: inline static utils::Synchronized, utils::SpinLock> procedure_counters_; }; class LoadCsv : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } LoadCsv() = default; LoadCsv(std::shared_ptr input, Expression *file, bool with_header, bool ignore_bad, Expression *delimiter, Expression *quote, Expression *nullif, Symbol row_var); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector OutputSymbols(const SymbolTable &) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = input; } std::shared_ptr input_; Expression *file_; bool with_header_; bool ignore_bad_; Expression *delimiter_{nullptr}; Expression *quote_{nullptr}; 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; object->file_ = file_ ? file_->Clone(storage) : nullptr; object->with_header_ = with_header_; object->ignore_bad_ = ignore_bad_; object->delimiter_ = delimiter_ ? delimiter_->Clone(storage) : nullptr; object->quote_ = quote_ ? quote_->Clone(storage) : nullptr; object->nullif_ = nullif_; object->row_var_ = row_var_; return object; } }; /// Iterates over a collection of elements and applies one or more update /// clauses. /// class Foreach : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } Foreach() = default; Foreach(std::shared_ptr input, std::shared_ptr updates, Expression *named_expr, Symbol loop_variable_symbol); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = std::move(input); } std::shared_ptr input_; std::shared_ptr update_clauses_; Expression *expression_; Symbol loop_variable_symbol_; std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; object->update_clauses_ = update_clauses_ ? update_clauses_->Clone(storage) : nullptr; object->expression_ = expression_ ? expression_->Clone(storage) : nullptr; object->loop_variable_symbol_ = loop_variable_symbol_; return object; } }; /// Applies symbols from both output branches. class Apply : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } Apply() {} Apply(const std::shared_ptr input, const std::shared_ptr subquery, bool subquery_has_return); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override { return true; } std::shared_ptr input() const override { return input_; } void set_input(std::shared_ptr input) override { input_ = input; } std::shared_ptr input_; std::shared_ptr subquery_; bool subquery_has_return_; std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->input_ = input_ ? input_->Clone(storage) : nullptr; object->subquery_ = subquery_ ? subquery_->Clone(storage) : nullptr; object->subquery_has_return_ = subquery_has_return_; return object; } private: class ApplyCursor : public Cursor { public: ApplyCursor(const Apply &, utils::MemoryResource *); bool Pull(Frame &, ExecutionContext &) override; void Shutdown() override; void Reset() override; private: const Apply &self_; UniqueCursorPtr input_; UniqueCursorPtr subquery_; bool pull_input_{true}; bool subquery_has_return_{true}; }; }; /// Applies symbols from both join branches class IndexedJoin : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } IndexedJoin() {} IndexedJoin(std::shared_ptr main_branch, std::shared_ptr sub_branch); bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource * /*unused*/) const override; std::vector ModifiedSymbols(const SymbolTable & /*unused*/) const override; bool HasSingleInput() const override; std::shared_ptr input() const override; void set_input(std::shared_ptr /*unused*/) override; std::shared_ptr main_branch_; std::shared_ptr sub_branch_; std::unique_ptr Clone(AstStorage *storage) const override { auto object = std::make_unique(); object->main_branch_ = main_branch_ ? main_branch_->Clone(storage) : nullptr; object->sub_branch_ = sub_branch_ ? sub_branch_->Clone(storage) : nullptr; return object; } private: class IndexedJoinCursor : public Cursor { public: IndexedJoinCursor(const IndexedJoin &, utils::MemoryResource *); bool Pull(Frame & /*unused*/, ExecutionContext & /*unused*/) override; void Shutdown() override; void Reset() override; private: const IndexedJoin &self_; UniqueCursorPtr main_branch_; UniqueCursorPtr sub_branch_; bool pull_input_{true}; }; }; /// Operator for producing the hash join of two input branches class HashJoin : public memgraph::query::plan::LogicalOperator { public: static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } HashJoin() {} /** Construct the operator with left input branch and right input branch. */ HashJoin(const std::shared_ptr &left_op, const std::vector &left_symbols, const std::shared_ptr &right_op, const std::vector &right_symbols, EqualOperator *hash_join_condition) : left_op_(left_op), left_symbols_(left_symbols), right_op_(right_op), right_symbols_(right_symbols), hash_join_condition_(hash_join_condition) {} bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; bool HasSingleInput() const override; std::shared_ptr input() const override; void set_input(std::shared_ptr) override; std::shared_ptr left_op_; std::vector left_symbols_; std::shared_ptr right_op_; std::vector right_symbols_; EqualOperator *hash_join_condition_; std::string ToString() const override { return fmt::format("HashJoin {{{} : {}}}", 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; object->left_symbols_ = left_symbols_; object->right_op_ = right_op_ ? right_op_->Clone(storage) : nullptr; object->right_symbols_ = right_symbols_; object->hash_join_condition_ = hash_join_condition_ ? hash_join_condition_->Clone(storage) : nullptr; return object; } }; } // namespace plan } // namespace query } // namespace memgraph