2636 lines
105 KiB
C++
2636 lines
105 KiB
C++
// 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 <memory>
|
|
#include <optional>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <utility>
|
|
#include <variant>
|
|
#include <vector>
|
|
|
|
#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<Cursor, std::function<void(Cursor *)>>;
|
|
|
|
template <class TCursor, class... TArgs>
|
|
std::unique_ptr<Cursor, std::function<void(Cursor *)>> MakeUniqueCursorPtr(utils::Allocator<TCursor> allocator,
|
|
TArgs &&...args) {
|
|
auto *ptr = allocator.allocate(1);
|
|
try {
|
|
auto *cursor = new (ptr) TCursor(std::forward<TArgs>(args)...);
|
|
return std::unique_ptr<Cursor, std::function<void(Cursor *)>>(cursor, [allocator](Cursor *base_ptr) mutable {
|
|
auto *p = static_cast<TCursor *>(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<Once, CreateNode, CreateExpand, ScanAll, ScanAllByLabel, ScanAllByLabelPropertyRange,
|
|
ScanAllByLabelPropertyValue, ScanAllByLabelProperty, ScanAllById, Expand, ExpandVariable,
|
|
ConstructNamedPath, Filter, Produce, Delete, SetProperty, SetProperties, SetLabels,
|
|
RemoveProperty, RemoveLabels, EdgeUniquenessFilter, Accumulate, Aggregate, Skip, Limit,
|
|
OrderBy, Merge, Optional, Unwind, Distinct, Union, Cartesian, CallProcedure, LoadCsv,
|
|
Foreach, EmptyResult, EvaluatePatternFilter, Apply, IndexedJoin, HashJoin>;
|
|
|
|
using LogicalOperatorLeafVisitor = utils::LeafVisitor<Once>;
|
|
|
|
/**
|
|
* @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<HierarchicalLogicalOperatorVisitor>,
|
|
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<Symbol> used for results.
|
|
*/
|
|
virtual std::vector<Symbol> OutputSymbols(const SymbolTable &) const { return std::vector<Symbol>(); }
|
|
|
|
/**
|
|
* 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<Symbol> 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<LogicalOperator> 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<LogicalOperator>) = 0;
|
|
|
|
struct SaveHelper {
|
|
std::vector<LogicalOperator *> saved_ops;
|
|
};
|
|
|
|
struct LoadHelper {
|
|
AstStorage ast_storage;
|
|
std::vector<std::pair<uint64_t, std::shared_ptr<LogicalOperator>>> loaded_ops;
|
|
};
|
|
|
|
struct SlkLoadHelper {
|
|
AstStorage ast_storage;
|
|
std::vector<std::shared_ptr<LogicalOperator>> loaded_ops;
|
|
};
|
|
|
|
std::string ToString() const override { return GetTypeInfo().name; }
|
|
|
|
virtual std::unique_ptr<LogicalOperator> 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<Symbol> symbols = {}) : symbols_{std::move(symbols)} {}
|
|
DEFVISITABLE(HierarchicalLogicalOperatorVisitor);
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override { return symbols_; }
|
|
|
|
bool HasSingleInput() const override;
|
|
std::shared_ptr<LogicalOperator> input() const override;
|
|
void set_input(std::shared_ptr<LogicalOperator>) override;
|
|
|
|
std::vector<Symbol> symbols_;
|
|
|
|
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<Once>();
|
|
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<std::pair<storage::PropertyId, Expression *>>;
|
|
|
|
struct NodeCreationInfo {
|
|
static const utils::TypeInfo kType;
|
|
const utils::TypeInfo &GetTypeInfo() const { return kType; }
|
|
|
|
NodeCreationInfo() = default;
|
|
|
|
NodeCreationInfo(Symbol symbol, std::vector<storage::LabelId> labels,
|
|
std::variant<PropertiesMapList, ParameterLookup *> properties)
|
|
: symbol{std::move(symbol)}, labels{std::move(labels)}, properties{std::move(properties)} {};
|
|
|
|
NodeCreationInfo(Symbol symbol, std::vector<storage::LabelId> labels, PropertiesMapList properties)
|
|
: symbol{std::move(symbol)}, labels{std::move(labels)}, properties{std::move(properties)} {};
|
|
|
|
NodeCreationInfo(Symbol symbol, std::vector<storage::LabelId> labels, ParameterLookup *properties)
|
|
: symbol{std::move(symbol)}, labels{std::move(labels)}, properties{properties} {};
|
|
|
|
Symbol symbol;
|
|
std::vector<storage::LabelId> labels;
|
|
std::variant<PropertiesMapList, ParameterLookup *> properties;
|
|
|
|
NodeCreationInfo Clone(AstStorage *storage) const {
|
|
NodeCreationInfo object;
|
|
object.symbol = symbol;
|
|
object.labels = labels;
|
|
if (const auto *props = std::get_if<PropertiesMapList>(&properties)) {
|
|
auto &destination_props = std::get<PropertiesMapList>(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<ParameterLookup *>(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<LogicalOperator> &input, const NodeCreationInfo &node_info);
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override { return true; }
|
|
std::shared_ptr<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; }
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> input_;
|
|
memgraph::query::plan::NodeCreationInfo node_info_;
|
|
|
|
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<CreateNode>();
|
|
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<PropertiesMapList, ParameterLookup *> 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<PropertiesMapList, ParameterLookup *> 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<PropertiesMapList>(&properties)) {
|
|
auto &destination_props = std::get<PropertiesMapList>(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<ParameterLookup *>(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<LogicalOperator> &input, Symbol input_symbol, bool existing_node);
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override { return true; }
|
|
std::shared_ptr<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; }
|
|
|
|
memgraph::query::plan::NodeCreationInfo node_info_;
|
|
memgraph::query::plan::EdgeCreationInfo edge_info_;
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> 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<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<CreateExpand>();
|
|
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<LogicalOperator> &input, Symbol output_symbol, storage::View view = storage::View::OLD);
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override { return true; }
|
|
std::shared_ptr<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; }
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> 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<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<ScanAll>();
|
|
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<LogicalOperator> &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<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<ScanAllByLabel>();
|
|
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<Expression *>;
|
|
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<LogicalOperator> &input, Symbol output_symbol,
|
|
storage::LabelId label, storage::PropertyId property, const std::string &property_name,
|
|
std::optional<Bound> lower_bound, std::optional<Bound> 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<Bound> lower_bound_;
|
|
std::optional<Bound> 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<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<ScanAllByLabelPropertyRange>();
|
|
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<Expression *>(lower_bound_->value()->Clone(storage), lower_bound_->type()));
|
|
} else {
|
|
object->lower_bound_ = std::nullopt;
|
|
}
|
|
if (upper_bound_) {
|
|
object->upper_bound_.emplace(
|
|
utils::Bound<Expression *>(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<LogicalOperator> &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<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<ScanAllByLabelPropertyValue>();
|
|
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<LogicalOperator> &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<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<ScanAllByLabelProperty>();
|
|
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<LogicalOperator> &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<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<ScanAllById>();
|
|
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<storage::EdgeTypeId> 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<VertexAccessor> input_node;
|
|
EdgeAtom::Direction direction;
|
|
std::optional<VertexAccessor> 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<LogicalOperator> &input, Symbol input_symbol, Symbol node_symbol, Symbol edge_symbol,
|
|
EdgeAtom::Direction direction, const std::vector<storage::EdgeTypeId> &edge_types, bool existing_node,
|
|
storage::View view);
|
|
|
|
Expand() {}
|
|
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override { return true; }
|
|
std::shared_ptr<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; }
|
|
|
|
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<EdgeAccessor>;
|
|
using InEdgeIteratorT = decltype(std::declval<InEdgeT>().begin());
|
|
using OutEdgeT = std::vector<EdgeAccessor>;
|
|
using OutEdgeIteratorT = decltype(std::declval<OutEdgeT>().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<InEdgeT> in_edges_;
|
|
std::optional<InEdgeIteratorT> in_edges_it_;
|
|
std::optional<OutEdgeT> out_edges_;
|
|
std::optional<OutEdgeIteratorT> 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<memgraph::query::plan::LogicalOperator> 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<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<Expand>();
|
|
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<LogicalOperator> &input, Symbol input_symbol, Symbol node_symbol,
|
|
Symbol edge_symbol, EdgeAtom::Type type, EdgeAtom::Direction direction,
|
|
const std::vector<storage::EdgeTypeId> &edge_types, bool is_reverse, Expression *lower_bound,
|
|
Expression *upper_bound, bool existing_node, ExpansionLambda filter_lambda,
|
|
std::optional<ExpansionLambda> weight_lambda, std::optional<Symbol> total_weight);
|
|
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override { return true; }
|
|
std::shared_ptr<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; }
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> 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<memgraph::query::plan::ExpansionLambda> weight_lambda_;
|
|
std::optional<Symbol> 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<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<ExpandVariable>();
|
|
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<LogicalOperator> &input, Symbol path_symbol,
|
|
const std::vector<Symbol> &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<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override { return true; }
|
|
std::shared_ptr<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; }
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> input_;
|
|
Symbol path_symbol_;
|
|
std::vector<Symbol> path_elements_;
|
|
|
|
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<ConstructNamedPath>();
|
|
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<LogicalOperator> &input,
|
|
const std::vector<std::shared_ptr<LogicalOperator>> &pattern_filters, Expression *expression);
|
|
Filter(const std::shared_ptr<LogicalOperator> &input,
|
|
const std::vector<std::shared_ptr<LogicalOperator>> &pattern_filters, Expression *expression,
|
|
const Filters &all_filters);
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override { return true; }
|
|
std::shared_ptr<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; }
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> input_;
|
|
std::vector<std::shared_ptr<memgraph::query::plan::LogicalOperator>> 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<std::string> 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<LabelsTest *>(single_filter.expression);
|
|
std::set<std::string> 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<Identifier *>(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<std::string> 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<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<Filter>();
|
|
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<UniqueCursorPtr> 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<LogicalOperator> &input, const std::vector<NamedExpression *> &named_expressions);
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> OutputSymbols(const SymbolTable &) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override { return true; }
|
|
std::shared_ptr<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; }
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> input_;
|
|
std::vector<NamedExpression *> named_expressions_;
|
|
|
|
std::string ToString() const override {
|
|
return fmt::format("Produce {{{}}}", utils::IterableToString(named_expressions_, ", ",
|
|
[](const auto &nexpr) { return nexpr->name_; }));
|
|
}
|
|
|
|
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<Produce>();
|
|
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<VertexAccessor> nodes{};
|
|
std::vector<EdgeAccessor> 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<LogicalOperator> &input_, const std::vector<Expression *> &expressions, bool detach_);
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override { return true; }
|
|
std::shared_ptr<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; }
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> input_;
|
|
std::vector<Expression *> 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<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<Delete>();
|
|
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<LogicalOperator> &input, storage::PropertyId property, PropertyLookup *lhs,
|
|
Expression *rhs);
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override { return true; }
|
|
std::shared_ptr<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; }
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> input_;
|
|
storage::PropertyId property_;
|
|
PropertyLookup *lhs_;
|
|
Expression *rhs_;
|
|
|
|
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<SetProperty>();
|
|
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<LogicalOperator> &input, Symbol input_symbol, Expression *rhs, Op op);
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override { return true; }
|
|
std::shared_ptr<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; }
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> input_;
|
|
Symbol input_symbol_;
|
|
Expression *rhs_;
|
|
memgraph::query::plan::SetProperties::Op op_;
|
|
|
|
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<SetProperties>();
|
|
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<std::string, storage::PropertyId> 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<LogicalOperator> &input, Symbol input_symbol,
|
|
const std::vector<storage::LabelId> &labels);
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override { return true; }
|
|
std::shared_ptr<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; }
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> input_;
|
|
Symbol input_symbol_;
|
|
std::vector<storage::LabelId> labels_;
|
|
|
|
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<SetLabels>();
|
|
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<LogicalOperator> &input, storage::PropertyId property, PropertyLookup *lhs);
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override { return true; }
|
|
std::shared_ptr<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; }
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> input_;
|
|
storage::PropertyId property_;
|
|
PropertyLookup *lhs_;
|
|
|
|
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<RemoveProperty>();
|
|
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<LogicalOperator> &input, Symbol input_symbol,
|
|
const std::vector<storage::LabelId> &labels);
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override { return true; }
|
|
std::shared_ptr<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; }
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> input_;
|
|
Symbol input_symbol_;
|
|
std::vector<storage::LabelId> labels_;
|
|
|
|
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<RemoveLabels>();
|
|
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<LogicalOperator> &input, Symbol expand_symbol,
|
|
const std::vector<Symbol> &previous_symbols);
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override { return true; }
|
|
std::shared_ptr<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; }
|
|
|
|
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<memgraph::query::plan::LogicalOperator> input_;
|
|
Symbol expand_symbol_;
|
|
std::vector<Symbol> previous_symbols_;
|
|
|
|
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<EdgeUniquenessFilter>();
|
|
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<LogicalOperator> &input);
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> OutputSymbols(const SymbolTable &) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override { return true; }
|
|
std::shared_ptr<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; }
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> input_;
|
|
|
|
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<EmptyResult>();
|
|
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<LogicalOperator> &input, const std::vector<Symbol> &symbols,
|
|
bool advance_command = false);
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override { return true; }
|
|
std::shared_ptr<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; }
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> input_;
|
|
std::vector<Symbol> symbols_;
|
|
bool advance_command_;
|
|
|
|
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<Accumulate>();
|
|
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<LogicalOperator> &input, const std::vector<Element> &aggregations,
|
|
const std::vector<Expression *> &group_by, const std::vector<Symbol> &remember);
|
|
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override { return true; }
|
|
std::shared_ptr<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; }
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> input_;
|
|
std::vector<memgraph::query::plan::Aggregate::Element> aggregations_;
|
|
std::vector<Expression *> group_by_;
|
|
std::vector<Symbol> 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<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<Aggregate>();
|
|
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<LogicalOperator> &input, Expression *expression);
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> OutputSymbols(const SymbolTable &) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override { return true; }
|
|
std::shared_ptr<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; }
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> input_;
|
|
Expression *expression_;
|
|
|
|
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<Skip>();
|
|
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<LogicalOperator> &input, Symbol output_symbol);
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override { return true; }
|
|
std::shared_ptr<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; }
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> input_;
|
|
Symbol output_symbol_;
|
|
|
|
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<EvaluatePatternFilter>();
|
|
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<LogicalOperator> &input, Expression *expression);
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> OutputSymbols(const SymbolTable &) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override { return true; }
|
|
std::shared_ptr<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; }
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> input_;
|
|
Expression *expression_;
|
|
|
|
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<Limit>();
|
|
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<LogicalOperator> &input, const std::vector<SortItem> &order_by,
|
|
const std::vector<Symbol> &output_symbols);
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> OutputSymbols(const SymbolTable &) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override { return true; }
|
|
std::shared_ptr<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; }
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> input_;
|
|
TypedValueVectorCompare compare_;
|
|
std::vector<Expression *> order_by_;
|
|
std::vector<Symbol> output_symbols_;
|
|
|
|
std::string ToString() const override {
|
|
return fmt::format("OrderBy {{{}}}",
|
|
utils::IterableToString(output_symbols_, ", ", [](const auto &sym) { return sym.name(); }));
|
|
}
|
|
|
|
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<OrderBy>();
|
|
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<LogicalOperator> &input, const std::shared_ptr<LogicalOperator> &merge_match,
|
|
const std::shared_ptr<LogicalOperator> &merge_create);
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> 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<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; }
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> input_;
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> merge_match_;
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> merge_create_;
|
|
|
|
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<Merge>();
|
|
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<LogicalOperator> &input, const std::shared_ptr<LogicalOperator> &optional,
|
|
const std::vector<Symbol> &optional_symbols);
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override { return true; }
|
|
std::shared_ptr<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; }
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> input_;
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> optional_;
|
|
std::vector<Symbol> optional_symbols_;
|
|
|
|
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<Optional>();
|
|
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<LogicalOperator> &input, Expression *input_expression_, Symbol output_symbol);
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override { return true; }
|
|
std::shared_ptr<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; }
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> input_;
|
|
Expression *input_expression_;
|
|
Symbol output_symbol_;
|
|
|
|
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<Unwind>();
|
|
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<LogicalOperator> &input, const std::vector<Symbol> &value_symbols);
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> OutputSymbols(const SymbolTable &) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override { return true; }
|
|
std::shared_ptr<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; }
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> input_;
|
|
std::vector<Symbol> value_symbols_;
|
|
|
|
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<Distinct>();
|
|
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<LogicalOperator> &left_op, const std::shared_ptr<LogicalOperator> &right_op,
|
|
const std::vector<Symbol> &union_symbols, const std::vector<Symbol> &left_symbols,
|
|
const std::vector<Symbol> &right_symbols);
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> OutputSymbols(const SymbolTable &) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override;
|
|
std::shared_ptr<LogicalOperator> input() const override;
|
|
void set_input(std::shared_ptr<LogicalOperator>) override;
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> left_op_;
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> right_op_;
|
|
std::vector<Symbol> union_symbols_;
|
|
std::vector<Symbol> left_symbols_;
|
|
std::vector<Symbol> 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<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<Union>();
|
|
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<LogicalOperator> &left_op, const std::vector<Symbol> &left_symbols,
|
|
const std::shared_ptr<LogicalOperator> &right_op, const std::vector<Symbol> &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<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override;
|
|
std::shared_ptr<LogicalOperator> input() const override;
|
|
void set_input(std::shared_ptr<LogicalOperator>) override;
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> left_op_;
|
|
std::vector<Symbol> left_symbols_;
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> right_op_;
|
|
std::vector<Symbol> right_symbols_;
|
|
|
|
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<Cartesian>();
|
|
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<Symbol> output_symbols,
|
|
std::function<std::vector<std::vector<TypedValue>>(Frame *, ExecutionContext *)> callback);
|
|
OutputTable(std::vector<Symbol> output_symbols, std::vector<std::vector<TypedValue>> rows);
|
|
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &) override {
|
|
LOG_FATAL("OutputTable operator should not be visited!");
|
|
}
|
|
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> OutputSymbols(const SymbolTable &) const override { return output_symbols_; }
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override { return output_symbols_; }
|
|
|
|
bool HasSingleInput() const override;
|
|
std::shared_ptr<LogicalOperator> input() const override;
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override;
|
|
|
|
std::vector<Symbol> output_symbols_;
|
|
std::function<std::vector<std::vector<TypedValue>>(Frame *, ExecutionContext *)> callback_;
|
|
|
|
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<OutputTable>();
|
|
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<Symbol> output_symbols,
|
|
std::function<std::optional<std::vector<TypedValue>>(Frame *, ExecutionContext *)> callback);
|
|
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &) override {
|
|
LOG_FATAL("OutputTableStream operator should not be visited!");
|
|
}
|
|
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> OutputSymbols(const SymbolTable &) const override { return output_symbols_; }
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override { return output_symbols_; }
|
|
|
|
bool HasSingleInput() const override;
|
|
std::shared_ptr<LogicalOperator> input() const override;
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override;
|
|
|
|
std::vector<Symbol> output_symbols_;
|
|
std::function<std::optional<std::vector<TypedValue>>(Frame *, ExecutionContext *)> callback_;
|
|
|
|
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<OutputTableStream>();
|
|
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<LogicalOperator> input, std::string name, std::vector<Expression *> arguments,
|
|
std::vector<std::string> fields, std::vector<Symbol> 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<Symbol> OutputSymbols(const SymbolTable &) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override { return true; }
|
|
std::shared_ptr<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; }
|
|
|
|
static void IncrementCounter(const std::string &procedure_name);
|
|
static std::unordered_map<std::string, int64_t> GetAndResetCounters();
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> input_;
|
|
std::string procedure_name_;
|
|
std::vector<Expression *> arguments_;
|
|
std::vector<std::string> result_fields_;
|
|
std::vector<Symbol> 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<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<CallProcedure>();
|
|
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<std::unordered_map<std::string, int64_t>, 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<LogicalOperator> 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<Symbol> OutputSymbols(const SymbolTable &) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override { return true; }
|
|
std::shared_ptr<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; }
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> 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<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<LoadCsv>();
|
|
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<LogicalOperator> input, std::shared_ptr<LogicalOperator> updates, Expression *named_expr,
|
|
Symbol loop_variable_symbol);
|
|
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
bool HasSingleInput() const override { return true; }
|
|
std::shared_ptr<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = std::move(input); }
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> input_;
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> update_clauses_;
|
|
Expression *expression_;
|
|
Symbol loop_variable_symbol_;
|
|
|
|
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<Foreach>();
|
|
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<LogicalOperator> input, const std::shared_ptr<LogicalOperator> subquery,
|
|
bool subquery_has_return);
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override { return true; }
|
|
std::shared_ptr<LogicalOperator> input() const override { return input_; }
|
|
void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; }
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> input_;
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> subquery_;
|
|
bool subquery_has_return_;
|
|
|
|
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<Apply>();
|
|
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<LogicalOperator> main_branch, std::shared_ptr<LogicalOperator> sub_branch);
|
|
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
|
UniqueCursorPtr MakeCursor(utils::MemoryResource * /*unused*/) const override;
|
|
std::vector<Symbol> ModifiedSymbols(const SymbolTable & /*unused*/) const override;
|
|
|
|
bool HasSingleInput() const override;
|
|
std::shared_ptr<LogicalOperator> input() const override;
|
|
void set_input(std::shared_ptr<LogicalOperator> /*unused*/) override;
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> main_branch_;
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> sub_branch_;
|
|
|
|
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<IndexedJoin>();
|
|
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<LogicalOperator> &left_op, const std::vector<Symbol> &left_symbols,
|
|
const std::shared_ptr<LogicalOperator> &right_op, const std::vector<Symbol> &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<Symbol> ModifiedSymbols(const SymbolTable &) const override;
|
|
|
|
bool HasSingleInput() const override;
|
|
std::shared_ptr<LogicalOperator> input() const override;
|
|
void set_input(std::shared_ptr<LogicalOperator>) override;
|
|
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> left_op_;
|
|
std::vector<Symbol> left_symbols_;
|
|
std::shared_ptr<memgraph::query::plan::LogicalOperator> right_op_;
|
|
std::vector<Symbol> 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<LogicalOperator> Clone(AstStorage *storage) const override {
|
|
auto object = std::make_unique<HashJoin>();
|
|
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
|