Merge pull request #663 from memgraph/T1167-MG-create-scanbyprimarykey-operator
Create ScanAllByPrimaryKey operator
This commit is contained in:
commit
e13faf41e9
@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@ -23,9 +23,9 @@ namespace memgraph::expr {
|
||||
class Frame {
|
||||
public:
|
||||
/// Create a Frame of given size backed by a utils::NewDeleteResource()
|
||||
explicit Frame(int64_t size) : elems_(size, utils::NewDeleteResource()) { MG_ASSERT(size >= 0); }
|
||||
explicit Frame(size_t size) : elems_(size, utils::NewDeleteResource()) { MG_ASSERT(size >= 0); }
|
||||
|
||||
Frame(int64_t size, utils::MemoryResource *memory) : elems_(size, memory) { MG_ASSERT(size >= 0); }
|
||||
Frame(size_t size, utils::MemoryResource *memory) : elems_(size, memory) { MG_ASSERT(size >= 0); }
|
||||
|
||||
TypedValue &operator[](const Symbol &symbol) { return elems_[symbol.position()]; }
|
||||
const TypedValue &operator[](const Symbol &symbol) const { return elems_[symbol.position()]; }
|
||||
@ -43,9 +43,9 @@ class Frame {
|
||||
|
||||
class FrameWithValidity final : public Frame {
|
||||
public:
|
||||
explicit FrameWithValidity(int64_t size) : Frame(size), is_valid_(false) {}
|
||||
explicit FrameWithValidity(size_t size) : Frame(size), is_valid_(false) {}
|
||||
|
||||
FrameWithValidity(int64_t size, utils::MemoryResource *memory) : Frame(size, memory), is_valid_(false) {}
|
||||
FrameWithValidity(size_t size, utils::MemoryResource *memory) : Frame(size, memory), is_valid_(false) {}
|
||||
|
||||
bool IsValid() const noexcept { return is_valid_; }
|
||||
void MakeValid() noexcept { is_valid_ = true; }
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
|
@ -149,8 +149,6 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: Cost estimate ScanAllById?
|
||||
|
||||
// For the given op first increments the cardinality and then cost.
|
||||
#define POST_VISIT_CARD_FIRST(NAME) \
|
||||
bool PostVisit(NAME &) override { \
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include "query/v2/bindings/eval.hpp"
|
||||
#include "query/v2/bindings/symbol_table.hpp"
|
||||
#include "query/v2/context.hpp"
|
||||
#include "query/v2/conversions.hpp"
|
||||
#include "query/v2/db_accessor.hpp"
|
||||
#include "query/v2/exceptions.hpp"
|
||||
#include "query/v2/frontend/ast/ast.hpp"
|
||||
@ -92,7 +93,7 @@ extern const Event ScanAllByLabelOperator;
|
||||
extern const Event ScanAllByLabelPropertyRangeOperator;
|
||||
extern const Event ScanAllByLabelPropertyValueOperator;
|
||||
extern const Event ScanAllByLabelPropertyOperator;
|
||||
extern const Event ScanAllByIdOperator;
|
||||
extern const Event ScanByPrimaryKeyOperator;
|
||||
extern const Event ExpandOperator;
|
||||
extern const Event ExpandVariableOperator;
|
||||
extern const Event ConstructNamedPathOperator;
|
||||
@ -518,6 +519,88 @@ class DistributedScanAllAndFilterCursor : public Cursor {
|
||||
std::optional<std::vector<Expression *>> filter_expressions_;
|
||||
};
|
||||
|
||||
class DistributedScanByPrimaryKeyCursor : public Cursor {
|
||||
public:
|
||||
explicit DistributedScanByPrimaryKeyCursor(Symbol output_symbol, UniqueCursorPtr input_cursor, const char *op_name,
|
||||
storage::v3::LabelId label,
|
||||
std::optional<std::vector<Expression *>> filter_expressions,
|
||||
std::vector<Expression *> primary_key)
|
||||
: output_symbol_(output_symbol),
|
||||
input_cursor_(std::move(input_cursor)),
|
||||
op_name_(op_name),
|
||||
label_(label),
|
||||
filter_expressions_(filter_expressions),
|
||||
primary_key_(primary_key) {}
|
||||
|
||||
enum class State : int8_t { INITIALIZING, COMPLETED };
|
||||
|
||||
using VertexAccessor = accessors::VertexAccessor;
|
||||
|
||||
std::optional<VertexAccessor> MakeRequestSingleFrame(Frame &frame, RequestRouterInterface &request_router,
|
||||
ExecutionContext &context) {
|
||||
// Evaluate the expressions that hold the PrimaryKey.
|
||||
ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.request_router,
|
||||
storage::v3::View::NEW);
|
||||
|
||||
std::vector<msgs::Value> pk;
|
||||
for (auto *primary_property : primary_key_) {
|
||||
pk.push_back(TypedValueToValue(primary_property->Accept(evaluator)));
|
||||
}
|
||||
|
||||
msgs::Label label = {.id = msgs::LabelId::FromUint(label_.AsUint())};
|
||||
|
||||
msgs::GetPropertiesRequest req = {.vertex_ids = {std::make_pair(label, pk)}};
|
||||
auto get_prop_result = std::invoke([&context, &request_router, &req]() mutable {
|
||||
SCOPED_REQUEST_WAIT_PROFILE;
|
||||
return request_router.GetProperties(req);
|
||||
});
|
||||
MG_ASSERT(get_prop_result.size() <= 1);
|
||||
|
||||
if (get_prop_result.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto properties = get_prop_result[0].props;
|
||||
// TODO (gvolfing) figure out labels when relevant.
|
||||
msgs::Vertex vertex = {.id = get_prop_result[0].vertex, .labels = {}};
|
||||
|
||||
return VertexAccessor(vertex, properties, &request_router);
|
||||
}
|
||||
|
||||
bool Pull(Frame &frame, ExecutionContext &context) override {
|
||||
SCOPED_PROFILE_OP(op_name_);
|
||||
|
||||
if (MustAbort(context)) {
|
||||
throw HintedAbortError();
|
||||
}
|
||||
|
||||
while (input_cursor_->Pull(frame, context)) {
|
||||
auto &request_router = *context.request_router;
|
||||
auto vertex = MakeRequestSingleFrame(frame, request_router, context);
|
||||
if (vertex) {
|
||||
frame[output_symbol_] = TypedValue(std::move(*vertex));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PullMultiple(MultiFrame & /*input_multi_frame*/, ExecutionContext & /*context*/) override {
|
||||
throw utils::NotYetImplemented("Multiframe version of ScanByPrimaryKey is yet to be implemented.");
|
||||
};
|
||||
|
||||
void Reset() override { input_cursor_->Reset(); }
|
||||
|
||||
void Shutdown() override { input_cursor_->Shutdown(); }
|
||||
|
||||
private:
|
||||
const Symbol output_symbol_;
|
||||
const UniqueCursorPtr input_cursor_;
|
||||
const char *op_name_;
|
||||
storage::v3::LabelId label_;
|
||||
std::optional<std::vector<Expression *>> filter_expressions_;
|
||||
std::vector<Expression *> primary_key_;
|
||||
};
|
||||
|
||||
ScanAll::ScanAll(const std::shared_ptr<LogicalOperator> &input, Symbol output_symbol, storage::v3::View view)
|
||||
: input_(input ? input : std::make_shared<Once>()), output_symbol_(output_symbol), view_(view) {}
|
||||
|
||||
@ -614,22 +697,21 @@ UniqueCursorPtr ScanAllByLabelProperty::MakeCursor(utils::MemoryResource *mem) c
|
||||
throw QueryRuntimeException("ScanAllByLabelProperty is not supported");
|
||||
}
|
||||
|
||||
ScanAllById::ScanAllById(const std::shared_ptr<LogicalOperator> &input, Symbol output_symbol, Expression *expression,
|
||||
storage::v3::View view)
|
||||
: ScanAll(input, output_symbol, view), expression_(expression) {
|
||||
MG_ASSERT(expression);
|
||||
ScanByPrimaryKey::ScanByPrimaryKey(const std::shared_ptr<LogicalOperator> &input, Symbol output_symbol,
|
||||
storage::v3::LabelId label, std::vector<query::v2::Expression *> primary_key,
|
||||
storage::v3::View view)
|
||||
: ScanAll(input, output_symbol, view), label_(label), primary_key_(primary_key) {
|
||||
MG_ASSERT(primary_key.front());
|
||||
}
|
||||
|
||||
ACCEPT_WITH_INPUT(ScanAllById)
|
||||
ACCEPT_WITH_INPUT(ScanByPrimaryKey)
|
||||
|
||||
UniqueCursorPtr ScanAllById::MakeCursor(utils::MemoryResource *mem) const {
|
||||
EventCounter::IncrementCounter(EventCounter::ScanAllByIdOperator);
|
||||
// TODO Reimplement when we have reliable conversion between hash value and pk
|
||||
auto vertices = [](Frame & /*frame*/, ExecutionContext & /*context*/) -> std::optional<std::vector<VertexAccessor>> {
|
||||
return std::nullopt;
|
||||
};
|
||||
return MakeUniqueCursorPtr<ScanAllCursor<decltype(vertices)>>(mem, output_symbol_, input_->MakeCursor(mem),
|
||||
std::move(vertices), "ScanAllById");
|
||||
UniqueCursorPtr ScanByPrimaryKey::MakeCursor(utils::MemoryResource *mem) const {
|
||||
EventCounter::IncrementCounter(EventCounter::ScanByPrimaryKeyOperator);
|
||||
|
||||
return MakeUniqueCursorPtr<DistributedScanByPrimaryKeyCursor>(mem, output_symbol_, input_->MakeCursor(mem),
|
||||
"ScanByPrimaryKey", label_,
|
||||
std::nullopt /*filter_expressions*/, primary_key_);
|
||||
}
|
||||
|
||||
Expand::Expand(const std::shared_ptr<LogicalOperator> &input, Symbol input_symbol, Symbol node_symbol,
|
||||
|
@ -113,7 +113,7 @@ class ScanAllByLabel;
|
||||
class ScanAllByLabelPropertyRange;
|
||||
class ScanAllByLabelPropertyValue;
|
||||
class ScanAllByLabelProperty;
|
||||
class ScanAllById;
|
||||
class ScanByPrimaryKey;
|
||||
class Expand;
|
||||
class ExpandVariable;
|
||||
class ConstructNamedPath;
|
||||
@ -144,7 +144,7 @@ class Foreach;
|
||||
using LogicalOperatorCompositeVisitor = utils::CompositeVisitor<
|
||||
Once, CreateNode, CreateExpand, ScanAll, ScanAllByLabel,
|
||||
ScanAllByLabelPropertyRange, ScanAllByLabelPropertyValue,
|
||||
ScanAllByLabelProperty, ScanAllById,
|
||||
ScanAllByLabelProperty, ScanByPrimaryKey,
|
||||
Expand, ExpandVariable, ConstructNamedPath, Filter, Produce, Delete,
|
||||
SetProperty, SetProperties, SetLabels, RemoveProperty, RemoveLabels,
|
||||
EdgeUniquenessFilter, Accumulate, Aggregate, Skip, Limit, OrderBy, Merge,
|
||||
@ -845,19 +845,21 @@ given label and property.
|
||||
(:serialize (:slk))
|
||||
(:clone))
|
||||
|
||||
|
||||
|
||||
(lcp:define-class scan-all-by-id (scan-all)
|
||||
((expression "Expression *" :scope :public
|
||||
(lcp:define-class scan-by-primary-key (scan-all)
|
||||
((label "::storage::v3::LabelId" :scope :public)
|
||||
(primary-key "std::vector<Expression*>" :scope :public)
|
||||
(expression "Expression *" :scope :public
|
||||
:slk-save #'slk-save-ast-pointer
|
||||
:slk-load (slk-load-ast-pointer "Expression")))
|
||||
(:documentation
|
||||
"ScanAll producing a single node with ID equal to evaluated expression")
|
||||
"ScanAll producing a single node with specified by the label and primary key")
|
||||
(:public
|
||||
#>cpp
|
||||
ScanAllById() {}
|
||||
ScanAllById(const std::shared_ptr<LogicalOperator> &input,
|
||||
Symbol output_symbol, Expression *expression,
|
||||
ScanByPrimaryKey() {}
|
||||
ScanByPrimaryKey(const std::shared_ptr<LogicalOperator> &input,
|
||||
Symbol output_symbol,
|
||||
storage::v3::LabelId label,
|
||||
std::vector<query::v2::Expression*> primary_key,
|
||||
storage::v3::View view = storage::v3::View::OLD);
|
||||
|
||||
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@ -86,10 +86,10 @@ bool PlanPrinter::PreVisit(query::v2::plan::ScanAllByLabelProperty &op) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlanPrinter::PreVisit(ScanAllById &op) {
|
||||
bool PlanPrinter::PreVisit(query::v2::plan::ScanByPrimaryKey &op) {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* ScanAllById"
|
||||
<< " (" << op.output_symbol_.name() << ")";
|
||||
out << "* ScanByPrimaryKey"
|
||||
<< " (" << op.output_symbol_.name() << " :" << request_router_->LabelToName(op.label_) << ")";
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@ -487,12 +487,15 @@ bool PlanToJsonVisitor::PreVisit(ScanAllByLabelProperty &op) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(ScanAllById &op) {
|
||||
bool PlanToJsonVisitor::PreVisit(ScanByPrimaryKey &op) {
|
||||
json self;
|
||||
self["name"] = "ScanAllById";
|
||||
self["name"] = "ScanByPrimaryKey";
|
||||
self["label"] = ToJson(op.label_, *request_router_);
|
||||
self["output_symbol"] = ToJson(op.output_symbol_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@ -67,7 +67,7 @@ class PlanPrinter : public virtual HierarchicalLogicalOperatorVisitor {
|
||||
bool PreVisit(ScanAllByLabelPropertyValue &) override;
|
||||
bool PreVisit(ScanAllByLabelPropertyRange &) override;
|
||||
bool PreVisit(ScanAllByLabelProperty &) override;
|
||||
bool PreVisit(ScanAllById &) override;
|
||||
bool PreVisit(ScanByPrimaryKey & /*unused*/) override;
|
||||
|
||||
bool PreVisit(Expand &) override;
|
||||
bool PreVisit(ExpandVariable &) override;
|
||||
@ -194,7 +194,7 @@ class PlanToJsonVisitor : public virtual HierarchicalLogicalOperatorVisitor {
|
||||
bool PreVisit(ScanAllByLabelPropertyRange &) override;
|
||||
bool PreVisit(ScanAllByLabelPropertyValue &) override;
|
||||
bool PreVisit(ScanAllByLabelProperty &) override;
|
||||
bool PreVisit(ScanAllById &) override;
|
||||
bool PreVisit(ScanByPrimaryKey & /*unused*/) override;
|
||||
|
||||
bool PreVisit(Produce &) override;
|
||||
bool PreVisit(Accumulate &) override;
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@ -11,10 +11,12 @@
|
||||
|
||||
#include "query/v2/plan/read_write_type_checker.hpp"
|
||||
|
||||
#define PRE_VISIT(TOp, RWType, continue_visiting) \
|
||||
bool ReadWriteTypeChecker::PreVisit(TOp &op) { \
|
||||
UpdateType(RWType); \
|
||||
return continue_visiting; \
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define PRE_VISIT(TOp, RWType, continue_visiting) \
|
||||
/*NOLINTNEXTLINE(bugprone-macro-parentheses)*/ \
|
||||
bool ReadWriteTypeChecker::PreVisit(TOp & /*op*/) { \
|
||||
UpdateType(RWType); \
|
||||
return continue_visiting; \
|
||||
}
|
||||
|
||||
namespace memgraph::query::v2::plan {
|
||||
@ -35,7 +37,7 @@ PRE_VISIT(ScanAllByLabel, RWType::R, true)
|
||||
PRE_VISIT(ScanAllByLabelPropertyRange, RWType::R, true)
|
||||
PRE_VISIT(ScanAllByLabelPropertyValue, RWType::R, true)
|
||||
PRE_VISIT(ScanAllByLabelProperty, RWType::R, true)
|
||||
PRE_VISIT(ScanAllById, RWType::R, true)
|
||||
PRE_VISIT(ScanByPrimaryKey, RWType::R, true)
|
||||
|
||||
PRE_VISIT(Expand, RWType::R, true)
|
||||
PRE_VISIT(ExpandVariable, RWType::R, true)
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@ -59,7 +59,7 @@ class ReadWriteTypeChecker : public virtual HierarchicalLogicalOperatorVisitor {
|
||||
bool PreVisit(ScanAllByLabelPropertyValue &) override;
|
||||
bool PreVisit(ScanAllByLabelPropertyRange &) override;
|
||||
bool PreVisit(ScanAllByLabelProperty &) override;
|
||||
bool PreVisit(ScanAllById &) override;
|
||||
bool PreVisit(ScanByPrimaryKey & /*unused*/) override;
|
||||
|
||||
bool PreVisit(Expand &) override;
|
||||
bool PreVisit(ExpandVariable &) override;
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@ -25,8 +25,10 @@
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include "query/v2/frontend/ast/ast.hpp"
|
||||
#include "query/v2/plan/operator.hpp"
|
||||
#include "query/v2/plan/preprocess.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
|
||||
DECLARE_int64(query_vertex_count_to_expand_existing);
|
||||
|
||||
@ -271,11 +273,12 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(ScanAllById &op) override {
|
||||
bool PreVisit(ScanByPrimaryKey &op) override {
|
||||
prev_ops_.push_back(&op);
|
||||
return true;
|
||||
}
|
||||
bool PostVisit(ScanAllById &) override {
|
||||
|
||||
bool PostVisit(ScanByPrimaryKey & /*unused*/) override {
|
||||
prev_ops_.pop_back();
|
||||
return true;
|
||||
}
|
||||
@ -487,6 +490,12 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor {
|
||||
|
||||
storage::v3::PropertyId GetProperty(PropertyIx prop) { return db_->NameToProperty(prop.name); }
|
||||
|
||||
void EraseLabelFilters(const memgraph::query::v2::Symbol &node_symbol, memgraph::query::v2::LabelIx prim_label) {
|
||||
std::vector<query::v2::Expression *> removed_expressions;
|
||||
filters_.EraseLabelFilter(node_symbol, prim_label, &removed_expressions);
|
||||
filter_exprs_for_removal_.insert(removed_expressions.begin(), removed_expressions.end());
|
||||
}
|
||||
|
||||
std::optional<LabelIx> FindBestLabelIndex(const std::unordered_set<LabelIx> &labels) {
|
||||
MG_ASSERT(!labels.empty(), "Trying to find the best label without any labels.");
|
||||
std::optional<LabelIx> best_label;
|
||||
@ -559,31 +568,81 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor {
|
||||
const auto &view = scan.view_;
|
||||
const auto &modified_symbols = scan.ModifiedSymbols(*symbol_table_);
|
||||
std::unordered_set<Symbol> bound_symbols(modified_symbols.begin(), modified_symbols.end());
|
||||
auto are_bound = [&bound_symbols](const auto &used_symbols) {
|
||||
for (const auto &used_symbol : used_symbols) {
|
||||
if (!utils::Contains(bound_symbols, used_symbol)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
// First, try to see if we can find a vertex by ID.
|
||||
if (!max_vertex_count || *max_vertex_count >= 1) {
|
||||
for (const auto &filter : filters_.IdFilters(node_symbol)) {
|
||||
if (filter.id_filter->is_symbol_in_value_ || !are_bound(filter.used_symbols)) continue;
|
||||
auto *value = filter.id_filter->value_;
|
||||
filter_exprs_for_removal_.insert(filter.expression);
|
||||
filters_.EraseFilter(filter);
|
||||
return std::make_unique<ScanAllById>(input, node_symbol, value, view);
|
||||
}
|
||||
}
|
||||
// Now try to see if we can use label+property index. If not, try to use
|
||||
// just the label index.
|
||||
|
||||
// Try to see if we can use label + primary-key or label + property index.
|
||||
// If not, try to use just the label index.
|
||||
const auto labels = filters_.FilteredLabels(node_symbol);
|
||||
if (labels.empty()) {
|
||||
// Without labels, we cannot generate any indexed ScanAll.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// First, try to see if we can find a vertex based on the possibly
|
||||
// supplied primary key.
|
||||
auto property_filters = filters_.PropertyFilters(node_symbol);
|
||||
query::v2::LabelIx prim_label;
|
||||
std::vector<std::pair<query::v2::Expression *, query::v2::plan::FilterInfo>> primary_key;
|
||||
|
||||
auto extract_primary_key = [this](storage::v3::LabelId label,
|
||||
std::vector<query::v2::plan::FilterInfo> property_filters)
|
||||
-> std::vector<std::pair<query::v2::Expression *, query::v2::plan::FilterInfo>> {
|
||||
std::vector<std::pair<query::v2::Expression *, query::v2::plan::FilterInfo>> pk_temp;
|
||||
std::vector<std::pair<query::v2::Expression *, query::v2::plan::FilterInfo>> pk;
|
||||
std::vector<memgraph::storage::v3::SchemaProperty> schema = db_->GetSchemaForLabel(label);
|
||||
|
||||
std::vector<storage::v3::PropertyId> schema_properties;
|
||||
schema_properties.reserve(schema.size());
|
||||
|
||||
std::transform(schema.begin(), schema.end(), std::back_inserter(schema_properties),
|
||||
[](const auto &schema_elem) { return schema_elem.property_id; });
|
||||
|
||||
for (const auto &property_filter : property_filters) {
|
||||
const auto &property_id = db_->NameToProperty(property_filter.property_filter->property_.name);
|
||||
if (std::find(schema_properties.begin(), schema_properties.end(), property_id) != schema_properties.end()) {
|
||||
pk_temp.emplace_back(std::make_pair(property_filter.expression, property_filter));
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure pk is in the same order as schema_properties.
|
||||
for (const auto &schema_prop : schema_properties) {
|
||||
for (auto &pk_temp_prop : pk_temp) {
|
||||
const auto &property_id = db_->NameToProperty(pk_temp_prop.second.property_filter->property_.name);
|
||||
if (schema_prop == property_id) {
|
||||
pk.push_back(pk_temp_prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
MG_ASSERT(pk.size() == pk_temp.size(),
|
||||
"The two vectors should represent the same primary key with a possibly different order of contained "
|
||||
"elements.");
|
||||
|
||||
return pk.size() == schema_properties.size()
|
||||
? pk
|
||||
: std::vector<std::pair<query::v2::Expression *, query::v2::plan::FilterInfo>>{};
|
||||
};
|
||||
|
||||
if (!property_filters.empty()) {
|
||||
for (const auto &label : labels) {
|
||||
if (db_->PrimaryLabelExists(GetLabel(label))) {
|
||||
prim_label = label;
|
||||
primary_key = extract_primary_key(GetLabel(prim_label), property_filters);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!primary_key.empty()) {
|
||||
// Mark the expressions so they won't be used for an additional, unnecessary filter.
|
||||
for (const auto &primary_property : primary_key) {
|
||||
filter_exprs_for_removal_.insert(primary_property.first);
|
||||
filters_.EraseFilter(primary_property.second);
|
||||
}
|
||||
EraseLabelFilters(node_symbol, prim_label);
|
||||
std::vector<query::v2::Expression *> pk_expressions;
|
||||
std::transform(primary_key.begin(), primary_key.end(), std::back_inserter(pk_expressions),
|
||||
[](const auto &exp) { return exp.second.property_filter->value_; });
|
||||
return std::make_unique<ScanByPrimaryKey>(input, node_symbol, GetLabel(prim_label), pk_expressions);
|
||||
}
|
||||
}
|
||||
|
||||
auto found_index = FindBestLabelPropertyIndex(node_symbol, bound_symbols);
|
||||
if (found_index &&
|
||||
// Use label+property index if we satisfy max_vertex_count.
|
||||
@ -597,9 +656,7 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor {
|
||||
filter_exprs_for_removal_.insert(found_index->filter.expression);
|
||||
}
|
||||
filters_.EraseFilter(found_index->filter);
|
||||
std::vector<Expression *> removed_expressions;
|
||||
filters_.EraseLabelFilter(node_symbol, found_index->label, &removed_expressions);
|
||||
filter_exprs_for_removal_.insert(removed_expressions.begin(), removed_expressions.end());
|
||||
EraseLabelFilters(node_symbol, found_index->label);
|
||||
if (prop_filter.lower_bound_ || prop_filter.upper_bound_) {
|
||||
return std::make_unique<ScanAllByLabelPropertyRange>(
|
||||
input, node_symbol, GetLabel(found_index->label), GetProperty(prop_filter.property_),
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@ -12,14 +12,17 @@
|
||||
/// @file
|
||||
#pragma once
|
||||
|
||||
#include <iterator>
|
||||
#include <optional>
|
||||
|
||||
#include "query/v2/bindings/typed_value.hpp"
|
||||
#include "query/v2/plan/preprocess.hpp"
|
||||
#include "query/v2/request_router.hpp"
|
||||
#include "storage/v3/conversions.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "utils/bound.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/fnv.hpp"
|
||||
|
||||
namespace memgraph::query::v2::plan {
|
||||
@ -52,11 +55,16 @@ class VertexCountCache {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// For now return true if label is primary label
|
||||
bool LabelIndexExists(storage::v3::LabelId label) { return request_router_->IsPrimaryLabel(label); }
|
||||
bool LabelIndexExists(storage::v3::LabelId label) { return PrimaryLabelExists(label); }
|
||||
|
||||
bool PrimaryLabelExists(storage::v3::LabelId label) { return request_router_->IsPrimaryLabel(label); }
|
||||
|
||||
bool LabelPropertyIndexExists(storage::v3::LabelId /*label*/, storage::v3::PropertyId /*property*/) { return false; }
|
||||
|
||||
const std::vector<memgraph::storage::v3::SchemaProperty> &GetSchemaForLabel(storage::v3::LabelId label) {
|
||||
return request_router_->GetSchemaForLabel(label);
|
||||
}
|
||||
|
||||
RequestRouterInterface *request_router_;
|
||||
};
|
||||
|
||||
|
@ -114,6 +114,7 @@ class RequestRouterInterface {
|
||||
virtual std::optional<storage::v3::LabelId> MaybeNameToLabel(const std::string &name) const = 0;
|
||||
virtual bool IsPrimaryLabel(storage::v3::LabelId label) const = 0;
|
||||
virtual bool IsPrimaryKey(storage::v3::LabelId primary_label, storage::v3::PropertyId property) const = 0;
|
||||
virtual const std::vector<coordinator::SchemaProperty> &GetSchemaForLabel(storage::v3::LabelId label) const = 0;
|
||||
};
|
||||
|
||||
// TODO(kostasrim)rename this class template
|
||||
@ -232,12 +233,17 @@ class RequestRouter : public RequestRouterInterface {
|
||||
}) != schema_it->second.end();
|
||||
}
|
||||
|
||||
const std::vector<coordinator::SchemaProperty> &GetSchemaForLabel(storage::v3::LabelId label) const override {
|
||||
return shards_map_.schemas.at(label);
|
||||
}
|
||||
|
||||
bool IsPrimaryLabel(storage::v3::LabelId label) const override { return shards_map_.label_spaces.contains(label); }
|
||||
|
||||
// TODO(kostasrim) Simplify return result
|
||||
std::vector<VertexAccessor> ScanVertices(std::optional<std::string> label) override {
|
||||
// create requests
|
||||
std::vector<ShardRequestState<msgs::ScanVerticesRequest>> requests_to_be_sent = RequestsForScanVertices(label);
|
||||
auto requests_to_be_sent = RequestsForScanVertices(label);
|
||||
|
||||
spdlog::trace("created {} ScanVertices requests", requests_to_be_sent.size());
|
||||
|
||||
// begin all requests in parallel
|
||||
@ -360,6 +366,7 @@ class RequestRouter : public RequestRouterInterface {
|
||||
}
|
||||
|
||||
std::vector<msgs::GetPropertiesResultRow> GetProperties(msgs::GetPropertiesRequest requests) override {
|
||||
requests.transaction_id = transaction_id_;
|
||||
// create requests
|
||||
std::vector<ShardRequestState<msgs::GetPropertiesRequest>> requests_to_be_sent =
|
||||
RequestsForGetProperties(std::move(requests));
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@ -535,13 +535,17 @@ msgs::ReadResponses ShardRsm::HandleRead(msgs::GetPropertiesRequest &&req) {
|
||||
return result;
|
||||
};
|
||||
|
||||
auto collect_props = [&req](const VertexAccessor &v_acc,
|
||||
const std::optional<EdgeAccessor> &e_acc) -> ShardResult<std::map<PropertyId, Value>> {
|
||||
auto collect_props = [this, &req](
|
||||
const VertexAccessor &v_acc,
|
||||
const std::optional<EdgeAccessor> &e_acc) -> ShardResult<std::map<PropertyId, Value>> {
|
||||
if (!req.property_ids) {
|
||||
if (e_acc) {
|
||||
return CollectAllPropertiesFromAccessor(*e_acc, view);
|
||||
}
|
||||
return CollectAllPropertiesFromAccessor(v_acc, view);
|
||||
const auto *schema = shard_->GetSchema(shard_->PrimaryLabel());
|
||||
MG_ASSERT(schema);
|
||||
|
||||
return CollectAllPropertiesFromAccessor(v_acc, view, *schema);
|
||||
}
|
||||
|
||||
if (e_acc) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@ -25,6 +25,7 @@
|
||||
M(ScanAllByLabelPropertyValueOperator, "Number of times ScanAllByLabelPropertyValue operator was used.") \
|
||||
M(ScanAllByLabelPropertyOperator, "Number of times ScanAllByLabelProperty operator was used.") \
|
||||
M(ScanAllByIdOperator, "Number of times ScanAllById operator was used.") \
|
||||
M(ScanByPrimaryKeyOperator, "Number of times ScanByPrimaryKey operator was used.") \
|
||||
M(ExpandOperator, "Number of times Expand operator was used.") \
|
||||
M(ExpandVariableOperator, "Number of times ExpandVariable operator was used.") \
|
||||
M(ConstructNamedPathOperator, "Number of times ConstructNamedPath operator was used.") \
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@ -1305,7 +1305,7 @@ void TestGetProperties(ShardClient &client) {
|
||||
MG_ASSERT(!result.error);
|
||||
MG_ASSERT(result.result_row.size() == 2);
|
||||
for (const auto &elem : result.result_row) {
|
||||
MG_ASSERT(elem.props.size() == 3);
|
||||
MG_ASSERT(elem.props.size() == 4);
|
||||
}
|
||||
}
|
||||
{
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
|
@ -93,6 +93,9 @@ target_link_libraries(${test_prefix}query_expression_evaluator mg-query)
|
||||
add_unit_test(query_plan.cpp)
|
||||
target_link_libraries(${test_prefix}query_plan mg-query)
|
||||
|
||||
add_unit_test(query_v2_plan.cpp)
|
||||
target_link_libraries(${test_prefix}query_v2_plan mg-query-v2)
|
||||
|
||||
add_unit_test(query_plan_accumulate_aggregate.cpp)
|
||||
target_link_libraries(${test_prefix}query_plan_accumulate_aggregate mg-query)
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
|
@ -42,6 +42,7 @@ class MockedRequestRouter : public RequestRouterInterface {
|
||||
MOCK_METHOD(std::optional<storage::v3::LabelId>, MaybeNameToLabel, (const std::string &), (const));
|
||||
MOCK_METHOD(bool, IsPrimaryLabel, (storage::v3::LabelId), (const));
|
||||
MOCK_METHOD(bool, IsPrimaryKey, (storage::v3::LabelId, storage::v3::PropertyId), (const));
|
||||
MOCK_METHOD(const std::vector<coordinator::SchemaProperty> &, GetSchemaForLabel, (storage::v3::LabelId), (const));
|
||||
};
|
||||
|
||||
class MockedLogicalOperator : public plan::LogicalOperator {
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@ -501,6 +501,8 @@ auto GetForeach(AstStorage &storage, NamedExpression *named_expr, const std::vec
|
||||
storage.Create<memgraph::query::MapLiteral>( \
|
||||
std::unordered_map<memgraph::query::PropertyIx, memgraph::query::Expression *>{__VA_ARGS__})
|
||||
#define PROPERTY_PAIR(property_name) std::make_pair(property_name, dba.NameToProperty(property_name))
|
||||
#define PRIMARY_PROPERTY_PAIR(property_name) std::make_pair(property_name, dba.NameToPrimaryProperty(property_name))
|
||||
#define SECONDARY_PROPERTY_PAIR(property_name) std::make_pair(property_name, dba.NameToSecondaryProperty(property_name))
|
||||
#define PROPERTY_LOOKUP(...) memgraph::query::test_common::GetPropertyLookup(storage, dba, __VA_ARGS__)
|
||||
#define PARAMETER_LOOKUP(token_position) storage.Create<memgraph::query::ParameterLookup>((token_position))
|
||||
#define NEXPR(name, expr) storage.Create<memgraph::query::NamedExpression>((name), (expr))
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@ -17,6 +17,7 @@
|
||||
#include "query/plan/operator.hpp"
|
||||
#include "query/plan/planner.hpp"
|
||||
#include "query/plan/preprocess.hpp"
|
||||
#include "query/v2/plan/operator.hpp"
|
||||
|
||||
namespace memgraph::query::plan {
|
||||
|
||||
@ -90,7 +91,7 @@ class PlanChecker : public virtual HierarchicalLogicalOperatorVisitor {
|
||||
}
|
||||
PRE_VISIT(Unwind);
|
||||
PRE_VISIT(Distinct);
|
||||
|
||||
|
||||
bool PreVisit(Foreach &op) override {
|
||||
CheckOp(op);
|
||||
return false;
|
||||
@ -336,6 +337,35 @@ class ExpectScanAllByLabelProperty : public OpChecker<ScanAllByLabelProperty> {
|
||||
memgraph::storage::PropertyId property_;
|
||||
};
|
||||
|
||||
class ExpectScanByPrimaryKey : public OpChecker<v2::plan::ScanByPrimaryKey> {
|
||||
public:
|
||||
ExpectScanByPrimaryKey(memgraph::storage::v3::LabelId label, const std::vector<Expression *> &properties)
|
||||
: label_(label), properties_(properties) {}
|
||||
|
||||
void ExpectOp(v2::plan::ScanByPrimaryKey &scan_all, const SymbolTable &) override {
|
||||
EXPECT_EQ(scan_all.label_, label_);
|
||||
|
||||
bool primary_property_match = true;
|
||||
for (const auto &expected_prop : properties_) {
|
||||
bool has_match = false;
|
||||
for (const auto &prop : scan_all.primary_key_) {
|
||||
if (typeid(prop).hash_code() == typeid(expected_prop).hash_code()) {
|
||||
has_match = true;
|
||||
}
|
||||
}
|
||||
if (!has_match) {
|
||||
primary_property_match = false;
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_TRUE(primary_property_match);
|
||||
}
|
||||
|
||||
private:
|
||||
memgraph::storage::v3::LabelId label_;
|
||||
std::vector<Expression *> properties_;
|
||||
};
|
||||
|
||||
class ExpectCartesian : public OpChecker<Cartesian> {
|
||||
public:
|
||||
ExpectCartesian(const std::list<std::unique_ptr<BaseOpChecker>> &left,
|
||||
|
452
tests/unit/query_plan_checker_v2.hpp
Normal file
452
tests/unit/query_plan_checker_v2.hpp
Normal file
@ -0,0 +1,452 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "query/frontend/semantic/symbol_generator.hpp"
|
||||
#include "query/frontend/semantic/symbol_table.hpp"
|
||||
#include "query/v2/plan/operator.hpp"
|
||||
#include "query/v2/plan/planner.hpp"
|
||||
#include "query/v2/plan/preprocess.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
|
||||
namespace memgraph::query::v2::plan {
|
||||
|
||||
class BaseOpChecker {
|
||||
public:
|
||||
virtual ~BaseOpChecker() {}
|
||||
|
||||
virtual void CheckOp(LogicalOperator &, const SymbolTable &) = 0;
|
||||
};
|
||||
|
||||
class PlanChecker : public virtual HierarchicalLogicalOperatorVisitor {
|
||||
public:
|
||||
using HierarchicalLogicalOperatorVisitor::PostVisit;
|
||||
using HierarchicalLogicalOperatorVisitor::PreVisit;
|
||||
using HierarchicalLogicalOperatorVisitor::Visit;
|
||||
|
||||
PlanChecker(const std::list<std::unique_ptr<BaseOpChecker>> &checkers, const SymbolTable &symbol_table)
|
||||
: symbol_table_(symbol_table) {
|
||||
for (const auto &checker : checkers) checkers_.emplace_back(checker.get());
|
||||
}
|
||||
|
||||
PlanChecker(const std::list<BaseOpChecker *> &checkers, const SymbolTable &symbol_table)
|
||||
: checkers_(checkers), symbol_table_(symbol_table) {}
|
||||
|
||||
#define PRE_VISIT(TOp) \
|
||||
bool PreVisit(TOp &op) override { \
|
||||
CheckOp(op); \
|
||||
return true; \
|
||||
}
|
||||
|
||||
#define VISIT(TOp) \
|
||||
bool Visit(TOp &op) override { \
|
||||
CheckOp(op); \
|
||||
return true; \
|
||||
}
|
||||
|
||||
PRE_VISIT(CreateNode);
|
||||
PRE_VISIT(CreateExpand);
|
||||
PRE_VISIT(Delete);
|
||||
PRE_VISIT(ScanAll);
|
||||
PRE_VISIT(ScanAllByLabel);
|
||||
PRE_VISIT(ScanAllByLabelPropertyValue);
|
||||
PRE_VISIT(ScanAllByLabelPropertyRange);
|
||||
PRE_VISIT(ScanAllByLabelProperty);
|
||||
PRE_VISIT(ScanByPrimaryKey);
|
||||
PRE_VISIT(Expand);
|
||||
PRE_VISIT(ExpandVariable);
|
||||
PRE_VISIT(Filter);
|
||||
PRE_VISIT(ConstructNamedPath);
|
||||
PRE_VISIT(Produce);
|
||||
PRE_VISIT(SetProperty);
|
||||
PRE_VISIT(SetProperties);
|
||||
PRE_VISIT(SetLabels);
|
||||
PRE_VISIT(RemoveProperty);
|
||||
PRE_VISIT(RemoveLabels);
|
||||
PRE_VISIT(EdgeUniquenessFilter);
|
||||
PRE_VISIT(Accumulate);
|
||||
PRE_VISIT(Aggregate);
|
||||
PRE_VISIT(Skip);
|
||||
PRE_VISIT(Limit);
|
||||
PRE_VISIT(OrderBy);
|
||||
bool PreVisit(Merge &op) override {
|
||||
CheckOp(op);
|
||||
op.input()->Accept(*this);
|
||||
return false;
|
||||
}
|
||||
bool PreVisit(Optional &op) override {
|
||||
CheckOp(op);
|
||||
op.input()->Accept(*this);
|
||||
return false;
|
||||
}
|
||||
PRE_VISIT(Unwind);
|
||||
PRE_VISIT(Distinct);
|
||||
|
||||
bool PreVisit(Foreach &op) override {
|
||||
CheckOp(op);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Visit(Once &) override {
|
||||
// Ignore checking Once, it is implicitly at the end.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(Cartesian &op) override {
|
||||
CheckOp(op);
|
||||
return false;
|
||||
}
|
||||
|
||||
PRE_VISIT(CallProcedure);
|
||||
|
||||
#undef PRE_VISIT
|
||||
#undef VISIT
|
||||
|
||||
void CheckOp(LogicalOperator &op) {
|
||||
ASSERT_FALSE(checkers_.empty());
|
||||
checkers_.back()->CheckOp(op, symbol_table_);
|
||||
checkers_.pop_back();
|
||||
}
|
||||
|
||||
std::list<BaseOpChecker *> checkers_;
|
||||
const SymbolTable &symbol_table_;
|
||||
};
|
||||
|
||||
template <class TOp>
|
||||
class OpChecker : public BaseOpChecker {
|
||||
public:
|
||||
void CheckOp(LogicalOperator &op, const SymbolTable &symbol_table) override {
|
||||
auto *expected_op = dynamic_cast<TOp *>(&op);
|
||||
ASSERT_TRUE(expected_op) << "op is '" << op.GetTypeInfo().name << "' expected '" << TOp::kType.name << "'!";
|
||||
ExpectOp(*expected_op, symbol_table);
|
||||
}
|
||||
|
||||
virtual void ExpectOp(TOp &, const SymbolTable &) {}
|
||||
};
|
||||
|
||||
using ExpectCreateNode = OpChecker<CreateNode>;
|
||||
using ExpectCreateExpand = OpChecker<CreateExpand>;
|
||||
using ExpectDelete = OpChecker<Delete>;
|
||||
using ExpectScanAll = OpChecker<ScanAll>;
|
||||
using ExpectScanAllByLabel = OpChecker<ScanAllByLabel>;
|
||||
using ExpectExpand = OpChecker<Expand>;
|
||||
using ExpectFilter = OpChecker<Filter>;
|
||||
using ExpectConstructNamedPath = OpChecker<ConstructNamedPath>;
|
||||
using ExpectProduce = OpChecker<Produce>;
|
||||
using ExpectSetProperty = OpChecker<SetProperty>;
|
||||
using ExpectSetProperties = OpChecker<SetProperties>;
|
||||
using ExpectSetLabels = OpChecker<SetLabels>;
|
||||
using ExpectRemoveProperty = OpChecker<RemoveProperty>;
|
||||
using ExpectRemoveLabels = OpChecker<RemoveLabels>;
|
||||
using ExpectEdgeUniquenessFilter = OpChecker<EdgeUniquenessFilter>;
|
||||
using ExpectSkip = OpChecker<Skip>;
|
||||
using ExpectLimit = OpChecker<Limit>;
|
||||
using ExpectOrderBy = OpChecker<OrderBy>;
|
||||
using ExpectUnwind = OpChecker<Unwind>;
|
||||
using ExpectDistinct = OpChecker<Distinct>;
|
||||
|
||||
class ExpectScanAllByLabelPropertyValue : public OpChecker<ScanAllByLabelPropertyValue> {
|
||||
public:
|
||||
ExpectScanAllByLabelPropertyValue(memgraph::storage::v3::LabelId label,
|
||||
const std::pair<std::string, memgraph::storage::v3::PropertyId> &prop_pair,
|
||||
memgraph::query::v2::Expression *expression)
|
||||
: label_(label), property_(prop_pair.second), expression_(expression) {}
|
||||
|
||||
void ExpectOp(ScanAllByLabelPropertyValue &scan_all, const SymbolTable &) override {
|
||||
EXPECT_EQ(scan_all.label_, label_);
|
||||
EXPECT_EQ(scan_all.property_, property_);
|
||||
// TODO: Proper expression equality
|
||||
EXPECT_EQ(typeid(scan_all.expression_).hash_code(), typeid(expression_).hash_code());
|
||||
}
|
||||
|
||||
private:
|
||||
memgraph::storage::v3::LabelId label_;
|
||||
memgraph::storage::v3::PropertyId property_;
|
||||
memgraph::query::v2::Expression *expression_;
|
||||
};
|
||||
|
||||
class ExpectScanByPrimaryKey : public OpChecker<v2::plan::ScanByPrimaryKey> {
|
||||
public:
|
||||
ExpectScanByPrimaryKey(memgraph::storage::v3::LabelId label, const std::vector<Expression *> &properties)
|
||||
: label_(label), properties_(properties) {}
|
||||
|
||||
void ExpectOp(v2::plan::ScanByPrimaryKey &scan_all, const SymbolTable &) override {
|
||||
EXPECT_EQ(scan_all.label_, label_);
|
||||
|
||||
bool primary_property_match = true;
|
||||
for (const auto &expected_prop : properties_) {
|
||||
bool has_match = false;
|
||||
for (const auto &prop : scan_all.primary_key_) {
|
||||
if (typeid(prop).hash_code() == typeid(expected_prop).hash_code()) {
|
||||
has_match = true;
|
||||
}
|
||||
}
|
||||
if (!has_match) {
|
||||
primary_property_match = false;
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_TRUE(primary_property_match);
|
||||
}
|
||||
|
||||
private:
|
||||
memgraph::storage::v3::LabelId label_;
|
||||
std::vector<Expression *> properties_;
|
||||
};
|
||||
|
||||
class ExpectCartesian : public OpChecker<Cartesian> {
|
||||
public:
|
||||
ExpectCartesian(const std::list<std::unique_ptr<BaseOpChecker>> &left,
|
||||
const std::list<std::unique_ptr<BaseOpChecker>> &right)
|
||||
: left_(left), right_(right) {}
|
||||
|
||||
void ExpectOp(Cartesian &op, const SymbolTable &symbol_table) override {
|
||||
ASSERT_TRUE(op.left_op_);
|
||||
PlanChecker left_checker(left_, symbol_table);
|
||||
op.left_op_->Accept(left_checker);
|
||||
ASSERT_TRUE(op.right_op_);
|
||||
PlanChecker right_checker(right_, symbol_table);
|
||||
op.right_op_->Accept(right_checker);
|
||||
}
|
||||
|
||||
private:
|
||||
const std::list<std::unique_ptr<BaseOpChecker>> &left_;
|
||||
const std::list<std::unique_ptr<BaseOpChecker>> &right_;
|
||||
};
|
||||
|
||||
class ExpectCallProcedure : public OpChecker<CallProcedure> {
|
||||
public:
|
||||
ExpectCallProcedure(const std::string &name, const std::vector<memgraph::query::Expression *> &args,
|
||||
const std::vector<std::string> &fields, const std::vector<Symbol> &result_syms)
|
||||
: name_(name), args_(args), fields_(fields), result_syms_(result_syms) {}
|
||||
|
||||
void ExpectOp(CallProcedure &op, const SymbolTable &symbol_table) override {
|
||||
EXPECT_EQ(op.procedure_name_, name_);
|
||||
EXPECT_EQ(op.arguments_.size(), args_.size());
|
||||
for (size_t i = 0; i < args_.size(); ++i) {
|
||||
const auto *op_arg = op.arguments_[i];
|
||||
const auto *expected_arg = args_[i];
|
||||
// TODO: Proper expression equality
|
||||
EXPECT_EQ(op_arg->GetTypeInfo(), expected_arg->GetTypeInfo());
|
||||
}
|
||||
EXPECT_EQ(op.result_fields_, fields_);
|
||||
EXPECT_EQ(op.result_symbols_, result_syms_);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
std::vector<memgraph::query::Expression *> args_;
|
||||
std::vector<std::string> fields_;
|
||||
std::vector<Symbol> result_syms_;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
std::list<std::unique_ptr<BaseOpChecker>> MakeCheckers(T arg) {
|
||||
std::list<std::unique_ptr<BaseOpChecker>> l;
|
||||
l.emplace_back(std::make_unique<T>(arg));
|
||||
return l;
|
||||
}
|
||||
|
||||
template <class T, class... Rest>
|
||||
std::list<std::unique_ptr<BaseOpChecker>> MakeCheckers(T arg, Rest &&...rest) {
|
||||
auto l = MakeCheckers(std::forward<Rest>(rest)...);
|
||||
l.emplace_front(std::make_unique<T>(arg));
|
||||
return std::move(l);
|
||||
}
|
||||
|
||||
template <class TPlanner, class TDbAccessor>
|
||||
TPlanner MakePlanner(TDbAccessor *dba, AstStorage &storage, SymbolTable &symbol_table, CypherQuery *query) {
|
||||
auto planning_context = MakePlanningContext(&storage, &symbol_table, query, dba);
|
||||
auto query_parts = CollectQueryParts(symbol_table, storage, query);
|
||||
auto single_query_parts = query_parts.query_parts.at(0).single_query_parts;
|
||||
return TPlanner(single_query_parts, planning_context);
|
||||
}
|
||||
|
||||
class FakeDistributedDbAccessor {
|
||||
public:
|
||||
int64_t VerticesCount(memgraph::storage::v3::LabelId label) const {
|
||||
auto found = label_index_.find(label);
|
||||
if (found != label_index_.end()) return found->second;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int64_t VerticesCount(memgraph::storage::v3::LabelId label, memgraph::storage::v3::PropertyId property) const {
|
||||
for (auto &index : label_property_index_) {
|
||||
if (std::get<0>(index) == label && std::get<1>(index) == property) {
|
||||
return std::get<2>(index);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool LabelIndexExists(memgraph::storage::v3::LabelId label) const {
|
||||
throw utils::NotYetImplemented("Label indicies are yet to be implemented.");
|
||||
}
|
||||
|
||||
bool LabelPropertyIndexExists(memgraph::storage::v3::LabelId label,
|
||||
memgraph::storage::v3::PropertyId property) const {
|
||||
for (auto &index : label_property_index_) {
|
||||
if (std::get<0>(index) == label && std::get<1>(index) == property) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PrimaryLabelExists(storage::v3::LabelId label) { return label_index_.find(label) != label_index_.end(); }
|
||||
|
||||
void SetIndexCount(memgraph::storage::v3::LabelId label, int64_t count) { label_index_[label] = count; }
|
||||
|
||||
void SetIndexCount(memgraph::storage::v3::LabelId label, memgraph::storage::v3::PropertyId property, int64_t count) {
|
||||
for (auto &index : label_property_index_) {
|
||||
if (std::get<0>(index) == label && std::get<1>(index) == property) {
|
||||
std::get<2>(index) = count;
|
||||
return;
|
||||
}
|
||||
}
|
||||
label_property_index_.emplace_back(label, property, count);
|
||||
}
|
||||
|
||||
memgraph::storage::v3::LabelId NameToLabel(const std::string &name) {
|
||||
auto found = primary_labels_.find(name);
|
||||
if (found != primary_labels_.end()) return found->second;
|
||||
return primary_labels_.emplace(name, memgraph::storage::v3::LabelId::FromUint(primary_labels_.size()))
|
||||
.first->second;
|
||||
}
|
||||
|
||||
memgraph::storage::v3::LabelId Label(const std::string &name) { return NameToLabel(name); }
|
||||
|
||||
memgraph::storage::v3::EdgeTypeId NameToEdgeType(const std::string &name) {
|
||||
auto found = edge_types_.find(name);
|
||||
if (found != edge_types_.end()) return found->second;
|
||||
return edge_types_.emplace(name, memgraph::storage::v3::EdgeTypeId::FromUint(edge_types_.size())).first->second;
|
||||
}
|
||||
|
||||
memgraph::storage::v3::PropertyId NameToPrimaryProperty(const std::string &name) {
|
||||
auto found = primary_properties_.find(name);
|
||||
if (found != primary_properties_.end()) return found->second;
|
||||
return primary_properties_.emplace(name, memgraph::storage::v3::PropertyId::FromUint(primary_properties_.size()))
|
||||
.first->second;
|
||||
}
|
||||
|
||||
memgraph::storage::v3::PropertyId NameToSecondaryProperty(const std::string &name) {
|
||||
auto found = secondary_properties_.find(name);
|
||||
if (found != secondary_properties_.end()) return found->second;
|
||||
return secondary_properties_
|
||||
.emplace(name, memgraph::storage::v3::PropertyId::FromUint(secondary_properties_.size()))
|
||||
.first->second;
|
||||
}
|
||||
|
||||
memgraph::storage::v3::PropertyId PrimaryProperty(const std::string &name) { return NameToPrimaryProperty(name); }
|
||||
memgraph::storage::v3::PropertyId SecondaryProperty(const std::string &name) { return NameToSecondaryProperty(name); }
|
||||
|
||||
std::string PrimaryPropertyToName(memgraph::storage::v3::PropertyId property) const {
|
||||
for (const auto &kv : primary_properties_) {
|
||||
if (kv.second == property) return kv.first;
|
||||
}
|
||||
LOG_FATAL("Unable to find primary property name");
|
||||
}
|
||||
|
||||
std::string SecondaryPropertyToName(memgraph::storage::v3::PropertyId property) const {
|
||||
for (const auto &kv : secondary_properties_) {
|
||||
if (kv.second == property) return kv.first;
|
||||
}
|
||||
LOG_FATAL("Unable to find secondary property name");
|
||||
}
|
||||
|
||||
std::string PrimaryPropertyName(memgraph::storage::v3::PropertyId property) const {
|
||||
return PrimaryPropertyToName(property);
|
||||
}
|
||||
std::string SecondaryPropertyName(memgraph::storage::v3::PropertyId property) const {
|
||||
return SecondaryPropertyToName(property);
|
||||
}
|
||||
|
||||
memgraph::storage::v3::PropertyId NameToProperty(const std::string &name) {
|
||||
auto find_in_prim_properties = primary_properties_.find(name);
|
||||
if (find_in_prim_properties != primary_properties_.end()) {
|
||||
return find_in_prim_properties->second;
|
||||
}
|
||||
auto find_in_secondary_properties = secondary_properties_.find(name);
|
||||
if (find_in_secondary_properties != secondary_properties_.end()) {
|
||||
return find_in_secondary_properties->second;
|
||||
}
|
||||
|
||||
LOG_FATAL("The property does not exist as a primary or a secondary property.");
|
||||
return memgraph::storage::v3::PropertyId::FromUint(0);
|
||||
}
|
||||
|
||||
std::vector<memgraph::storage::v3::SchemaProperty> GetSchemaForLabel(storage::v3::LabelId label) {
|
||||
auto schema_properties = schemas_.at(label);
|
||||
std::vector<memgraph::storage::v3::SchemaProperty> ret;
|
||||
std::transform(schema_properties.begin(), schema_properties.end(), std::back_inserter(ret), [](const auto &prop) {
|
||||
memgraph::storage::v3::SchemaProperty schema_prop = {
|
||||
.property_id = prop,
|
||||
// This should not be hardcoded, but for testing purposes it will suffice.
|
||||
.type = memgraph::common::SchemaType::INT};
|
||||
|
||||
return schema_prop;
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<std::pair<query::v2::Expression *, query::v2::plan::FilterInfo>> ExtractPrimaryKey(
|
||||
storage::v3::LabelId label, std::vector<query::v2::plan::FilterInfo> property_filters) {
|
||||
MG_ASSERT(schemas_.contains(label),
|
||||
"You did not specify the Schema for this label! Use FakeDistributedDbAccessor::CreateSchema(...).");
|
||||
|
||||
std::vector<std::pair<query::v2::Expression *, query::v2::plan::FilterInfo>> pk;
|
||||
const auto schema = GetSchemaPropertiesForLabel(label);
|
||||
|
||||
std::vector<storage::v3::PropertyId> schema_properties;
|
||||
schema_properties.reserve(schema.size());
|
||||
|
||||
std::transform(schema.begin(), schema.end(), std::back_inserter(schema_properties),
|
||||
[](const auto &schema_elem) { return schema_elem; });
|
||||
|
||||
for (const auto &property_filter : property_filters) {
|
||||
const auto &property_id = NameToProperty(property_filter.property_filter->property_.name);
|
||||
if (std::find(schema_properties.begin(), schema_properties.end(), property_id) != schema_properties.end()) {
|
||||
pk.emplace_back(std::make_pair(property_filter.expression, property_filter));
|
||||
}
|
||||
}
|
||||
|
||||
return pk.size() == schema_properties.size()
|
||||
? pk
|
||||
: std::vector<std::pair<query::v2::Expression *, query::v2::plan::FilterInfo>>{};
|
||||
}
|
||||
|
||||
std::vector<memgraph::storage::v3::PropertyId> GetSchemaPropertiesForLabel(storage::v3::LabelId label) {
|
||||
return schemas_.at(label);
|
||||
}
|
||||
|
||||
void CreateSchema(const memgraph::storage::v3::LabelId primary_label,
|
||||
const std::vector<memgraph::storage::v3::PropertyId> &schemas_types) {
|
||||
MG_ASSERT(!schemas_.contains(primary_label), "You already created the schema for this label!");
|
||||
schemas_.emplace(primary_label, schemas_types);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, memgraph::storage::v3::LabelId> primary_labels_;
|
||||
std::unordered_map<std::string, memgraph::storage::v3::LabelId> secondary_labels_;
|
||||
std::unordered_map<std::string, memgraph::storage::v3::EdgeTypeId> edge_types_;
|
||||
std::unordered_map<std::string, memgraph::storage::v3::PropertyId> primary_properties_;
|
||||
std::unordered_map<std::string, memgraph::storage::v3::PropertyId> secondary_properties_;
|
||||
|
||||
std::unordered_map<memgraph::storage::v3::LabelId, int64_t> label_index_;
|
||||
std::vector<std::tuple<memgraph::storage::v3::LabelId, memgraph::storage::v3::PropertyId, int64_t>>
|
||||
label_property_index_;
|
||||
|
||||
std::unordered_map<memgraph::storage::v3::LabelId, std::vector<memgraph::storage::v3::PropertyId>> schemas_;
|
||||
};
|
||||
|
||||
} // namespace memgraph::query::v2::plan
|
603
tests/unit/query_v2_common.hpp
Normal file
603
tests/unit/query_v2_common.hpp
Normal file
@ -0,0 +1,603 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
/// @file
|
||||
/// This file provides macros for easier construction of openCypher query AST.
|
||||
/// The usage of macros is very similar to how one would write openCypher. For
|
||||
/// example:
|
||||
///
|
||||
/// AstStorage storage; // Macros rely on storage being in scope.
|
||||
/// // PROPERTY_LOOKUP and PROPERTY_PAIR macros
|
||||
/// // rely on a DbAccessor *reference* named dba.
|
||||
/// database::GraphDb db;
|
||||
/// auto dba_ptr = db.Access();
|
||||
/// auto &dba = *dba_ptr;
|
||||
///
|
||||
/// QUERY(MATCH(PATTERN(NODE("n"), EDGE("e"), NODE("m"))),
|
||||
/// WHERE(LESS(PROPERTY_LOOKUP("e", edge_prop), LITERAL(3))),
|
||||
/// RETURN(SUM(PROPERTY_LOOKUP("m", prop)), AS("sum"),
|
||||
/// ORDER_BY(IDENT("sum")),
|
||||
/// SKIP(ADD(LITERAL(1), LITERAL(2)))));
|
||||
///
|
||||
/// Each of the macros is accompanied by a function. The functions use overload
|
||||
/// resolution and template magic to provide a type safe way of constructing
|
||||
/// queries. Although the functions can be used by themselves, it is more
|
||||
/// convenient to use the macros.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "query/frontend/ast/pretty_print.hpp" // not sure if that is ok...
|
||||
#include "query/v2/frontend/ast/ast.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
#include "query/v2/frontend/ast/ast.hpp"
|
||||
|
||||
namespace memgraph::query::v2 {
|
||||
|
||||
namespace test_common {
|
||||
|
||||
auto ToIntList(const TypedValue &t) {
|
||||
std::vector<int64_t> list;
|
||||
for (auto x : t.ValueList()) {
|
||||
list.push_back(x.ValueInt());
|
||||
}
|
||||
return list;
|
||||
};
|
||||
|
||||
auto ToIntMap(const TypedValue &t) {
|
||||
std::map<std::string, int64_t> map;
|
||||
for (const auto &kv : t.ValueMap()) map.emplace(kv.first, kv.second.ValueInt());
|
||||
return map;
|
||||
};
|
||||
|
||||
std::string ToString(Expression *expr) {
|
||||
std::ostringstream ss;
|
||||
// PrintExpression(expr, &ss);
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string ToString(NamedExpression *expr) {
|
||||
std::ostringstream ss;
|
||||
// PrintExpression(expr, &ss);
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
// Custom types for ORDER BY, SKIP, LIMIT, ON MATCH and ON CREATE expressions,
|
||||
// so that they can be used to resolve function calls.
|
||||
struct OrderBy {
|
||||
std::vector<SortItem> expressions;
|
||||
};
|
||||
|
||||
struct Skip {
|
||||
Expression *expression = nullptr;
|
||||
};
|
||||
|
||||
struct Limit {
|
||||
Expression *expression = nullptr;
|
||||
};
|
||||
|
||||
struct OnMatch {
|
||||
std::vector<Clause *> set;
|
||||
};
|
||||
struct OnCreate {
|
||||
std::vector<Clause *> set;
|
||||
};
|
||||
|
||||
// Helper functions for filling the OrderBy with expressions.
|
||||
auto FillOrderBy(OrderBy &order_by, Expression *expression, Ordering ordering = Ordering::ASC) {
|
||||
order_by.expressions.push_back({ordering, expression});
|
||||
}
|
||||
template <class... T>
|
||||
auto FillOrderBy(OrderBy &order_by, Expression *expression, Ordering ordering, T... rest) {
|
||||
FillOrderBy(order_by, expression, ordering);
|
||||
FillOrderBy(order_by, rest...);
|
||||
}
|
||||
template <class... T>
|
||||
auto FillOrderBy(OrderBy &order_by, Expression *expression, T... rest) {
|
||||
FillOrderBy(order_by, expression);
|
||||
FillOrderBy(order_by, rest...);
|
||||
}
|
||||
|
||||
/// Create OrderBy expressions.
|
||||
///
|
||||
/// The supported combination of arguments is: (Expression, [Ordering])+
|
||||
/// Since the Ordering is optional, by default it is ascending.
|
||||
template <class... T>
|
||||
auto GetOrderBy(T... exprs) {
|
||||
OrderBy order_by;
|
||||
FillOrderBy(order_by, exprs...);
|
||||
return order_by;
|
||||
}
|
||||
|
||||
/// Create PropertyLookup with given name and property.
|
||||
///
|
||||
/// Name is used to create the Identifier which is used for property lookup.
|
||||
template <class TDbAccessor>
|
||||
auto GetPropertyLookup(AstStorage &storage, TDbAccessor &dba, const std::string &name,
|
||||
memgraph::storage::v3::PropertyId property) {
|
||||
return storage.Create<PropertyLookup>(storage.Create<Identifier>(name),
|
||||
storage.GetPropertyIx(dba.PropertyToName(property)));
|
||||
}
|
||||
|
||||
template <class TDbAccessor>
|
||||
auto GetPropertyLookup(AstStorage &storage, TDbAccessor &dba, Expression *expr,
|
||||
memgraph::storage::v3::PropertyId property) {
|
||||
return storage.Create<PropertyLookup>(expr, storage.GetPropertyIx(dba.PropertyToName(property)));
|
||||
}
|
||||
|
||||
template <class TDbAccessor>
|
||||
auto GetPropertyLookup(AstStorage &storage, TDbAccessor &dba, Expression *expr, const std::string &property) {
|
||||
return storage.Create<PropertyLookup>(expr, storage.GetPropertyIx(property));
|
||||
}
|
||||
|
||||
template <class TDbAccessor>
|
||||
auto GetPropertyLookup(AstStorage &storage, TDbAccessor &, const std::string &name,
|
||||
const std::pair<std::string, memgraph::storage::v3::PropertyId> &prop_pair) {
|
||||
return storage.Create<PropertyLookup>(storage.Create<Identifier>(name), storage.GetPropertyIx(prop_pair.first));
|
||||
}
|
||||
|
||||
template <class TDbAccessor>
|
||||
auto GetPropertyLookup(AstStorage &storage, TDbAccessor &, Expression *expr,
|
||||
const std::pair<std::string, memgraph::storage::v3::PropertyId> &prop_pair) {
|
||||
return storage.Create<PropertyLookup>(expr, storage.GetPropertyIx(prop_pair.first));
|
||||
}
|
||||
|
||||
/// Create an EdgeAtom with given name, direction and edge_type.
|
||||
///
|
||||
/// Name is used to create the Identifier which is assigned to the edge.
|
||||
auto GetEdge(AstStorage &storage, const std::string &name, EdgeAtom::Direction dir = EdgeAtom::Direction::BOTH,
|
||||
const std::vector<std::string> &edge_types = {}) {
|
||||
std::vector<EdgeTypeIx> types;
|
||||
types.reserve(edge_types.size());
|
||||
for (const auto &type : edge_types) {
|
||||
types.push_back(storage.GetEdgeTypeIx(type));
|
||||
}
|
||||
return storage.Create<EdgeAtom>(storage.Create<Identifier>(name), EdgeAtom::Type::SINGLE, dir, types);
|
||||
}
|
||||
|
||||
/// Create a variable length expansion EdgeAtom with given name, direction and
|
||||
/// edge_type.
|
||||
///
|
||||
/// Name is used to create the Identifier which is assigned to the edge.
|
||||
auto GetEdgeVariable(AstStorage &storage, const std::string &name, EdgeAtom::Type type = EdgeAtom::Type::DEPTH_FIRST,
|
||||
EdgeAtom::Direction dir = EdgeAtom::Direction::BOTH,
|
||||
const std::vector<std::string> &edge_types = {}, Identifier *flambda_inner_edge = nullptr,
|
||||
Identifier *flambda_inner_node = nullptr, Identifier *wlambda_inner_edge = nullptr,
|
||||
Identifier *wlambda_inner_node = nullptr, Expression *wlambda_expression = nullptr,
|
||||
Identifier *total_weight = nullptr) {
|
||||
std::vector<EdgeTypeIx> types;
|
||||
types.reserve(edge_types.size());
|
||||
for (const auto &type : edge_types) {
|
||||
types.push_back(storage.GetEdgeTypeIx(type));
|
||||
}
|
||||
auto r_val = storage.Create<EdgeAtom>(storage.Create<Identifier>(name), type, dir, types);
|
||||
|
||||
r_val->filter_lambda_.inner_edge =
|
||||
flambda_inner_edge ? flambda_inner_edge : storage.Create<Identifier>(memgraph::utils::RandomString(20));
|
||||
r_val->filter_lambda_.inner_node =
|
||||
flambda_inner_node ? flambda_inner_node : storage.Create<Identifier>(memgraph::utils::RandomString(20));
|
||||
|
||||
if (type == EdgeAtom::Type::WEIGHTED_SHORTEST_PATH) {
|
||||
r_val->weight_lambda_.inner_edge =
|
||||
wlambda_inner_edge ? wlambda_inner_edge : storage.Create<Identifier>(memgraph::utils::RandomString(20));
|
||||
r_val->weight_lambda_.inner_node =
|
||||
wlambda_inner_node ? wlambda_inner_node : storage.Create<Identifier>(memgraph::utils::RandomString(20));
|
||||
r_val->weight_lambda_.expression =
|
||||
wlambda_expression ? wlambda_expression : storage.Create<memgraph::query::v2::PrimitiveLiteral>(1);
|
||||
|
||||
r_val->total_weight_ = total_weight;
|
||||
}
|
||||
|
||||
return r_val;
|
||||
}
|
||||
|
||||
/// Create a NodeAtom with given name and label.
|
||||
///
|
||||
/// Name is used to create the Identifier which is assigned to the node.
|
||||
auto GetNode(AstStorage &storage, const std::string &name, std::optional<std::string> label = std::nullopt) {
|
||||
auto node = storage.Create<NodeAtom>(storage.Create<Identifier>(name));
|
||||
if (label) node->labels_.emplace_back(storage.GetLabelIx(*label));
|
||||
return node;
|
||||
}
|
||||
|
||||
/// Create a Pattern with given atoms.
|
||||
auto GetPattern(AstStorage &storage, std::vector<PatternAtom *> atoms) {
|
||||
auto pattern = storage.Create<Pattern>();
|
||||
pattern->identifier_ = storage.Create<Identifier>(memgraph::utils::RandomString(20), false);
|
||||
pattern->atoms_.insert(pattern->atoms_.begin(), atoms.begin(), atoms.end());
|
||||
return pattern;
|
||||
}
|
||||
|
||||
/// Create a Pattern with given name and atoms.
|
||||
auto GetPattern(AstStorage &storage, const std::string &name, std::vector<PatternAtom *> atoms) {
|
||||
auto pattern = storage.Create<Pattern>();
|
||||
pattern->identifier_ = storage.Create<Identifier>(name, true);
|
||||
pattern->atoms_.insert(pattern->atoms_.begin(), atoms.begin(), atoms.end());
|
||||
return pattern;
|
||||
}
|
||||
|
||||
/// This function fills an AST node which with given patterns.
|
||||
///
|
||||
/// The function is most commonly used to create Match and Create clauses.
|
||||
template <class TWithPatterns>
|
||||
auto GetWithPatterns(TWithPatterns *with_patterns, std::vector<Pattern *> patterns) {
|
||||
with_patterns->patterns_.insert(with_patterns->patterns_.begin(), patterns.begin(), patterns.end());
|
||||
return with_patterns;
|
||||
}
|
||||
|
||||
/// Create a query with given clauses.
|
||||
|
||||
auto GetSingleQuery(SingleQuery *single_query, Clause *clause) {
|
||||
single_query->clauses_.emplace_back(clause);
|
||||
return single_query;
|
||||
}
|
||||
auto GetSingleQuery(SingleQuery *single_query, Match *match, Where *where) {
|
||||
match->where_ = where;
|
||||
single_query->clauses_.emplace_back(match);
|
||||
return single_query;
|
||||
}
|
||||
auto GetSingleQuery(SingleQuery *single_query, With *with, Where *where) {
|
||||
with->where_ = where;
|
||||
single_query->clauses_.emplace_back(with);
|
||||
return single_query;
|
||||
}
|
||||
template <class... T>
|
||||
auto GetSingleQuery(SingleQuery *single_query, Match *match, Where *where, T *...clauses) {
|
||||
match->where_ = where;
|
||||
single_query->clauses_.emplace_back(match);
|
||||
return GetSingleQuery(single_query, clauses...);
|
||||
}
|
||||
template <class... T>
|
||||
auto GetSingleQuery(SingleQuery *single_query, With *with, Where *where, T *...clauses) {
|
||||
with->where_ = where;
|
||||
single_query->clauses_.emplace_back(with);
|
||||
return GetSingleQuery(single_query, clauses...);
|
||||
}
|
||||
|
||||
template <class... T>
|
||||
auto GetSingleQuery(SingleQuery *single_query, Clause *clause, T *...clauses) {
|
||||
single_query->clauses_.emplace_back(clause);
|
||||
return GetSingleQuery(single_query, clauses...);
|
||||
}
|
||||
|
||||
auto GetCypherUnion(CypherUnion *cypher_union, SingleQuery *single_query) {
|
||||
cypher_union->single_query_ = single_query;
|
||||
return cypher_union;
|
||||
}
|
||||
|
||||
auto GetQuery(AstStorage &storage, SingleQuery *single_query) {
|
||||
auto *query = storage.Create<CypherQuery>();
|
||||
query->single_query_ = single_query;
|
||||
return query;
|
||||
}
|
||||
|
||||
template <class... T>
|
||||
auto GetQuery(AstStorage &storage, SingleQuery *single_query, T *...cypher_unions) {
|
||||
auto *query = storage.Create<CypherQuery>();
|
||||
query->single_query_ = single_query;
|
||||
query->cypher_unions_ = std::vector<CypherUnion *>{cypher_unions...};
|
||||
return query;
|
||||
}
|
||||
|
||||
// Helper functions for constructing RETURN and WITH clauses.
|
||||
void FillReturnBody(AstStorage &, ReturnBody &body, NamedExpression *named_expr) {
|
||||
body.named_expressions.emplace_back(named_expr);
|
||||
}
|
||||
void FillReturnBody(AstStorage &storage, ReturnBody &body, const std::string &name) {
|
||||
if (name == "*") {
|
||||
body.all_identifiers = true;
|
||||
} else {
|
||||
auto *ident = storage.Create<memgraph::query::v2::Identifier>(name);
|
||||
auto *named_expr = storage.Create<memgraph::query::v2::NamedExpression>(name, ident);
|
||||
body.named_expressions.emplace_back(named_expr);
|
||||
}
|
||||
}
|
||||
void FillReturnBody(AstStorage &, ReturnBody &body, Limit limit) { body.limit = limit.expression; }
|
||||
void FillReturnBody(AstStorage &, ReturnBody &body, Skip skip, Limit limit = Limit{}) {
|
||||
body.skip = skip.expression;
|
||||
body.limit = limit.expression;
|
||||
}
|
||||
void FillReturnBody(AstStorage &, ReturnBody &body, OrderBy order_by, Limit limit = Limit{}) {
|
||||
body.order_by = order_by.expressions;
|
||||
body.limit = limit.expression;
|
||||
}
|
||||
void FillReturnBody(AstStorage &, ReturnBody &body, OrderBy order_by, Skip skip, Limit limit = Limit{}) {
|
||||
body.order_by = order_by.expressions;
|
||||
body.skip = skip.expression;
|
||||
body.limit = limit.expression;
|
||||
}
|
||||
void FillReturnBody(AstStorage &, ReturnBody &body, Expression *expr, NamedExpression *named_expr) {
|
||||
// This overload supports `RETURN(expr, AS(name))` construct, since
|
||||
// NamedExpression does not inherit Expression.
|
||||
named_expr->expression_ = expr;
|
||||
body.named_expressions.emplace_back(named_expr);
|
||||
}
|
||||
void FillReturnBody(AstStorage &storage, ReturnBody &body, const std::string &name, NamedExpression *named_expr) {
|
||||
named_expr->expression_ = storage.Create<memgraph::query::v2::Identifier>(name);
|
||||
body.named_expressions.emplace_back(named_expr);
|
||||
}
|
||||
template <class... T>
|
||||
void FillReturnBody(AstStorage &storage, ReturnBody &body, Expression *expr, NamedExpression *named_expr, T... rest) {
|
||||
named_expr->expression_ = expr;
|
||||
body.named_expressions.emplace_back(named_expr);
|
||||
FillReturnBody(storage, body, rest...);
|
||||
}
|
||||
template <class... T>
|
||||
void FillReturnBody(AstStorage &storage, ReturnBody &body, NamedExpression *named_expr, T... rest) {
|
||||
body.named_expressions.emplace_back(named_expr);
|
||||
FillReturnBody(storage, body, rest...);
|
||||
}
|
||||
template <class... T>
|
||||
void FillReturnBody(AstStorage &storage, ReturnBody &body, const std::string &name, NamedExpression *named_expr,
|
||||
T... rest) {
|
||||
named_expr->expression_ = storage.Create<memgraph::query::v2::Identifier>(name);
|
||||
body.named_expressions.emplace_back(named_expr);
|
||||
FillReturnBody(storage, body, rest...);
|
||||
}
|
||||
template <class... T>
|
||||
void FillReturnBody(AstStorage &storage, ReturnBody &body, const std::string &name, T... rest) {
|
||||
auto *ident = storage.Create<memgraph::query::v2::Identifier>(name);
|
||||
auto *named_expr = storage.Create<memgraph::query::v2::NamedExpression>(name, ident);
|
||||
body.named_expressions.emplace_back(named_expr);
|
||||
FillReturnBody(storage, body, rest...);
|
||||
}
|
||||
|
||||
/// Create the return clause with given expressions.
|
||||
///
|
||||
/// The supported expression combination of arguments is:
|
||||
///
|
||||
/// (String | NamedExpression | (Expression NamedExpression))+
|
||||
/// [OrderBy] [Skip] [Limit]
|
||||
///
|
||||
/// When the pair (Expression NamedExpression) is given, the Expression will be
|
||||
/// moved inside the NamedExpression. This is done, so that the constructs like
|
||||
/// RETURN(expr, AS("name"), ...) are supported. Taking a String is a shorthand
|
||||
/// for RETURN(IDENT(string), AS(string), ....).
|
||||
///
|
||||
/// @sa GetWith
|
||||
template <class... T>
|
||||
auto GetReturn(AstStorage &storage, bool distinct, T... exprs) {
|
||||
auto ret = storage.Create<Return>();
|
||||
ret->body_.distinct = distinct;
|
||||
FillReturnBody(storage, ret->body_, exprs...);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Create the with clause with given expressions.
|
||||
///
|
||||
/// The supported expression combination is the same as for @c GetReturn.
|
||||
///
|
||||
/// @sa GetReturn
|
||||
template <class... T>
|
||||
auto GetWith(AstStorage &storage, bool distinct, T... exprs) {
|
||||
auto with = storage.Create<With>();
|
||||
with->body_.distinct = distinct;
|
||||
FillReturnBody(storage, with->body_, exprs...);
|
||||
return with;
|
||||
}
|
||||
|
||||
/// Create the UNWIND clause with given named expression.
|
||||
auto GetUnwind(AstStorage &storage, NamedExpression *named_expr) {
|
||||
return storage.Create<memgraph::query::v2::Unwind>(named_expr);
|
||||
}
|
||||
auto GetUnwind(AstStorage &storage, Expression *expr, NamedExpression *as) {
|
||||
as->expression_ = expr;
|
||||
return GetUnwind(storage, as);
|
||||
}
|
||||
|
||||
/// Create the delete clause with given named expressions.
|
||||
auto GetDelete(AstStorage &storage, std::vector<Expression *> exprs, bool detach = false) {
|
||||
auto del = storage.Create<Delete>();
|
||||
del->expressions_.insert(del->expressions_.begin(), exprs.begin(), exprs.end());
|
||||
del->detach_ = detach;
|
||||
return del;
|
||||
}
|
||||
|
||||
/// Create a set property clause for given property lookup and the right hand
|
||||
/// side expression.
|
||||
auto GetSet(AstStorage &storage, PropertyLookup *prop_lookup, Expression *expr) {
|
||||
return storage.Create<SetProperty>(prop_lookup, expr);
|
||||
}
|
||||
|
||||
/// Create a set properties clause for given identifier name and the right hand
|
||||
/// side expression.
|
||||
auto GetSet(AstStorage &storage, const std::string &name, Expression *expr, bool update = false) {
|
||||
return storage.Create<SetProperties>(storage.Create<Identifier>(name), expr, update);
|
||||
}
|
||||
|
||||
/// Create a set labels clause for given identifier name and labels.
|
||||
auto GetSet(AstStorage &storage, const std::string &name, std::vector<std::string> label_names) {
|
||||
std::vector<LabelIx> labels;
|
||||
labels.reserve(label_names.size());
|
||||
for (const auto &label : label_names) {
|
||||
labels.push_back(storage.GetLabelIx(label));
|
||||
}
|
||||
return storage.Create<SetLabels>(storage.Create<Identifier>(name), labels);
|
||||
}
|
||||
|
||||
/// Create a remove property clause for given property lookup
|
||||
auto GetRemove(AstStorage &storage, PropertyLookup *prop_lookup) { return storage.Create<RemoveProperty>(prop_lookup); }
|
||||
|
||||
/// Create a remove labels clause for given identifier name and labels.
|
||||
auto GetRemove(AstStorage &storage, const std::string &name, std::vector<std::string> label_names) {
|
||||
std::vector<LabelIx> labels;
|
||||
labels.reserve(label_names.size());
|
||||
for (const auto &label : label_names) {
|
||||
labels.push_back(storage.GetLabelIx(label));
|
||||
}
|
||||
return storage.Create<RemoveLabels>(storage.Create<Identifier>(name), labels);
|
||||
}
|
||||
|
||||
/// Create a Merge clause for given Pattern with optional OnMatch and OnCreate
|
||||
/// parts.
|
||||
auto GetMerge(AstStorage &storage, Pattern *pattern, OnCreate on_create = OnCreate{}) {
|
||||
auto *merge = storage.Create<memgraph::query::v2::Merge>();
|
||||
merge->pattern_ = pattern;
|
||||
merge->on_create_ = on_create.set;
|
||||
return merge;
|
||||
}
|
||||
auto GetMerge(AstStorage &storage, Pattern *pattern, OnMatch on_match, OnCreate on_create = OnCreate{}) {
|
||||
auto *merge = storage.Create<memgraph::query::v2::Merge>();
|
||||
merge->pattern_ = pattern;
|
||||
merge->on_match_ = on_match.set;
|
||||
merge->on_create_ = on_create.set;
|
||||
return merge;
|
||||
}
|
||||
|
||||
auto GetCallProcedure(AstStorage &storage, std::string procedure_name,
|
||||
std::vector<memgraph::query::v2::Expression *> arguments = {}) {
|
||||
auto *call_procedure = storage.Create<memgraph::query::v2::CallProcedure>();
|
||||
call_procedure->procedure_name_ = std::move(procedure_name);
|
||||
call_procedure->arguments_ = std::move(arguments);
|
||||
return call_procedure;
|
||||
}
|
||||
|
||||
/// Create the FOREACH clause with given named expression.
|
||||
auto GetForeach(AstStorage &storage, NamedExpression *named_expr, const std::vector<query::v2::Clause *> &clauses) {
|
||||
return storage.Create<query::v2::Foreach>(named_expr, clauses);
|
||||
}
|
||||
|
||||
} // namespace test_common
|
||||
|
||||
} // namespace memgraph::query::v2
|
||||
|
||||
/// All the following macros implicitly pass `storage` variable to functions.
|
||||
/// You need to have `AstStorage storage;` somewhere in scope to use them.
|
||||
/// Refer to function documentation to see what the macro does.
|
||||
///
|
||||
/// Example usage:
|
||||
///
|
||||
/// // Create MATCH (n) -[r]- (m) RETURN m AS new_name
|
||||
/// AstStorage storage;
|
||||
/// auto query = QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m"))),
|
||||
/// RETURN(NEXPR("new_name"), IDENT("m")));
|
||||
#define NODE(...) memgraph::expr::test_common::GetNode(storage, __VA_ARGS__)
|
||||
#define EDGE(...) memgraph::expr::test_common::GetEdge(storage, __VA_ARGS__)
|
||||
#define EDGE_VARIABLE(...) memgraph::expr::test_common::GetEdgeVariable(storage, __VA_ARGS__)
|
||||
#define PATTERN(...) memgraph::expr::test_common::GetPattern(storage, {__VA_ARGS__})
|
||||
#define PATTERN(...) memgraph::expr::test_common::GetPattern(storage, {__VA_ARGS__})
|
||||
#define NAMED_PATTERN(name, ...) memgraph::expr::test_common::GetPattern(storage, name, {__VA_ARGS__})
|
||||
#define OPTIONAL_MATCH(...) \
|
||||
memgraph::expr::test_common::GetWithPatterns(storage.Create<memgraph::query::v2::Match>(true), {__VA_ARGS__})
|
||||
#define MATCH(...) \
|
||||
memgraph::expr::test_common::GetWithPatterns(storage.Create<memgraph::query::v2::Match>(), {__VA_ARGS__})
|
||||
#define WHERE(expr) storage.Create<memgraph::query::v2::Where>((expr))
|
||||
#define CREATE(...) \
|
||||
memgraph::expr::test_common::GetWithPatterns(storage.Create<memgraph::query::v2::Create>(), {__VA_ARGS__})
|
||||
#define IDENT(...) storage.Create<memgraph::query::v2::Identifier>(__VA_ARGS__)
|
||||
#define LITERAL(val) storage.Create<memgraph::query::v2::PrimitiveLiteral>((val))
|
||||
#define LIST(...) \
|
||||
storage.Create<memgraph::query::v2::ListLiteral>(std::vector<memgraph::query::v2::Expression *>{__VA_ARGS__})
|
||||
#define MAP(...) \
|
||||
storage.Create<memgraph::query::v2::MapLiteral>( \
|
||||
std::unordered_map<memgraph::query::v2::PropertyIx, memgraph::query::v2::Expression *>{__VA_ARGS__})
|
||||
#define PROPERTY_PAIR(property_name) \
|
||||
std::make_pair(property_name, dba.NameToProperty(property_name)) // This one might not be needed at all
|
||||
#define PRIMARY_PROPERTY_PAIR(property_name) std::make_pair(property_name, dba.NameToPrimaryProperty(property_name))
|
||||
#define SECONDARY_PROPERTY_PAIR(property_name) std::make_pair(property_name, dba.NameToSecondaryProperty(property_name))
|
||||
#define PROPERTY_LOOKUP(...) memgraph::expr::test_common::GetPropertyLookup(storage, dba, __VA_ARGS__)
|
||||
#define PARAMETER_LOOKUP(token_position) storage.Create<memgraph::query::v2::ParameterLookup>((token_position))
|
||||
#define NEXPR(name, expr) storage.Create<memgraph::query::v2::NamedExpression>((name), (expr))
|
||||
// AS is alternative to NEXPR which does not initialize NamedExpression with
|
||||
// Expression. It should be used with RETURN or WITH. For example:
|
||||
// RETURN(IDENT("n"), AS("n")) vs. RETURN(NEXPR("n", IDENT("n"))).
|
||||
#define AS(name) storage.Create<memgraph::query::v2::NamedExpression>((name))
|
||||
#define RETURN(...) memgraph::expr::test_common::GetReturn(storage, false, __VA_ARGS__)
|
||||
#define WITH(...) memgraph::expr::test_common::GetWith(storage, false, __VA_ARGS__)
|
||||
#define RETURN_DISTINCT(...) memgraph::expr::test_common::GetReturn(storage, true, __VA_ARGS__)
|
||||
#define WITH_DISTINCT(...) memgraph::expr::test_common::GetWith(storage, true, __VA_ARGS__)
|
||||
#define UNWIND(...) memgraph::expr::test_common::GetUnwind(storage, __VA_ARGS__)
|
||||
#define ORDER_BY(...) memgraph::expr::test_common::GetOrderBy(__VA_ARGS__)
|
||||
#define SKIP(expr) \
|
||||
memgraph::expr::test_common::Skip { (expr) }
|
||||
#define LIMIT(expr) \
|
||||
memgraph::expr::test_common::Limit { (expr) }
|
||||
#define DELETE(...) memgraph::expr::test_common::GetDelete(storage, {__VA_ARGS__})
|
||||
#define DETACH_DELETE(...) memgraph::expr::test_common::GetDelete(storage, {__VA_ARGS__}, true)
|
||||
#define SET(...) memgraph::expr::test_common::GetSet(storage, __VA_ARGS__)
|
||||
#define REMOVE(...) memgraph::expr::test_common::GetRemove(storage, __VA_ARGS__)
|
||||
#define MERGE(...) memgraph::expr::test_common::GetMerge(storage, __VA_ARGS__)
|
||||
#define ON_MATCH(...) \
|
||||
memgraph::expr::test_common::OnMatch { \
|
||||
std::vector<memgraph::query::v2::Clause *> { __VA_ARGS__ } \
|
||||
}
|
||||
#define ON_CREATE(...) \
|
||||
memgraph::expr::test_common::OnCreate { \
|
||||
std::vector<memgraph::query::v2::Clause *> { __VA_ARGS__ } \
|
||||
}
|
||||
#define CREATE_INDEX_ON(label, property) \
|
||||
storage.Create<memgraph::query::v2::IndexQuery>(memgraph::query::v2::IndexQuery::Action::CREATE, (label), \
|
||||
std::vector<memgraph::query::v2::PropertyIx>{(property)})
|
||||
#define QUERY(...) memgraph::expr::test_common::GetQuery(storage, __VA_ARGS__)
|
||||
#define SINGLE_QUERY(...) memgraph::expr::test_common::GetSingleQuery(storage.Create<SingleQuery>(), __VA_ARGS__)
|
||||
#define UNION(...) memgraph::expr::test_common::GetCypherUnion(storage.Create<CypherUnion>(true), __VA_ARGS__)
|
||||
#define UNION_ALL(...) memgraph::expr::test_common::GetCypherUnion(storage.Create<CypherUnion>(false), __VA_ARGS__)
|
||||
#define FOREACH(...) memgraph::expr::test_common::GetForeach(storage, __VA_ARGS__)
|
||||
// Various operators
|
||||
#define NOT(expr) storage.Create<memgraph::query::v2::NotOperator>((expr))
|
||||
#define UPLUS(expr) storage.Create<memgraph::query::v2::UnaryPlusOperator>((expr))
|
||||
#define UMINUS(expr) storage.Create<memgraph::query::v2::UnaryMinusOperator>((expr))
|
||||
#define IS_NULL(expr) storage.Create<memgraph::query::v2::IsNullOperator>((expr))
|
||||
#define ADD(expr1, expr2) storage.Create<memgraph::query::v2::AdditionOperator>((expr1), (expr2))
|
||||
#define LESS(expr1, expr2) storage.Create<memgraph::query::v2::LessOperator>((expr1), (expr2))
|
||||
#define LESS_EQ(expr1, expr2) storage.Create<memgraph::query::v2::LessEqualOperator>((expr1), (expr2))
|
||||
#define GREATER(expr1, expr2) storage.Create<memgraph::query::v2::GreaterOperator>((expr1), (expr2))
|
||||
#define GREATER_EQ(expr1, expr2) storage.Create<memgraph::query::v2::GreaterEqualOperator>((expr1), (expr2))
|
||||
#define SUM(expr) \
|
||||
storage.Create<memgraph::query::v2::Aggregation>((expr), nullptr, memgraph::query::v2::Aggregation::Op::SUM)
|
||||
#define COUNT(expr) \
|
||||
storage.Create<memgraph::query::v2::Aggregation>((expr), nullptr, memgraph::query::v2::Aggregation::Op::COUNT)
|
||||
#define AVG(expr) \
|
||||
storage.Create<memgraph::query::v2::Aggregation>((expr), nullptr, memgraph::query::v2::Aggregation::Op::AVG)
|
||||
#define COLLECT_LIST(expr) \
|
||||
storage.Create<memgraph::query::v2::Aggregation>((expr), nullptr, memgraph::query::v2::Aggregation::Op::COLLECT_LIST)
|
||||
#define EQ(expr1, expr2) storage.Create<memgraph::query::v2::EqualOperator>((expr1), (expr2))
|
||||
#define NEQ(expr1, expr2) storage.Create<memgraph::query::v2::NotEqualOperator>((expr1), (expr2))
|
||||
#define AND(expr1, expr2) storage.Create<memgraph::query::v2::AndOperator>((expr1), (expr2))
|
||||
#define OR(expr1, expr2) storage.Create<memgraph::query::v2::OrOperator>((expr1), (expr2))
|
||||
#define IN_LIST(expr1, expr2) storage.Create<memgraph::query::v2::InListOperator>((expr1), (expr2))
|
||||
#define IF(cond, then, else) storage.Create<memgraph::query::v2::IfOperator>((cond), (then), (else))
|
||||
// Function call
|
||||
#define FN(function_name, ...) \
|
||||
storage.Create<memgraph::query::v2::Function>(memgraph::utils::ToUpperCase(function_name), \
|
||||
std::vector<memgraph::query::v2::Expression *>{__VA_ARGS__})
|
||||
// List slicing
|
||||
#define SLICE(list, lower_bound, upper_bound) \
|
||||
storage.Create<memgraph::query::v2::ListSlicingOperator>(list, lower_bound, upper_bound)
|
||||
// all(variable IN list WHERE predicate)
|
||||
#define ALL(variable, list, where) \
|
||||
storage.Create<memgraph::query::v2::All>(storage.Create<memgraph::query::v2::Identifier>(variable), list, where)
|
||||
#define SINGLE(variable, list, where) \
|
||||
storage.Create<memgraph::query::v2::Single>(storage.Create<memgraph::query::v2::Identifier>(variable), list, where)
|
||||
#define ANY(variable, list, where) \
|
||||
storage.Create<memgraph::query::v2::Any>(storage.Create<memgraph::query::v2::Identifier>(variable), list, where)
|
||||
#define NONE(variable, list, where) \
|
||||
storage.Create<memgraph::query::v2::None>(storage.Create<memgraph::query::v2::Identifier>(variable), list, where)
|
||||
#define REDUCE(accumulator, initializer, variable, list, expr) \
|
||||
storage.Create<memgraph::query::v2::Reduce>(storage.Create<memgraph::query::v2::Identifier>(accumulator), \
|
||||
initializer, storage.Create<memgraph::query::v2::Identifier>(variable), \
|
||||
list, expr)
|
||||
#define COALESCE(...) \
|
||||
storage.Create<memgraph::query::v2::Coalesce>(std::vector<memgraph::query::v2::Expression *>{__VA_ARGS__})
|
||||
#define EXTRACT(variable, list, expr) \
|
||||
storage.Create<memgraph::query::v2::Extract>(storage.Create<memgraph::query::v2::Identifier>(variable), list, expr)
|
||||
#define AUTH_QUERY(action, user, role, user_or_role, password, privileges) \
|
||||
storage.Create<memgraph::query::v2::AuthQuery>((action), (user), (role), (user_or_role), password, (privileges))
|
||||
#define DROP_USER(usernames) storage.Create<memgraph::query::v2::DropUser>((usernames))
|
||||
#define CALL_PROCEDURE(...) memgraph::query::v2::test_common::GetCallProcedure(storage, __VA_ARGS__)
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@ -125,6 +125,11 @@ class MockedRequestRouter : public RequestRouterInterface {
|
||||
|
||||
bool IsPrimaryKey(LabelId primary_label, PropertyId property) const override { return true; }
|
||||
|
||||
const std::vector<coordinator::SchemaProperty> &GetSchemaForLabel(storage::v3::LabelId /*label*/) const override {
|
||||
static std::vector<coordinator::SchemaProperty> schema;
|
||||
return schema;
|
||||
};
|
||||
|
||||
private:
|
||||
void SetUpNameIdMappers() {
|
||||
std::unordered_map<uint64_t, std::string> id_to_name;
|
||||
|
178
tests/unit/query_v2_plan.cpp
Normal file
178
tests/unit/query_v2_plan.cpp
Normal file
@ -0,0 +1,178 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "query_plan_checker_v2.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <sstream>
|
||||
#include <tuple>
|
||||
#include <typeinfo>
|
||||
#include <unordered_set>
|
||||
#include <variant>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "expr/semantic/symbol_generator.hpp"
|
||||
#include "query/frontend/semantic/symbol_table.hpp"
|
||||
#include "query/v2/frontend/ast/ast.hpp"
|
||||
#include "query/v2/plan/operator.hpp"
|
||||
#include "query/v2/plan/planner.hpp"
|
||||
|
||||
#include "query_v2_common.hpp"
|
||||
|
||||
namespace memgraph::query {
|
||||
::std::ostream &operator<<(::std::ostream &os, const Symbol &sym) {
|
||||
return os << "Symbol{\"" << sym.name() << "\" [" << sym.position() << "] " << Symbol::TypeToString(sym.type()) << "}";
|
||||
}
|
||||
} // namespace memgraph::query
|
||||
|
||||
// using namespace memgraph::query::v2::plan;
|
||||
using namespace memgraph::expr::plan;
|
||||
using memgraph::query::Symbol;
|
||||
using memgraph::query::SymbolGenerator;
|
||||
using memgraph::query::v2::AstStorage;
|
||||
using memgraph::query::v2::SingleQuery;
|
||||
using memgraph::query::v2::SymbolTable;
|
||||
using Type = memgraph::query::v2::EdgeAtom::Type;
|
||||
using Direction = memgraph::query::v2::EdgeAtom::Direction;
|
||||
using Bound = ScanAllByLabelPropertyRange::Bound;
|
||||
|
||||
namespace {
|
||||
|
||||
class Planner {
|
||||
public:
|
||||
template <class TDbAccessor>
|
||||
Planner(std::vector<SingleQueryPart> single_query_parts, PlanningContext<TDbAccessor> context) {
|
||||
memgraph::expr::Parameters parameters;
|
||||
PostProcessor post_processor(parameters);
|
||||
plan_ = MakeLogicalPlanForSingleQuery<RuleBasedPlanner>(single_query_parts, &context);
|
||||
plan_ = post_processor.Rewrite(std::move(plan_), &context);
|
||||
}
|
||||
|
||||
auto &plan() { return *plan_; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<LogicalOperator> plan_;
|
||||
};
|
||||
|
||||
template <class... TChecker>
|
||||
auto CheckPlan(LogicalOperator &plan, const SymbolTable &symbol_table, TChecker... checker) {
|
||||
std::list<BaseOpChecker *> checkers{&checker...};
|
||||
PlanChecker plan_checker(checkers, symbol_table);
|
||||
plan.Accept(plan_checker);
|
||||
EXPECT_TRUE(plan_checker.checkers_.empty());
|
||||
}
|
||||
|
||||
template <class TPlanner, class... TChecker>
|
||||
auto CheckPlan(memgraph::query::v2::CypherQuery *query, AstStorage &storage, TChecker... checker) {
|
||||
auto symbol_table = memgraph::expr::MakeSymbolTable(query);
|
||||
FakeDistributedDbAccessor dba;
|
||||
auto planner = MakePlanner<TPlanner>(&dba, storage, symbol_table, query);
|
||||
CheckPlan(planner.plan(), symbol_table, checker...);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
class TestPlanner : public ::testing::Test {};
|
||||
|
||||
using PlannerTypes = ::testing::Types<Planner>;
|
||||
|
||||
TYPED_TEST_CASE(TestPlanner, PlannerTypes);
|
||||
|
||||
TYPED_TEST(TestPlanner, MatchFilterPropIsNotNull) {
|
||||
const char *prim_label_name = "prim_label_one";
|
||||
// Exact primary key match, one elem as PK.
|
||||
{
|
||||
FakeDistributedDbAccessor dba;
|
||||
auto label = dba.Label(prim_label_name);
|
||||
auto prim_prop_one = PRIMARY_PROPERTY_PAIR("prim_prop_one");
|
||||
|
||||
dba.SetIndexCount(label, 1);
|
||||
dba.SetIndexCount(label, prim_prop_one.second, 1);
|
||||
|
||||
dba.CreateSchema(label, {prim_prop_one.second});
|
||||
|
||||
memgraph::query::v2::AstStorage storage;
|
||||
|
||||
memgraph::query::v2::Expression *expected_primary_key;
|
||||
expected_primary_key = PROPERTY_LOOKUP("n", prim_prop_one);
|
||||
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", prim_label_name))),
|
||||
WHERE(EQ(PROPERTY_LOOKUP("n", prim_prop_one), LITERAL(1))), RETURN("n")));
|
||||
auto symbol_table = (memgraph::expr::MakeSymbolTable(query));
|
||||
auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query);
|
||||
CheckPlan(planner.plan(), symbol_table, ExpectScanByPrimaryKey(label, {expected_primary_key}), ExpectProduce());
|
||||
}
|
||||
// Exact primary key match, two elem as PK.
|
||||
{
|
||||
FakeDistributedDbAccessor dba;
|
||||
auto label = dba.Label(prim_label_name);
|
||||
auto prim_prop_one = PRIMARY_PROPERTY_PAIR("prim_prop_one");
|
||||
|
||||
auto prim_prop_two = PRIMARY_PROPERTY_PAIR("prim_prop_two");
|
||||
auto sec_prop_one = PRIMARY_PROPERTY_PAIR("sec_prop_one");
|
||||
auto sec_prop_two = PRIMARY_PROPERTY_PAIR("sec_prop_two");
|
||||
auto sec_prop_three = PRIMARY_PROPERTY_PAIR("sec_prop_three");
|
||||
|
||||
dba.SetIndexCount(label, 1);
|
||||
dba.SetIndexCount(label, prim_prop_one.second, 1);
|
||||
|
||||
dba.CreateSchema(label, {prim_prop_one.second, prim_prop_two.second});
|
||||
|
||||
dba.SetIndexCount(label, prim_prop_two.second, 1);
|
||||
dba.SetIndexCount(label, sec_prop_one.second, 1);
|
||||
dba.SetIndexCount(label, sec_prop_two.second, 1);
|
||||
dba.SetIndexCount(label, sec_prop_three.second, 1);
|
||||
memgraph::query::v2::AstStorage storage;
|
||||
|
||||
memgraph::query::v2::Expression *expected_primary_key;
|
||||
expected_primary_key = PROPERTY_LOOKUP("n", prim_prop_one);
|
||||
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", prim_label_name))),
|
||||
WHERE(AND(EQ(PROPERTY_LOOKUP("n", prim_prop_one), LITERAL(1)),
|
||||
EQ(PROPERTY_LOOKUP("n", prim_prop_two), LITERAL(1)))),
|
||||
RETURN("n")));
|
||||
auto symbol_table = (memgraph::expr::MakeSymbolTable(query));
|
||||
auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query);
|
||||
CheckPlan(planner.plan(), symbol_table, ExpectScanByPrimaryKey(label, {expected_primary_key}), ExpectProduce());
|
||||
}
|
||||
// One elem is missing from PK, default to ScanAllByLabelPropertyValue.
|
||||
{
|
||||
FakeDistributedDbAccessor dba;
|
||||
auto label = dba.Label(prim_label_name);
|
||||
|
||||
auto prim_prop_one = PRIMARY_PROPERTY_PAIR("prim_prop_one");
|
||||
auto prim_prop_two = PRIMARY_PROPERTY_PAIR("prim_prop_two");
|
||||
|
||||
auto sec_prop_one = PRIMARY_PROPERTY_PAIR("sec_prop_one");
|
||||
auto sec_prop_two = PRIMARY_PROPERTY_PAIR("sec_prop_two");
|
||||
auto sec_prop_three = PRIMARY_PROPERTY_PAIR("sec_prop_three");
|
||||
|
||||
dba.SetIndexCount(label, 1);
|
||||
dba.SetIndexCount(label, prim_prop_one.second, 1);
|
||||
|
||||
dba.CreateSchema(label, {prim_prop_one.second, prim_prop_two.second});
|
||||
|
||||
dba.SetIndexCount(label, prim_prop_two.second, 1);
|
||||
dba.SetIndexCount(label, sec_prop_one.second, 1);
|
||||
dba.SetIndexCount(label, sec_prop_two.second, 1);
|
||||
dba.SetIndexCount(label, sec_prop_three.second, 1);
|
||||
memgraph::query::v2::AstStorage storage;
|
||||
|
||||
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", prim_label_name))),
|
||||
WHERE(EQ(PROPERTY_LOOKUP("n", prim_prop_one), LITERAL(1))), RETURN("n")));
|
||||
auto symbol_table = (memgraph::expr::MakeSymbolTable(query));
|
||||
auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query);
|
||||
CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelPropertyValue(label, prim_prop_one, IDENT("n")),
|
||||
ExpectProduce());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
Loading…
Reference in New Issue
Block a user