From 925835b08031505c130132485ac0aad3b0dbee51 Mon Sep 17 00:00:00 2001 From: Kostas Kyrimis <kostaskyrim@gmail.com> Date: Thu, 22 Sep 2022 15:05:43 +0200 Subject: [PATCH] Implement query engine client (#531) - Add shard request manager --- src/coordinator/coordinator.hpp | 5 + src/coordinator/coordinator_client.hpp | 1 - src/coordinator/shard_map.hpp | 20 + src/expr/interpret/eval.hpp | 98 +- src/expr/typed_value.hpp | 12 +- src/io/transport.hpp | 2 +- src/query/v2/CMakeLists.txt | 21 +- src/query/v2/accessors.cpp | 75 + src/query/v2/accessors.hpp | 187 ++ src/query/v2/bindings/eval.hpp | 26 +- src/query/v2/bindings/typed_value.cpp | 4 +- src/query/v2/bindings/typed_value.hpp | 9 +- src/query/v2/context.hpp | 6 +- src/query/v2/conversions.hpp | 100 + .../frontend/semantic/required_privileges.cpp | 8 +- .../interpret/awesome_memgraph_functions.cpp | 174 +- src/query/v2/interpreter.cpp | 487 +---- src/query/v2/interpreter.hpp | 8 - src/query/v2/plan/operator.cpp | 1839 +++-------------- src/query/v2/plan/operator.lcp | 11 +- src/query/v2/plan/rule_based_planner.hpp | 1 + src/query/v2/requests.hpp | 69 +- src/query/v2/shard_request_manager.hpp | 405 ++++ src/storage/v3/conversions.hpp | 37 + src/storage/v3/shard_rsm.cpp | 112 +- src/storage/v3/value_conversions.hpp | 131 ++ tests/simulation/CMakeLists.txt | 16 +- tests/simulation/common.hpp | 126 ++ tests/simulation/shard_request_manager.cpp | 320 +++ tests/unit/CMakeLists.txt | 62 +- tests/unit/query_v2_dummy_test.cpp | 36 + tests/unit/storage_v3_expr.cpp | 4 +- 32 files changed, 2009 insertions(+), 2403 deletions(-) create mode 100644 src/query/v2/accessors.cpp create mode 100644 src/query/v2/accessors.hpp create mode 100644 src/query/v2/conversions.hpp create mode 100644 src/query/v2/shard_request_manager.hpp create mode 100644 src/storage/v3/value_conversions.hpp create mode 100644 tests/simulation/common.hpp create mode 100644 tests/simulation/shard_request_manager.cpp create mode 100644 tests/unit/query_v2_dummy_test.cpp diff --git a/src/coordinator/coordinator.hpp b/src/coordinator/coordinator.hpp index f363d799e..31e20bb9f 100644 --- a/src/coordinator/coordinator.hpp +++ b/src/coordinator/coordinator.hpp @@ -54,6 +54,11 @@ struct GetShardMapResponse { ShardMap shard_map; }; +struct AllocateHlcBatchRequest { + Hlc low; + Hlc high; +}; + struct AllocateHlcBatchResponse { bool success; Hlc low; diff --git a/src/coordinator/coordinator_client.hpp b/src/coordinator/coordinator_client.hpp index f73ecfcd2..1f7499e69 100644 --- a/src/coordinator/coordinator_client.hpp +++ b/src/coordinator/coordinator_client.hpp @@ -21,5 +21,4 @@ using memgraph::io::rsm::RsmClient; template <typename IoImpl> using CoordinatorClient = RsmClient<IoImpl, CoordinatorWriteRequests, CoordinatorWriteResponses, CoordinatorReadRequests, CoordinatorReadResponses>; - } // namespace memgraph::coordinator diff --git a/src/coordinator/shard_map.hpp b/src/coordinator/shard_map.hpp index a842d798d..44759fcca 100644 --- a/src/coordinator/shard_map.hpp +++ b/src/coordinator/shard_map.hpp @@ -48,6 +48,7 @@ enum class Status : uint8_t { struct AddressAndStatus { memgraph::io::Address address; Status status; + friend bool operator<(const AddressAndStatus &lhs, const AddressAndStatus &rhs) { return lhs.address < rhs.address; } }; using PrimaryKey = std::vector<PropertyValue>; @@ -82,6 +83,12 @@ struct ShardMap { std::map<LabelId, LabelSpace> label_spaces; std::map<LabelId, std::vector<SchemaProperty>> schemas; + Shards GetShards(const LabelName &label) { + const auto id = labels.at(label); + auto &shards = label_spaces.at(id).shards; + return shards; + } + // TODO(gabor) later we will want to update the wallclock time with // the given Io<impl>'s time as well Hlc IncrementShardMapVersion() noexcept { @@ -183,6 +190,8 @@ struct ShardMap { // Find a random place for the server to plug in } + LabelId GetLabelId(const std::string &label) const { return labels.at(label); } + Shards GetShardsForRange(const LabelName &label_name, const PrimaryKey &start_key, const PrimaryKey &end_key) const { MG_ASSERT(start_key <= end_key); MG_ASSERT(labels.contains(label_name)); @@ -219,6 +228,17 @@ struct ShardMap { return std::prev(label_space.shards.upper_bound(key))->second; } + Shard GetShardForKey(const LabelId &label_id, const PrimaryKey &key) const { + MG_ASSERT(label_spaces.contains(label_id)); + + const auto &label_space = label_spaces.at(label_id); + + MG_ASSERT(label_space.shards.begin()->first <= key, + "the ShardMap must always contain a minimal key that is less than or equal to any requested key"); + + return std::prev(label_space.shards.upper_bound(key))->second; + } + PropertyMap AllocatePropertyIds(const std::vector<PropertyName> &new_properties) { PropertyMap ret{}; diff --git a/src/expr/interpret/eval.hpp b/src/expr/interpret/eval.hpp index aca15c5e9..253c2e86d 100644 --- a/src/expr/interpret/eval.hpp +++ b/src/expr/interpret/eval.hpp @@ -17,6 +17,7 @@ #include <map> #include <optional> #include <regex> +#include <type_traits> #include <vector> #include "expr/ast.hpp" @@ -27,8 +28,11 @@ namespace memgraph::expr { +struct StorageTag {}; +struct QueryEngineTag {}; + template <typename TypedValue, typename EvaluationContext, typename DbAccessor, typename StorageView, typename LabelId, - typename PropertyValue, typename ConvFunctor, typename Error> + typename PropertyValue, typename ConvFunctor, typename Error, typename Tag = StorageTag> class ExpressionEvaluator : public ExpressionVisitor<TypedValue> { public: ExpressionEvaluator(Frame<TypedValue> *frame, const SymbolTable &symbol_table, const EvaluationContext &ctx, @@ -377,6 +381,43 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> { } } + template <typename VertexAccessor, typename TTag = Tag, + typename TReturnType = std::enable_if_t<std::is_same_v<TTag, StorageTag>, bool>> + TReturnType HasLabelImpl(const VertexAccessor &vertex, const LabelIx &label, StorageTag /*tag*/) { + auto has_label = vertex.HasLabel(view_, GetLabel(label)); + if (has_label.HasError() && has_label.GetError() == Error::NONEXISTENT_OBJECT) { + // This is a very nasty and temporary hack in order to make MERGE + // work. The old storage had the following logic when returning an + // `OLD` view: `return old ? old : new`. That means that if the + // `OLD` view didn't exist, it returned the NEW view. With this hack + // we simulate that behavior. + // TODO (mferencevic, teon.banek): Remove once MERGE is + // reimplemented. + has_label = vertex.HasLabel(StorageView::NEW, GetLabel(label)); + } + if (has_label.HasError()) { + switch (has_label.GetError()) { + case Error::DELETED_OBJECT: + throw ExpressionRuntimeException("Trying to access labels on a deleted node."); + case Error::NONEXISTENT_OBJECT: + throw ExpressionRuntimeException("Trying to access labels from a node that doesn't exist."); + case Error::SERIALIZATION_ERROR: + case Error::VERTEX_HAS_EDGES: + case Error::PROPERTIES_DISABLED: + throw ExpressionRuntimeException("Unexpected error when accessing labels."); + } + } + return *has_label; + } + + template <typename VertexAccessor, typename TTag = Tag, + typename TReturnType = std::enable_if_t<std::is_same_v<TTag, QueryEngineTag>, bool>> + TReturnType HasLabelImpl(const VertexAccessor &vertex, const LabelIx &label_ix, QueryEngineTag /*tag*/) { + auto label = typename VertexAccessor::Label{LabelId::FromUint(label_ix.ix)}; + auto has_label = vertex.HasLabel(label); + return !has_label; + } + TypedValue Visit(LabelsTest &labels_test) override { auto expression_result = labels_test.expression_->Accept(*this); switch (expression_result.type()) { @@ -384,35 +425,12 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> { return TypedValue(ctx_->memory); case TypedValue::Type::Vertex: { const auto &vertex = expression_result.ValueVertex(); - for (const auto &label : labels_test.labels_) { - auto has_label = vertex.HasLabel(view_, GetLabel(label)); - if (has_label.HasError() && has_label.GetError() == Error::NONEXISTENT_OBJECT) { - // This is a very nasty and temporary hack in order to make MERGE - // work. The old storage had the following logic when returning an - // `OLD` view: `return old ? old : new`. That means that if the - // `OLD` view didn't exist, it returned the NEW view. With this hack - // we simulate that behavior. - // TODO (mferencevic, teon.banek): Remove once MERGE is - // reimplemented. - has_label = vertex.HasLabel(StorageView::NEW, GetLabel(label)); - } - if (has_label.HasError()) { - switch (has_label.GetError()) { - case Error::DELETED_OBJECT: - throw ExpressionRuntimeException("Trying to access labels on a deleted node."); - case Error::NONEXISTENT_OBJECT: - throw ExpressionRuntimeException("Trying to access labels from a node that doesn't exist."); - case Error::SERIALIZATION_ERROR: - case Error::VERTEX_HAS_EDGES: - case Error::PROPERTIES_DISABLED: - throw ExpressionRuntimeException("Unexpected error when accessing labels."); - } - } - if (!*has_label) { - return TypedValue(false, ctx_->memory); - } + if (std::ranges::all_of(labels_test.labels_, [&vertex, this](const auto label_test) { + return this->HasLabelImpl(vertex, label_test, Tag{}); + })) { + return TypedValue(true, ctx_->memory); } - return TypedValue(true, ctx_->memory); + return TypedValue(false, ctx_->memory); } default: throw ExpressionRuntimeException("Only nodes have labels."); @@ -695,7 +713,24 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> { } private: - template <class TRecordAccessor> + template <class TRecordAccessor, class TTag = Tag, + class TReturnType = std::enable_if_t<std::is_same_v<TTag, QueryEngineTag>, TypedValue>> + TReturnType GetProperty(const TRecordAccessor &record_accessor, PropertyIx prop) { + auto maybe_prop = record_accessor.GetProperty(prop.name); + // Handler non existent property + return conv_(maybe_prop); + } + + template <class TRecordAccessor, class TTag = Tag, + class TReturnType = std::enable_if_t<std::is_same_v<TTag, QueryEngineTag>, TypedValue>> + TReturnType GetProperty(const TRecordAccessor &record_accessor, const std::string_view name) { + auto maybe_prop = record_accessor.GetProperty(std::string(name)); + // Handler non existent property + return conv_(maybe_prop); + } + + template <class TRecordAccessor, class TTag = Tag, + class TReturnType = std::enable_if_t<std::is_same_v<TTag, StorageTag>, TypedValue>> TypedValue GetProperty(const TRecordAccessor &record_accessor, PropertyIx prop) { auto maybe_prop = record_accessor.GetProperty(view_, ctx_->properties[prop.ix]); if (maybe_prop.HasError() && maybe_prop.GetError() == Error::NONEXISTENT_OBJECT) { @@ -722,7 +757,8 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> { return conv_(*maybe_prop, ctx_->memory); } - template <class TRecordAccessor> + template <class TRecordAccessor, class TTag = Tag, + class TReturnType = std::enable_if_t<std::is_same_v<TTag, StorageTag>, TypedValue>> TypedValue GetProperty(const TRecordAccessor &record_accessor, const std::string_view name) { auto maybe_prop = record_accessor.GetProperty(view_, dba_->NameToProperty(name)); if (maybe_prop.HasError() && maybe_prop.GetError() == Error::NONEXISTENT_OBJECT) { diff --git a/src/expr/typed_value.hpp b/src/expr/typed_value.hpp index 092926803..b851278cb 100644 --- a/src/expr/typed_value.hpp +++ b/src/expr/typed_value.hpp @@ -116,13 +116,15 @@ class TypedValueT { return hash; } case TypedValueT::Type::Vertex: + return 34; case TypedValueT::Type::Edge: - return 0; + return 35; case TypedValueT::Type::Path: { - const auto &vertices = value.ValuePath().vertices(); - const auto &edges = value.ValuePath().edges(); - return utils::FnvCollection<decltype(vertices), TVertexAccessor>{}(vertices) ^ - utils::FnvCollection<decltype(edges), TEdgeAccessor>{}(edges); + // const auto &vertices = value.ValuePath().vertices(); + // const auto &edges = value.ValuePath().edges(); + // return utils::FnvCollection<decltype(vertices), TVertexAccessor>{}(vertices) ^ + // utils::FnvCollection<decltype(edges), TEdgeAccessor>{}(edges); + return 36; } case TypedValueT::Type::Date: return utils::DateHash{}(value.ValueDate()); diff --git a/src/io/transport.hpp b/src/io/transport.hpp index 87e6bc06f..07b5df660 100644 --- a/src/io/transport.hpp +++ b/src/io/transport.hpp @@ -67,7 +67,7 @@ class Io { I implementation_; Address address_; RequestId request_id_counter_ = 0; - Duration default_timeout_ = std::chrono::microseconds{50000}; + Duration default_timeout_ = std::chrono::microseconds{100000}; public: Io(I io, Address address) : implementation_(io), address_(address) {} diff --git a/src/query/v2/CMakeLists.txt b/src/query/v2/CMakeLists.txt index 93c08495b..ebe7e41e4 100644 --- a/src/query/v2/CMakeLists.txt +++ b/src/query/v2/CMakeLists.txt @@ -23,17 +23,18 @@ set(mg_query_v2_sources plan/rewrite/index_lookup.cpp plan/rule_based_planner.cpp plan/variable_start_planner.cpp - procedure/mg_procedure_impl.cpp - procedure/mg_procedure_helpers.cpp - procedure/module.cpp - procedure/py_module.cpp + # procedure/mg_procedure_impl.cpp + # procedure/mg_procedure_helpers.cpp + # procedure/module.cpp + # procedure/py_module.cpp serialization/property_value.cpp - stream/streams.cpp - stream/sources.cpp - stream/common.cpp - trigger.cpp - trigger_context.cpp - bindings/typed_value.cpp) + # stream/streams.cpp + # stream/sources.cpp + # stream/common.cpp + # trigger.cpp + # trigger_context.cpp + bindings/typed_value.cpp + accessors.cpp) find_package(Boost REQUIRED) diff --git a/src/query/v2/accessors.cpp b/src/query/v2/accessors.cpp new file mode 100644 index 000000000..1fd4a3d91 --- /dev/null +++ b/src/query/v2/accessors.cpp @@ -0,0 +1,75 @@ +// Copyright 2022 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include "query/v2/accessors.hpp" +#include "query/v2/requests.hpp" + +namespace memgraph::query::v2::accessors { +EdgeAccessor::EdgeAccessor(Edge edge, std::vector<std::pair<PropertyId, Value>> props) + : edge(std::move(edge)), properties(std::move(props)) {} + +uint64_t EdgeAccessor::EdgeType() const { return edge.type.id; } + +std::vector<std::pair<PropertyId, Value>> EdgeAccessor::Properties() const { + return properties; + // std::map<std::string, TypedValue> res; + // for (const auto &[name, value] : *properties) { + // res[name] = ValueToTypedValue(value); + // } + // return res; +} + +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) +Value EdgeAccessor::GetProperty(const std::string & /*prop_name*/) const { + // TODO(kostasrim) fix this + return {}; +} + +Edge EdgeAccessor::GetEdge() const { return edge; } + +VertexAccessor EdgeAccessor::To() const { return VertexAccessor(Vertex{edge.dst}, {}); } + +VertexAccessor EdgeAccessor::From() const { return VertexAccessor(Vertex{edge.src}, {}); } + +VertexAccessor::VertexAccessor(Vertex v, std::vector<std::pair<PropertyId, Value>> props) + : vertex(std::move(v)), properties(std::move(props)) {} + +std::vector<Label> VertexAccessor::Labels() const { return vertex.labels; } + +bool VertexAccessor::HasLabel(Label &label) const { + return std::find_if(vertex.labels.begin(), vertex.labels.end(), + [label](const auto &l) { return l.id == label.id; }) != vertex.labels.end(); +} + +std::vector<std::pair<PropertyId, Value>> VertexAccessor::Properties() const { + // std::map<std::string, TypedValue> res; + // for (const auto &[name, value] : *properties) { + // res[name] = ValueToTypedValue(value); + // } + // return res; + return properties; +} + +Value VertexAccessor::GetProperty(PropertyId prop_id) const { + return std::find_if(properties.begin(), properties.end(), [&](auto &pr) { return prop_id == pr.first; })->second; + // return ValueToTypedValue(properties[prop_name]); +} + +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) +Value VertexAccessor::GetProperty(const std::string & /*prop_name*/) const { + // TODO(kostasrim) Add string mapping + return {}; + // return ValueToTypedValue(properties[prop_name]); +} + +msgs::Vertex VertexAccessor::GetVertex() const { return vertex; } + +} // namespace memgraph::query::v2::accessors diff --git a/src/query/v2/accessors.hpp b/src/query/v2/accessors.hpp new file mode 100644 index 000000000..1d545650f --- /dev/null +++ b/src/query/v2/accessors.hpp @@ -0,0 +1,187 @@ +// 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. + +#pragma once + +#include <optional> +#include <utility> +#include <vector> + +#include "query/exceptions.hpp" +#include "query/v2/requests.hpp" +#include "storage/v3/view.hpp" +#include "utils/bound.hpp" +#include "utils/exceptions.hpp" +#include "utils/memory.hpp" +#include "utils/memory_tracker.hpp" + +namespace memgraph::query::v2::accessors { + +using Value = memgraph::msgs::Value; +using Edge = memgraph::msgs::Edge; +using Vertex = memgraph::msgs::Vertex; +using Label = memgraph::msgs::Label; +using PropertyId = memgraph::msgs::PropertyId; + +class VertexAccessor; + +class EdgeAccessor final { + public: + EdgeAccessor(Edge edge, std::vector<std::pair<PropertyId, Value>> props); + + uint64_t EdgeType() const; + + std::vector<std::pair<PropertyId, Value>> Properties() const; + + Value GetProperty(const std::string &prop_name) const; + + Edge GetEdge() const; + + // Dummy function + // NOLINTNEXTLINE(readability-convert-member-functions-to-static) + inline size_t CypherId() const { return 10; } + + // bool HasSrcAccessor const { return src == nullptr; } + // bool HasDstAccessor const { return dst == nullptr; } + + VertexAccessor To() const; + VertexAccessor From() const; + + friend bool operator==(const EdgeAccessor &lhs, const EdgeAccessor &rhs) { + return lhs.edge == rhs.edge && lhs.properties == rhs.properties; + } + + friend bool operator!=(const EdgeAccessor &lhs, const EdgeAccessor &rhs) { return !(lhs == rhs); } + + private: + Edge edge; + std::vector<std::pair<PropertyId, Value>> properties; +}; + +class VertexAccessor final { + public: + using PropertyId = msgs::PropertyId; + using Label = msgs::Label; + VertexAccessor(Vertex v, std::vector<std::pair<PropertyId, Value>> props); + + std::vector<Label> Labels() const; + + bool HasLabel(Label &label) const; + + std::vector<std::pair<PropertyId, Value>> Properties() const; + + Value GetProperty(PropertyId prop_id) const; + Value GetProperty(const std::string &prop_name) const; + + msgs::Vertex GetVertex() const; + + // Dummy function + // NOLINTNEXTLINE(readability-convert-member-functions-to-static) + inline size_t CypherId() const { return 10; } + + // auto InEdges(storage::View view, const std::vector<storage::EdgeTypeId> &edge_types) const + // -> storage::Result<decltype(iter::imap(MakeEdgeAccessor, *impl_.InEdges(view)))> { + // auto maybe_edges = impl_.InEdges(view, edge_types); + // if (maybe_edges.HasError()) return maybe_edges.GetError(); + // return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges)); + // } + // + // auto InEdges(storage::View view) const { return InEdges(view, {}); } + // + // auto InEdges(storage::View view, const std::vector<storage::EdgeTypeId> &edge_types, const VertexAccessor &dest) + // const + // -> storage::Result<decltype(iter::imap(MakeEdgeAccessor, *impl_.InEdges(view)))> { + // auto maybe_edges = impl_.InEdges(view, edge_types, &dest.impl_); + // if (maybe_edges.HasError()) return maybe_edges.GetError(); + // return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges)); + // } + // + // auto OutEdges(storage::View view, const std::vector<storage::EdgeTypeId> &edge_types) const + // -> storage::Result<decltype(iter::imap(MakeEdgeAccessor, *impl_.OutEdges(view)))> { + // auto maybe_edges = impl_.OutEdges(view, edge_types); + // if (maybe_edges.HasError()) return maybe_edges.GetError(); + // return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges)); + // } + // + // auto OutEdges(storage::View view) const { return OutEdges(view, {}); } + // + // auto OutEdges(storage::View view, const std::vector<storage::EdgeTypeId> &edge_types, + // const VertexAccessor &dest) const + // -> storage::Result<decltype(iter::imap(MakeEdgeAccessor, *impl_.OutEdges(view)))> { + // auto maybe_edges = impl_.OutEdges(view, edge_types, &dest.impl_); + // if (maybe_edges.HasError()) return maybe_edges.GetError(); + // return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges)); + // } + + // storage::Result<size_t> InDegree(storage::View view) const { return impl_.InDegree(view); } + // + // storage::Result<size_t> OutDegree(storage::View view) const { return impl_.OutDegree(view); } + // + + friend bool operator==(const VertexAccessor &lhs, const VertexAccessor &rhs) { + return lhs.vertex == rhs.vertex && lhs.properties == rhs.properties; + } + + friend bool operator!=(const VertexAccessor &lhs, const VertexAccessor &rhs) { return !(lhs == rhs); } + + private: + Vertex vertex; + std::vector<std::pair<PropertyId, Value>> properties; +}; + +// inline VertexAccessor EdgeAccessor::To() const { return VertexAccessor(impl_.ToVertex()); } + +// inline VertexAccessor EdgeAccessor::From() const { return VertexAccessor(impl_.FromVertex()); } + +// Highly mocked interface. Won't work if used. +class Path { + public: + // Empty for now + explicit Path(const VertexAccessor & /*vertex*/, utils::MemoryResource *memory = utils::NewDeleteResource()) + : mem(memory) {} + + template <typename... TOthers> + explicit Path(const VertexAccessor &vertex, const TOthers &...others) {} + + template <typename... TOthers> + Path(std::allocator_arg_t /*unused*/, utils::MemoryResource *memory, const VertexAccessor &vertex, + const TOthers &...others) {} + + Path(const Path & /*other*/) {} + + Path(const Path & /*other*/, utils::MemoryResource *memory) : mem(memory) {} + + Path(Path && /*other*/) noexcept {} + + Path(Path && /*other*/, utils::MemoryResource *memory) : mem(memory) {} + Path &operator=(const Path &path) { + if (this == &path) { + return *this; + } + return *this; + } + + Path &operator=(Path &&path) noexcept { + if (this == &path) { + return *this; + } + return *this; + } + + ~Path() {} + + friend bool operator==(const Path & /*lhs*/, const Path & /*rhs*/) { return true; }; + utils::MemoryResource *GetMemoryResource() { return mem; } + + private: + utils::MemoryResource *mem = utils::NewDeleteResource(); +}; +} // namespace memgraph::query::v2::accessors diff --git a/src/query/v2/bindings/eval.hpp b/src/query/v2/bindings/eval.hpp index 57003d16e..8c4ad6d34 100644 --- a/src/query/v2/bindings/eval.hpp +++ b/src/query/v2/bindings/eval.hpp @@ -16,25 +16,29 @@ #include "expr/interpret/eval.hpp" #include "query/v2/bindings/typed_value.hpp" #include "query/v2/context.hpp" +#include "query/v2/conversions.hpp" #include "query/v2/db_accessor.hpp" +#include "query/v2/requests.hpp" #include "storage/v3/conversions.hpp" -#include "storage/v3/id_types.hpp" -#include "storage/v3/property_store.hpp" +#include "storage/v3/property_value.hpp" #include "storage/v3/view.hpp" namespace memgraph::query::v2 { -struct PropertyToTypedValueConverter { - TypedValue operator()(const auto &val) { return memgraph::storage::v3::PropertyToTypedValue<TypedValue>(val); } - - TypedValue operator()(const auto &val, utils::MemoryResource *mem) { - return memgraph::storage::v3::PropertyToTypedValue<TypedValue>(val, mem); - } +inline const auto lam = [](const auto &val) { return ValueToTypedValue(val); }; +namespace detail { +class Callable { + public: + auto operator()(const memgraph::storage::v3::PropertyValue &val) const { + return memgraph::storage::v3::PropertyToTypedValue<TypedValue>(val); + }; + auto operator()(const msgs::Value &val) const { return ValueToTypedValue(val); }; }; +} // namespace detail using ExpressionEvaluator = - memgraph::expr::ExpressionEvaluator<TypedValue, EvaluationContext, DbAccessor, storage::v3::View, - storage::v3::LabelId, storage::v3::PropertyStore, PropertyToTypedValueConverter, - memgraph::storage::v3::Error>; + memgraph::expr::ExpressionEvaluator<TypedValue, memgraph::query::v2::EvaluationContext, DbAccessor, + storage::v3::View, storage::v3::LabelId, msgs::Value, detail::Callable, + memgraph::storage::v3::Error, memgraph::expr::QueryEngineTag>; } // namespace memgraph::query::v2 diff --git a/src/query/v2/bindings/typed_value.cpp b/src/query/v2/bindings/typed_value.cpp index 3c0105011..39205468b 100644 --- a/src/query/v2/bindings/typed_value.cpp +++ b/src/query/v2/bindings/typed_value.cpp @@ -10,10 +10,10 @@ // licenses/APL.txt. #include "expr/typed_value.hpp" -#include "query/v2/db_accessor.hpp" +#include "query/v2/accessors.hpp" #include "query/v2/path.hpp" namespace memgraph::expr { namespace v2 = memgraph::query::v2; -template class TypedValueT<v2::VertexAccessor, v2::EdgeAccessor, v2::Path>; +template class TypedValueT<v2::accessors::VertexAccessor, v2::accessors::EdgeAccessor, v2::accessors::Path>; } // namespace memgraph::expr diff --git a/src/query/v2/bindings/typed_value.hpp b/src/query/v2/bindings/typed_value.hpp index 901e21260..27fa07806 100644 --- a/src/query/v2/bindings/typed_value.hpp +++ b/src/query/v2/bindings/typed_value.hpp @@ -14,13 +14,14 @@ #include "query/v2/bindings/bindings.hpp" #include "expr/typed_value.hpp" -#include "query/v2/db_accessor.hpp" -#include "query/v2/path.hpp" +#include "query/v2/accessors.hpp" namespace memgraph::expr { namespace v2 = memgraph::query::v2; -extern template class memgraph::expr::TypedValueT<v2::VertexAccessor, v2::EdgeAccessor, v2::Path>; +extern template class memgraph::expr::TypedValueT<v2::accessors::VertexAccessor, v2::accessors::EdgeAccessor, + v2::accessors::Path>; } // namespace memgraph::expr namespace memgraph::query::v2 { -using TypedValue = memgraph::expr::TypedValueT<VertexAccessor, EdgeAccessor, Path>; +using TypedValue = + memgraph::expr::TypedValueT<v2::accessors::VertexAccessor, v2::accessors::EdgeAccessor, v2::accessors::Path>; } // namespace memgraph::query::v2 diff --git a/src/query/v2/context.hpp b/src/query/v2/context.hpp index fc788d9e5..48ee4c923 100644 --- a/src/query/v2/context.hpp +++ b/src/query/v2/context.hpp @@ -18,7 +18,8 @@ #include "query/v2/metadata.hpp" #include "query/v2/parameters.hpp" #include "query/v2/plan/profile.hpp" -#include "query/v2/trigger.hpp" +//#include "query/v2/trigger.hpp" +#include "query/v2/shard_request_manager.hpp" #include "utils/async_timer.hpp" namespace memgraph::query::v2 { @@ -70,8 +71,9 @@ struct ExecutionContext { plan::ProfilingStats stats; plan::ProfilingStats *stats_root{nullptr}; ExecutionStats execution_stats; - TriggerContextCollector *trigger_context_collector{nullptr}; + // TriggerContextCollector *trigger_context_collector{nullptr}; utils::AsyncTimer timer; + std::unique_ptr<msgs::ShardRequestManagerInterface> shard_request_manager{nullptr}; }; static_assert(std::is_move_assignable_v<ExecutionContext>, "ExecutionContext must be move assignable!"); diff --git a/src/query/v2/conversions.hpp b/src/query/v2/conversions.hpp new file mode 100644 index 000000000..9556d789c --- /dev/null +++ b/src/query/v2/conversions.hpp @@ -0,0 +1,100 @@ +// 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. + +#pragma once +#include "bindings/typed_value.hpp" +#include "query/v2/accessors.hpp" +#include "query/v2/requests.hpp" + +namespace memgraph::query::v2 { + +inline TypedValue ValueToTypedValue(const msgs::Value &value) { + using Value = msgs::Value; + switch (value.type) { + case Value::Type::Null: + return {}; + case Value::Type::Bool: + return TypedValue(value.bool_v); + case Value::Type::Int64: + return TypedValue(value.int_v); + case Value::Type::Double: + return TypedValue(value.double_v); + case Value::Type::String: + return TypedValue(value.string_v); + case Value::Type::List: { + const auto &lst = value.list_v; + std::vector<TypedValue> dst; + dst.reserve(lst.size()); + for (const auto &elem : lst) { + dst.push_back(ValueToTypedValue(elem)); + } + return TypedValue(std::move(dst)); + } + case Value::Type::Map: { + const auto &value_map = value.map_v; + std::map<std::string, TypedValue> dst; + for (const auto &[key, val] : value_map) { + dst[key] = ValueToTypedValue(val); + } + return TypedValue(std::move(dst)); + } + case Value::Type::Vertex: + return TypedValue(accessors::VertexAccessor(value.vertex_v, {})); + case Value::Type::Edge: + return TypedValue(accessors::EdgeAccessor(value.edge_v, {})); + case Value::Type::Path: + break; + } + throw std::runtime_error("Incorrect type in conversion"); +} + +inline msgs::Value TypedValueToValue(const TypedValue &value) { + using Value = msgs::Value; + switch (value.type()) { + case TypedValue::Type::Null: + return {}; + case TypedValue::Type::Bool: + return Value(value.ValueBool()); + case TypedValue::Type::Int: + return Value(value.ValueInt()); + case TypedValue::Type::Double: + return Value(value.ValueDouble()); + case TypedValue::Type::String: + return Value(std::string(value.ValueString())); + case TypedValue::Type::List: { + const auto &lst = value.ValueList(); + std::vector<Value> dst; + dst.reserve(lst.size()); + for (const auto &elem : lst) { + dst.push_back(TypedValueToValue(elem)); + } + return Value(std::move(dst)); + } + case TypedValue::Type::Map: { + const auto &value_map = value.ValueMap(); + std::map<std::string, Value> dst; + for (const auto &[key, val] : value_map) { + dst[std::string(key)] = TypedValueToValue(val); + } + return Value(std::move(dst)); + } + case TypedValue::Type::Vertex: + return Value(value.ValueVertex().GetVertex()); + case TypedValue::Type::Edge: + return Value(value.ValueEdge().GetEdge()); + case TypedValue::Type::Path: + default: + break; + } + throw std::runtime_error("Incorrect type in conversion"); +} + +} // namespace memgraph::query::v2 diff --git a/src/query/v2/frontend/semantic/required_privileges.cpp b/src/query/v2/frontend/semantic/required_privileges.cpp index 3582821ae..784b9ef59 100644 --- a/src/query/v2/frontend/semantic/required_privileges.cpp +++ b/src/query/v2/frontend/semantic/required_privileges.cpp @@ -11,7 +11,6 @@ #include "query/v2/bindings/ast_visitor.hpp" #include "query/v2/frontend/ast/ast.hpp" -#include "query/v2/procedure/module.hpp" #include "utils/memory.hpp" namespace memgraph::query::v2 { @@ -86,12 +85,7 @@ class PrivilegeExtractor : public QueryVisitor<void>, public HierarchicalTreeVis AddPrivilege(AuthQuery::Privilege::CREATE); return false; } - bool PreVisit(CallProcedure &procedure) override { - const auto maybe_proc = - procedure::FindProcedure(procedure::gModuleRegistry, procedure.procedure_name_, utils::NewDeleteResource()); - if (maybe_proc && maybe_proc->second->info.required_privilege) { - AddPrivilege(*maybe_proc->second->info.required_privilege); - } + bool PreVisit(CallProcedure & /*procedure*/) override { return false; } bool PreVisit(Delete & /*unused*/) override { diff --git a/src/query/v2/interpret/awesome_memgraph_functions.cpp b/src/query/v2/interpret/awesome_memgraph_functions.cpp index e66002299..323703bf5 100644 --- a/src/query/v2/interpret/awesome_memgraph_functions.cpp +++ b/src/query/v2/interpret/awesome_memgraph_functions.cpp @@ -21,11 +21,12 @@ #include <type_traits> #include "query/v2/bindings/typed_value.hpp" +#include "query/v2/conversions.hpp" #include "query/v2/db_accessor.hpp" #include "query/v2/exceptions.hpp" #include "query/v2/procedure/cypher_types.hpp" -#include "query/v2/procedure/mg_procedure_impl.hpp" -#include "query/v2/procedure/module.hpp" +//#include "query/v2/procedure/mg_procedure_impl.hpp" +//#include "query/v2/procedure/module.hpp" #include "storage/v3/conversions.hpp" #include "utils/string.hpp" #include "utils/temporal.hpp" @@ -387,39 +388,7 @@ TypedValue Last(const TypedValue *args, int64_t nargs, const FunctionContext &ct return TypedValue(list.back(), ctx.memory); } -TypedValue Properties(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) { - FType<Or<Null, Vertex, Edge>>("properties", args, nargs); - auto *dba = ctx.db_accessor; - auto get_properties = [&](const auto &record_accessor) { - TypedValue::TMap properties(ctx.memory); - auto maybe_props = record_accessor.Properties(ctx.view); - if (maybe_props.HasError()) { - switch (maybe_props.GetError()) { - case storage::v3::Error::DELETED_OBJECT: - throw QueryRuntimeException("Trying to get properties from a deleted object."); - case storage::v3::Error::NONEXISTENT_OBJECT: - throw query::v2::QueryRuntimeException("Trying to get properties from an object that doesn't exist."); - case storage::v3::Error::SERIALIZATION_ERROR: - case storage::v3::Error::VERTEX_HAS_EDGES: - case storage::v3::Error::PROPERTIES_DISABLED: - throw QueryRuntimeException("Unexpected error when getting properties."); - } - } - for (const auto &property : *maybe_props) { - properties.emplace(dba->PropertyToName(property.first), - storage::v3::PropertyToTypedValue<TypedValue>(property.second)); - } - return TypedValue(std::move(properties)); - }; - const auto &value = args[0]; - if (value.IsNull()) { - return TypedValue(ctx.memory); - } else if (value.IsVertex()) { - return get_properties(value.ValueVertex()); - } else { - return get_properties(value.ValueEdge()); - } -} +TypedValue Properties(const TypedValue * /*args*/, int64_t /*nargs*/, const FunctionContext & /*ctx*/) { return {}; } TypedValue Size(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) { FType<Or<Null, List, String, Map, Path>>("size", args, nargs); @@ -435,7 +404,8 @@ TypedValue Size(const TypedValue *args, int64_t nargs, const FunctionContext &ct // to do it. return TypedValue(static_cast<int64_t>(value.ValueMap().size()), ctx.memory); } else { - return TypedValue(static_cast<int64_t>(value.ValuePath().edges().size()), ctx.memory); + // TODO(kostasrim) Fix the dummy return + return TypedValue(int64_t(0), ctx.memory); } } @@ -469,25 +439,22 @@ TypedValue Degree(const TypedValue *args, int64_t nargs, const FunctionContext & FType<Or<Null, Vertex>>("degree", args, nargs); if (args[0].IsNull()) return TypedValue(ctx.memory); const auto &vertex = args[0].ValueVertex(); - size_t out_degree = UnwrapDegreeResult(vertex.OutDegree(ctx.view)); - size_t in_degree = UnwrapDegreeResult(vertex.InDegree(ctx.view)); - return TypedValue(static_cast<int64_t>(out_degree + in_degree), ctx.memory); + // TODO(kostasrim) Fix dummy values + return TypedValue(int64_t(0), ctx.memory); } TypedValue InDegree(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) { FType<Or<Null, Vertex>>("inDegree", args, nargs); if (args[0].IsNull()) return TypedValue(ctx.memory); const auto &vertex = args[0].ValueVertex(); - size_t in_degree = UnwrapDegreeResult(vertex.InDegree(ctx.view)); - return TypedValue(static_cast<int64_t>(in_degree), ctx.memory); + return TypedValue(int64_t(0), ctx.memory); } TypedValue OutDegree(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) { FType<Or<Null, Vertex>>("outDegree", args, nargs); if (args[0].IsNull()) return TypedValue(ctx.memory); const auto &vertex = args[0].ValueVertex(); - size_t out_degree = UnwrapDegreeResult(vertex.OutDegree(ctx.view)); - return TypedValue(static_cast<int64_t>(out_degree), ctx.memory); + return TypedValue(int64_t(0), ctx.memory); } TypedValue ToBoolean(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) { @@ -553,7 +520,7 @@ TypedValue Type(const TypedValue *args, int64_t nargs, const FunctionContext &ct FType<Or<Null, Edge>>("type", args, nargs); auto *dba = ctx.db_accessor; if (args[0].IsNull()) return TypedValue(ctx.memory); - return TypedValue(dba->EdgeTypeToName(args[0].ValueEdge().EdgeType()), ctx.memory); + return TypedValue(static_cast<int64_t>(args[0].ValueEdge().EdgeType()), ctx.memory); } TypedValue ValueType(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) { @@ -593,81 +560,27 @@ TypedValue ValueType(const TypedValue *args, int64_t nargs, const FunctionContex } // TODO: How is Keys different from Properties function? -TypedValue Keys(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) { +TypedValue Keys(const TypedValue *args, int64_t nargs, const FunctionContext & /*ctx*/) { FType<Or<Null, Vertex, Edge>>("keys", args, nargs); - auto *dba = ctx.db_accessor; - auto get_keys = [&](const auto &record_accessor) { - TypedValue::TVector keys(ctx.memory); - auto maybe_props = record_accessor.Properties(ctx.view); - if (maybe_props.HasError()) { - switch (maybe_props.GetError()) { - case storage::v3::Error::DELETED_OBJECT: - throw QueryRuntimeException("Trying to get keys from a deleted object."); - case storage::v3::Error::NONEXISTENT_OBJECT: - throw query::v2::QueryRuntimeException("Trying to get keys from an object that doesn't exist."); - case storage::v3::Error::SERIALIZATION_ERROR: - case storage::v3::Error::VERTEX_HAS_EDGES: - case storage::v3::Error::PROPERTIES_DISABLED: - throw QueryRuntimeException("Unexpected error when getting keys."); - } - } - for (const auto &property : *maybe_props) { - keys.emplace_back(dba->PropertyToName(property.first)); - } - return TypedValue(std::move(keys)); - }; - const auto &value = args[0]; - if (value.IsNull()) { - return TypedValue(ctx.memory); - } else if (value.IsVertex()) { - return get_keys(value.ValueVertex()); - } else { - return get_keys(value.ValueEdge()); - } + return {}; } TypedValue Labels(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) { FType<Or<Null, Vertex>>("labels", args, nargs); - auto *dba = ctx.db_accessor; if (args[0].IsNull()) return TypedValue(ctx.memory); - TypedValue::TVector labels(ctx.memory); - auto maybe_labels = args[0].ValueVertex().Labels(ctx.view); - if (maybe_labels.HasError()) { - switch (maybe_labels.GetError()) { - case storage::v3::Error::DELETED_OBJECT: - throw QueryRuntimeException("Trying to get labels from a deleted node."); - case storage::v3::Error::NONEXISTENT_OBJECT: - throw query::v2::QueryRuntimeException("Trying to get labels from a node that doesn't exist."); - case storage::v3::Error::SERIALIZATION_ERROR: - case storage::v3::Error::VERTEX_HAS_EDGES: - case storage::v3::Error::PROPERTIES_DISABLED: - throw QueryRuntimeException("Unexpected error when getting labels."); - } - } - for (const auto &label : *maybe_labels) { - labels.emplace_back(dba->LabelToName(label)); - } - return TypedValue(std::move(labels)); + return {}; } TypedValue Nodes(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) { FType<Or<Null, Path>>("nodes", args, nargs); if (args[0].IsNull()) return TypedValue(ctx.memory); - const auto &vertices = args[0].ValuePath().vertices(); - TypedValue::TVector values(ctx.memory); - values.reserve(vertices.size()); - for (const auto &v : vertices) values.emplace_back(v); - return TypedValue(std::move(values)); + return {}; } TypedValue Relationships(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) { FType<Or<Null, Path>>("relationships", args, nargs); if (args[0].IsNull()) return TypedValue(ctx.memory); - const auto &edges = args[0].ValuePath().edges(); - TypedValue::TVector values(ctx.memory); - values.reserve(edges.size()); - for (const auto &e : edges) values.emplace_back(e); - return TypedValue(std::move(values)); + return {}; } TypedValue Range(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) { @@ -877,9 +790,9 @@ TypedValue Id(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) if (arg.IsNull()) { return TypedValue(ctx.memory); } else if (arg.IsVertex()) { - return TypedValue(arg.ValueVertex().CypherId(), ctx.memory); + return TypedValue(int64_t(arg.ValueVertex().CypherId()), ctx.memory); } else { - return TypedValue(arg.ValueEdge().CypherId(), ctx.memory); + return TypedValue(int64_t(arg.ValueEdge().CypherId()), ctx.memory); } } @@ -1180,49 +1093,6 @@ TypedValue Duration(const TypedValue *args, int64_t nargs, const FunctionContext return TypedValue(utils::Duration(duration_parameters), ctx.memory); } -std::function<TypedValue(const TypedValue *, const int64_t, const FunctionContext &)> UserFunction( - const mgp_func &func, const std::string &fully_qualified_name) { - return [func, fully_qualified_name](const TypedValue *args, int64_t nargs, const FunctionContext &ctx) -> TypedValue { - /// Find function is called to aquire the lock on Module pointer while user-defined function is executed - const auto &maybe_found = - procedure::FindFunction(procedure::gModuleRegistry, fully_qualified_name, utils::NewDeleteResource()); - if (!maybe_found) { - throw QueryRuntimeException( - "Function '{}' has been unloaded. Please check query modules to confirm that function is loaded in Memgraph.", - fully_qualified_name); - } - - const auto &func_cb = func.cb; - mgp_memory memory{ctx.memory}; - mgp_func_context functx{ctx.db_accessor, ctx.view}; - auto graph = mgp_graph::NonWritableGraph(*ctx.db_accessor, ctx.view); - - std::vector<TypedValue> args_list; - args_list.reserve(nargs); - for (std::size_t i = 0; i < nargs; ++i) { - args_list.emplace_back(args[i]); - } - - auto function_argument_list = mgp_list(ctx.memory); - procedure::ConstructArguments(args_list, func, fully_qualified_name, function_argument_list, graph); - - mgp_func_result maybe_res; - func_cb(&function_argument_list, &functx, &maybe_res, &memory); - if (maybe_res.error_msg) { - throw QueryRuntimeException(*maybe_res.error_msg); - } - - if (!maybe_res.value) { - throw QueryRuntimeException( - "Function '{}' didn't set the result nor the error message. Please either set the result by using " - "mgp_func_result_set_value or the error by using mgp_func_result_set_error_msg.", - fully_qualified_name); - } - - return {*(maybe_res.value), ctx.memory}; - }; -} - } // namespace std::function<TypedValue(const TypedValue *, int64_t, const FunctionContext &ctx)> NameToFunction( @@ -1309,14 +1179,6 @@ std::function<TypedValue(const TypedValue *, int64_t, const FunctionContext &ctx if (function_name == "LOCALDATETIME") return LocalDateTime; if (function_name == "DURATION") return Duration; - const auto &maybe_found = - procedure::FindFunction(procedure::gModuleRegistry, function_name, utils::NewDeleteResource()); - - if (maybe_found) { - const auto *func = (*maybe_found).second; - return UserFunction(*func, function_name); - } - return nullptr; } diff --git a/src/query/v2/interpreter.cpp b/src/query/v2/interpreter.cpp index 7347c71a4..4cee495da 100644 --- a/src/query/v2/interpreter.cpp +++ b/src/query/v2/interpreter.cpp @@ -41,8 +41,6 @@ #include "query/v2/plan/planner.hpp" #include "query/v2/plan/profile.hpp" #include "query/v2/plan/vertex_count_cache.hpp" -#include "query/v2/stream/common.hpp" -#include "query/v2/trigger.hpp" #include "storage/v3/property_value.hpp" #include "storage/v3/shard.hpp" #include "storage/v3/storage.hpp" @@ -461,14 +459,6 @@ std::optional<std::string> StringPointerToOptional(const std::string *str) { return str == nullptr ? std::nullopt : std::make_optional(*str); } -stream::CommonStreamInfo GetCommonStreamInfo(StreamQuery *stream_query, ExpressionEvaluator &evaluator) { - return { - .batch_interval = GetOptionalValue<std::chrono::milliseconds>(stream_query->batch_interval_, evaluator) - .value_or(stream::kDefaultBatchInterval), - .batch_size = GetOptionalValue<int64_t>(stream_query->batch_size_, evaluator).value_or(stream::kDefaultBatchSize), - .transformation_name = stream_query->transform_name_}; -} - std::vector<std::string> EvaluateTopicNames(ExpressionEvaluator &evaluator, std::variant<Expression *, std::vector<std::string>> topic_variant) { return std::visit(utils::Overloaded{[&](Expression *expression) { @@ -480,214 +470,6 @@ std::vector<std::string> EvaluateTopicNames(ExpressionEvaluator &evaluator, std::move(topic_variant)); } -Callback::CallbackFunction GetKafkaCreateCallback(StreamQuery *stream_query, ExpressionEvaluator &evaluator, - InterpreterContext *interpreter_context, - const std::string *username) { - static constexpr std::string_view kDefaultConsumerGroup = "mg_consumer"; - std::string consumer_group{stream_query->consumer_group_.empty() ? kDefaultConsumerGroup - : stream_query->consumer_group_}; - - auto bootstrap = GetOptionalStringValue(stream_query->bootstrap_servers_, evaluator); - if (bootstrap && bootstrap->empty()) { - throw SemanticException("Bootstrap servers must not be an empty string!"); - } - auto common_stream_info = GetCommonStreamInfo(stream_query, evaluator); - - const auto get_config_map = [&evaluator](std::unordered_map<Expression *, Expression *> map, - std::string_view map_name) -> std::unordered_map<std::string, std::string> { - std::unordered_map<std::string, std::string> config_map; - for (const auto [key_expr, value_expr] : map) { - const auto key = key_expr->Accept(evaluator); - const auto value = value_expr->Accept(evaluator); - if (!key.IsString() || !value.IsString()) { - throw SemanticException("{} must contain only string keys and values!", map_name); - } - config_map.emplace(key.ValueString(), value.ValueString()); - } - return config_map; - }; - - return [interpreter_context, stream_name = stream_query->stream_name_, - topic_names = EvaluateTopicNames(evaluator, stream_query->topic_names_), - consumer_group = std::move(consumer_group), common_stream_info = std::move(common_stream_info), - bootstrap_servers = std::move(bootstrap), owner = StringPointerToOptional(username), - configs = get_config_map(stream_query->configs_, "Configs"), - credentials = get_config_map(stream_query->credentials_, "Credentials")]() mutable { - std::string bootstrap = bootstrap_servers - ? std::move(*bootstrap_servers) - : std::string{interpreter_context->config.default_kafka_bootstrap_servers}; - interpreter_context->streams.Create<query::v2::stream::KafkaStream>(stream_name, - {.common_info = std::move(common_stream_info), - .topics = std::move(topic_names), - .consumer_group = std::move(consumer_group), - .bootstrap_servers = std::move(bootstrap), - .configs = std::move(configs), - .credentials = std::move(credentials)}, - std::move(owner)); - - return std::vector<std::vector<TypedValue>>{}; - }; -} - -Callback::CallbackFunction GetPulsarCreateCallback(StreamQuery *stream_query, ExpressionEvaluator &evaluator, - InterpreterContext *interpreter_context, - const std::string *username) { - auto service_url = GetOptionalStringValue(stream_query->service_url_, evaluator); - if (service_url && service_url->empty()) { - throw SemanticException("Service URL must not be an empty string!"); - } - auto common_stream_info = GetCommonStreamInfo(stream_query, evaluator); - return [interpreter_context, stream_name = stream_query->stream_name_, - topic_names = EvaluateTopicNames(evaluator, stream_query->topic_names_), - common_stream_info = std::move(common_stream_info), service_url = std::move(service_url), - owner = StringPointerToOptional(username)]() mutable { - std::string url = - service_url ? std::move(*service_url) : std::string{interpreter_context->config.default_pulsar_service_url}; - interpreter_context->streams.Create<query::v2::stream::PulsarStream>( - stream_name, - {.common_info = std::move(common_stream_info), .topics = std::move(topic_names), .service_url = std::move(url)}, - std::move(owner)); - - return std::vector<std::vector<TypedValue>>{}; - }; -} - -Callback HandleStreamQuery(StreamQuery *stream_query, const Parameters ¶meters, - InterpreterContext *interpreter_context, DbAccessor *db_accessor, - const std::string *username, std::vector<Notification> *notifications) { - expr::Frame<TypedValue> frame(0); - SymbolTable symbol_table; - EvaluationContext evaluation_context; - // TODO: MemoryResource for EvaluationContext, it should probably be passed as - // the argument to Callback. - evaluation_context.timestamp = QueryTimestamp(); - evaluation_context.parameters = parameters; - ExpressionEvaluator evaluator(&frame, symbol_table, evaluation_context, db_accessor, storage::v3::View::OLD); - - Callback callback; - switch (stream_query->action_) { - case StreamQuery::Action::CREATE_STREAM: { - EventCounter::IncrementCounter(EventCounter::StreamsCreated); - switch (stream_query->type_) { - case StreamQuery::Type::KAFKA: - callback.fn = GetKafkaCreateCallback(stream_query, evaluator, interpreter_context, username); - break; - case StreamQuery::Type::PULSAR: - callback.fn = GetPulsarCreateCallback(stream_query, evaluator, interpreter_context, username); - break; - } - notifications->emplace_back(SeverityLevel::INFO, NotificationCode::CREATE_STREAM, - fmt::format("Created stream {}.", stream_query->stream_name_)); - return callback; - } - case StreamQuery::Action::START_STREAM: { - const auto batch_limit = GetOptionalValue<int64_t>(stream_query->batch_limit_, evaluator); - const auto timeout = GetOptionalValue<std::chrono::milliseconds>(stream_query->timeout_, evaluator); - - if (batch_limit.has_value()) { - if (batch_limit.value() < 0) { - throw utils::BasicException("Parameter BATCH_LIMIT cannot hold negative value"); - } - - callback.fn = [interpreter_context, stream_name = stream_query->stream_name_, batch_limit, timeout]() { - interpreter_context->streams.StartWithLimit(stream_name, static_cast<uint64_t>(batch_limit.value()), timeout); - return std::vector<std::vector<TypedValue>>{}; - }; - } else { - callback.fn = [interpreter_context, stream_name = stream_query->stream_name_]() { - interpreter_context->streams.Start(stream_name); - return std::vector<std::vector<TypedValue>>{}; - }; - notifications->emplace_back(SeverityLevel::INFO, NotificationCode::START_STREAM, - fmt::format("Started stream {}.", stream_query->stream_name_)); - } - return callback; - } - case StreamQuery::Action::START_ALL_STREAMS: { - callback.fn = [interpreter_context]() { - interpreter_context->streams.StartAll(); - return std::vector<std::vector<TypedValue>>{}; - }; - notifications->emplace_back(SeverityLevel::INFO, NotificationCode::START_ALL_STREAMS, "Started all streams."); - return callback; - } - case StreamQuery::Action::STOP_STREAM: { - callback.fn = [interpreter_context, stream_name = stream_query->stream_name_]() { - interpreter_context->streams.Stop(stream_name); - return std::vector<std::vector<TypedValue>>{}; - }; - notifications->emplace_back(SeverityLevel::INFO, NotificationCode::STOP_STREAM, - fmt::format("Stopped stream {}.", stream_query->stream_name_)); - return callback; - } - case StreamQuery::Action::STOP_ALL_STREAMS: { - callback.fn = [interpreter_context]() { - interpreter_context->streams.StopAll(); - return std::vector<std::vector<TypedValue>>{}; - }; - notifications->emplace_back(SeverityLevel::INFO, NotificationCode::STOP_ALL_STREAMS, "Stopped all streams."); - return callback; - } - case StreamQuery::Action::DROP_STREAM: { - callback.fn = [interpreter_context, stream_name = stream_query->stream_name_]() { - interpreter_context->streams.Drop(stream_name); - return std::vector<std::vector<TypedValue>>{}; - }; - notifications->emplace_back(SeverityLevel::INFO, NotificationCode::DROP_STREAM, - fmt::format("Dropped stream {}.", stream_query->stream_name_)); - return callback; - } - case StreamQuery::Action::SHOW_STREAMS: { - callback.header = {"name", "type", "batch_interval", "batch_size", "transformation_name", "owner", "is running"}; - callback.fn = [interpreter_context]() { - auto streams_status = interpreter_context->streams.GetStreamInfo(); - std::vector<std::vector<TypedValue>> results; - results.reserve(streams_status.size()); - auto stream_info_as_typed_stream_info_emplace_in = [](auto &typed_status, const auto &stream_info) { - typed_status.emplace_back(stream_info.batch_interval.count()); - typed_status.emplace_back(stream_info.batch_size); - typed_status.emplace_back(stream_info.transformation_name); - }; - - for (const auto &status : streams_status) { - std::vector<TypedValue> typed_status; - typed_status.reserve(7); - typed_status.emplace_back(status.name); - typed_status.emplace_back(StreamSourceTypeToString(status.type)); - stream_info_as_typed_stream_info_emplace_in(typed_status, status.info); - if (status.owner.has_value()) { - typed_status.emplace_back(*status.owner); - } else { - typed_status.emplace_back(); - } - typed_status.emplace_back(status.is_running); - results.push_back(std::move(typed_status)); - } - - return results; - }; - return callback; - } - case StreamQuery::Action::CHECK_STREAM: { - callback.header = {"queries", "raw messages"}; - - const auto batch_limit = GetOptionalValue<int64_t>(stream_query->batch_limit_, evaluator); - if (batch_limit.has_value() && batch_limit.value() < 0) { - throw utils::BasicException("Parameter BATCH_LIMIT cannot hold negative value"); - } - - callback.fn = [interpreter_context, stream_name = stream_query->stream_name_, - timeout = GetOptionalValue<std::chrono::milliseconds>(stream_query->timeout_, evaluator), - batch_limit]() mutable { - return interpreter_context->streams.Check(stream_name, timeout, batch_limit); - }; - notifications->emplace_back(SeverityLevel::INFO, NotificationCode::CHECK_STREAM, - fmt::format("Checked stream {}.", stream_query->stream_name_)); - return callback; - } - } -} - Callback HandleSettingQuery(SettingQuery *setting_query, const Parameters ¶meters, DbAccessor *db_accessor) { expr::Frame<TypedValue> frame(0); SymbolTable symbol_table; @@ -889,7 +671,7 @@ struct PullPlanVector { struct PullPlan { explicit PullPlan(std::shared_ptr<CachedPlan> plan, const Parameters ¶meters, bool is_profile_query, DbAccessor *dba, InterpreterContext *interpreter_context, utils::MemoryResource *execution_memory, - TriggerContextCollector *trigger_context_collector = nullptr, + // TriggerContextCollector *trigger_context_collector = nullptr, std::optional<size_t> memory_limit = {}); std::optional<plan::ProfilingStatsWithTotalTime> Pull(AnyStream *stream, std::optional<int> n, const std::vector<Symbol> &output_symbols, @@ -918,7 +700,8 @@ struct PullPlan { PullPlan::PullPlan(const std::shared_ptr<CachedPlan> plan, const Parameters ¶meters, const bool is_profile_query, DbAccessor *dba, InterpreterContext *interpreter_context, utils::MemoryResource *execution_memory, - TriggerContextCollector *trigger_context_collector, const std::optional<size_t> memory_limit) + const std::optional<size_t> memory_limit) + // TriggerContextCollector *trigger_context_collector, const std::optional<size_t> memory_limit) : plan_(plan), cursor_(plan->plan().MakeCursor(execution_memory)), frame_(plan->symbol_table().max_position(), execution_memory), @@ -934,7 +717,7 @@ PullPlan::PullPlan(const std::shared_ptr<CachedPlan> plan, const Parameters &par } ctx_.is_shutting_down = &interpreter_context->is_shutting_down; ctx_.is_profile_query = is_profile_query; - ctx_.trigger_context_collector = trigger_context_collector; + // ctx_.trigger_context_collector = trigger_context_collector; } std::optional<plan::ProfilingStatsWithTotalTime> PullPlan::Pull(AnyStream *stream, std::optional<int> n, @@ -1031,7 +814,9 @@ using RWType = plan::ReadWriteTypeChecker::RWType; InterpreterContext::InterpreterContext(storage::v3::Shard *db, const InterpreterConfig config, const std::filesystem::path &data_directory) - : db(db), trigger_store(data_directory / "triggers"), config(config), streams{this, data_directory / "streams"} {} + // : db(db), trigger_store(data_directory / "triggers"), config(config), streams{this, data_directory / + // "streams"} {} + : db(db), config(config) {} Interpreter::Interpreter(InterpreterContext *interpreter_context) : interpreter_context_(interpreter_context) { MG_ASSERT(interpreter_context_, "Interpreter context must not be NULL"); @@ -1052,9 +837,9 @@ PreparedQuery Interpreter::PrepareTransactionQuery(std::string_view query_upper) interpreter_context_->db->Access(coordinator::Hlc{}, GetIsolationLevelOverride())); execution_db_accessor_.emplace(db_accessor_.get()); - if (interpreter_context_->trigger_store.HasTriggers()) { - trigger_context_collector_.emplace(interpreter_context_->trigger_store.GetEventTypes()); - } + // if (interpreter_context_->trigger_store.HasTriggers()) { + // trigger_context_collector_.emplace(interpreter_context_->trigger_store.GetEventTypes()); + // } }; } else if (query_upper == "COMMIT") { handler = [this] { @@ -1101,8 +886,8 @@ PreparedQuery Interpreter::PrepareTransactionQuery(std::string_view query_upper) PreparedQuery PrepareCypherQuery(ParsedQuery parsed_query, std::map<std::string, TypedValue> *summary, InterpreterContext *interpreter_context, DbAccessor *dba, - utils::MemoryResource *execution_memory, std::vector<Notification> *notifications, - TriggerContextCollector *trigger_context_collector = nullptr) { + utils::MemoryResource *execution_memory, std::vector<Notification> *notifications) { + // TriggerContextCollector *trigger_context_collector = nullptr) { auto *cypher_query = utils::Downcast<CypherQuery>(parsed_query.query); expr::Frame<TypedValue> frame(0); @@ -1147,7 +932,8 @@ PreparedQuery PrepareCypherQuery(ParsedQuery parsed_query, std::map<std::string, utils::FindOr(parsed_query.stripped_query.named_expressions(), symbol.token_position(), symbol.name()).first); } auto pull_plan = std::make_shared<PullPlan>(plan, parsed_query.parameters, false, dba, interpreter_context, - execution_memory, trigger_context_collector, memory_limit); + execution_memory, memory_limit); + // execution_memory, trigger_context_collector, memory_limit); return PreparedQuery{std::move(header), std::move(parsed_query.required_privileges), [pull_plan = std::move(pull_plan), output_symbols = std::move(output_symbols), summary]( AnyStream *stream, std::optional<int> n) -> std::optional<QueryHandlerResult> { @@ -1272,7 +1058,7 @@ PreparedQuery PrepareProfileQuery(ParsedQuery parsed_query, bool in_explicit_tra // No output symbols are given so that nothing is streamed. if (!stats_and_total_time) { stats_and_total_time = PullPlan(plan, parameters, true, dba, interpreter_context, - execution_memory, nullptr, memory_limit) + execution_memory, memory_limit) .Pull(stream, {}, {}, summary); pull_plan = std::make_shared<PullPlanVector>(ProfilingStatsToTable(*stats_and_total_time)); } @@ -1489,170 +1275,6 @@ PreparedQuery PrepareFreeMemoryQuery(ParsedQuery parsed_query, const bool in_exp RWType::NONE}; } -TriggerEventType ToTriggerEventType(const TriggerQuery::EventType event_type) { - switch (event_type) { - case TriggerQuery::EventType::ANY: - return TriggerEventType::ANY; - - case TriggerQuery::EventType::CREATE: - return TriggerEventType::CREATE; - - case TriggerQuery::EventType::VERTEX_CREATE: - return TriggerEventType::VERTEX_CREATE; - - case TriggerQuery::EventType::EDGE_CREATE: - return TriggerEventType::EDGE_CREATE; - - case TriggerQuery::EventType::DELETE: - return TriggerEventType::DELETE; - - case TriggerQuery::EventType::VERTEX_DELETE: - return TriggerEventType::VERTEX_DELETE; - - case TriggerQuery::EventType::EDGE_DELETE: - return TriggerEventType::EDGE_DELETE; - - case TriggerQuery::EventType::UPDATE: - return TriggerEventType::UPDATE; - - case TriggerQuery::EventType::VERTEX_UPDATE: - return TriggerEventType::VERTEX_UPDATE; - - case TriggerQuery::EventType::EDGE_UPDATE: - return TriggerEventType::EDGE_UPDATE; - } -} - -Callback CreateTrigger(TriggerQuery *trigger_query, - const std::map<std::string, storage::v3::PropertyValue> &user_parameters, - InterpreterContext *interpreter_context, DbAccessor *dba, std::optional<std::string> owner) { - return { - {}, - [trigger_name = std::move(trigger_query->trigger_name_), trigger_statement = std::move(trigger_query->statement_), - event_type = trigger_query->event_type_, before_commit = trigger_query->before_commit_, interpreter_context, dba, - user_parameters, owner = std::move(owner)]() mutable -> std::vector<std::vector<TypedValue>> { - interpreter_context->trigger_store.AddTrigger( - std::move(trigger_name), trigger_statement, user_parameters, ToTriggerEventType(event_type), - before_commit ? TriggerPhase::BEFORE_COMMIT : TriggerPhase::AFTER_COMMIT, &interpreter_context->ast_cache, - dba, interpreter_context->config.query, std::move(owner), interpreter_context->auth_checker); - return {}; - }}; -} - -Callback DropTrigger(TriggerQuery *trigger_query, InterpreterContext *interpreter_context) { - return {{}, - [trigger_name = std::move(trigger_query->trigger_name_), - interpreter_context]() -> std::vector<std::vector<TypedValue>> { - interpreter_context->trigger_store.DropTrigger(trigger_name); - return {}; - }}; -} - -Callback ShowTriggers(InterpreterContext *interpreter_context) { - return {{"trigger name", "statement", "event type", "phase", "owner"}, [interpreter_context] { - std::vector<std::vector<TypedValue>> results; - auto trigger_infos = interpreter_context->trigger_store.GetTriggerInfo(); - results.reserve(trigger_infos.size()); - for (auto &trigger_info : trigger_infos) { - std::vector<TypedValue> typed_trigger_info; - typed_trigger_info.reserve(4); - typed_trigger_info.emplace_back(std::move(trigger_info.name)); - typed_trigger_info.emplace_back(std::move(trigger_info.statement)); - typed_trigger_info.emplace_back(TriggerEventTypeToString(trigger_info.event_type)); - typed_trigger_info.emplace_back(trigger_info.phase == TriggerPhase::BEFORE_COMMIT ? "BEFORE COMMIT" - : "AFTER COMMIT"); - typed_trigger_info.emplace_back(trigger_info.owner.has_value() ? TypedValue{*trigger_info.owner} - : TypedValue{}); - - results.push_back(std::move(typed_trigger_info)); - } - - return results; - }}; -} - -PreparedQuery PrepareTriggerQuery(ParsedQuery parsed_query, const bool in_explicit_transaction, - std::vector<Notification> *notifications, InterpreterContext *interpreter_context, - DbAccessor *dba, - const std::map<std::string, storage::v3::PropertyValue> &user_parameters, - const std::string *username) { - if (in_explicit_transaction) { - throw TriggerModificationInMulticommandTxException(); - } - - auto *trigger_query = utils::Downcast<TriggerQuery>(parsed_query.query); - MG_ASSERT(trigger_query); - - std::optional<Notification> trigger_notification; - auto callback = std::invoke([trigger_query, interpreter_context, dba, &user_parameters, - owner = StringPointerToOptional(username), &trigger_notification]() mutable { - switch (trigger_query->action_) { - case TriggerQuery::Action::CREATE_TRIGGER: - trigger_notification.emplace(SeverityLevel::INFO, NotificationCode::CREATE_TRIGGER, - fmt::format("Created trigger {}.", trigger_query->trigger_name_)); - EventCounter::IncrementCounter(EventCounter::TriggersCreated); - return CreateTrigger(trigger_query, user_parameters, interpreter_context, dba, std::move(owner)); - case TriggerQuery::Action::DROP_TRIGGER: - trigger_notification.emplace(SeverityLevel::INFO, NotificationCode::DROP_TRIGGER, - fmt::format("Dropped trigger {}.", trigger_query->trigger_name_)); - return DropTrigger(trigger_query, interpreter_context); - case TriggerQuery::Action::SHOW_TRIGGERS: - return ShowTriggers(interpreter_context); - } - }); - - return PreparedQuery{std::move(callback.header), std::move(parsed_query.required_privileges), - [callback_fn = std::move(callback.fn), pull_plan = std::shared_ptr<PullPlanVector>{nullptr}, - trigger_notification = std::move(trigger_notification), notifications]( - AnyStream *stream, std::optional<int> n) mutable -> std::optional<QueryHandlerResult> { - if (UNLIKELY(!pull_plan)) { - pull_plan = std::make_shared<PullPlanVector>(callback_fn()); - } - - if (pull_plan->Pull(stream, n)) { - if (trigger_notification) { - notifications->push_back(std::move(*trigger_notification)); - } - return QueryHandlerResult::COMMIT; - } - return std::nullopt; - }, - RWType::NONE}; - // False positive report for the std::make_shared above - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) -} - -PreparedQuery PrepareStreamQuery(ParsedQuery parsed_query, const bool in_explicit_transaction, - std::vector<Notification> *notifications, InterpreterContext *interpreter_context, - DbAccessor *dba, - const std::map<std::string, storage::v3::PropertyValue> & /*user_parameters*/, - const std::string *username) { - if (in_explicit_transaction) { - throw StreamQueryInMulticommandTxException(); - } - - auto *stream_query = utils::Downcast<StreamQuery>(parsed_query.query); - MG_ASSERT(stream_query); - auto callback = - HandleStreamQuery(stream_query, parsed_query.parameters, interpreter_context, dba, username, notifications); - - return PreparedQuery{std::move(callback.header), std::move(parsed_query.required_privileges), - [callback_fn = std::move(callback.fn), pull_plan = std::shared_ptr<PullPlanVector>{nullptr}]( - AnyStream *stream, std::optional<int> n) mutable -> std::optional<QueryHandlerResult> { - if (UNLIKELY(!pull_plan)) { - pull_plan = std::make_shared<PullPlanVector>(callback_fn()); - } - - if (pull_plan->Pull(stream, n)) { - return QueryHandlerResult::COMMIT; - } - return std::nullopt; - }, - RWType::NONE}; - // False positive report for the std::make_shared above - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) -} - constexpr auto ToStorageIsolationLevel(const IsolationLevelQuery::IsolationLevel isolation_level) noexcept { switch (isolation_level) { case IsolationLevelQuery::IsolationLevel::SNAPSHOT_ISOLATION: @@ -1912,10 +1534,6 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string, db_accessor_ = std::make_unique<storage::v3::Shard::Accessor>( interpreter_context_->db->Access(coordinator::Hlc{}, GetIsolationLevelOverride())); execution_db_accessor_.emplace(db_accessor_.get()); - - if (utils::Downcast<CypherQuery>(parsed_query.query) && interpreter_context_->trigger_store.HasTriggers()) { - trigger_context_collector_.emplace(interpreter_context_->trigger_store.GetEventTypes()); - } } utils::Timer planning_timer; @@ -1924,8 +1542,7 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string, if (utils::Downcast<CypherQuery>(parsed_query.query)) { prepared_query = PrepareCypherQuery(std::move(parsed_query), &query_execution->summary, interpreter_context_, &*execution_db_accessor_, &query_execution->execution_memory, - &query_execution->notifications, - trigger_context_collector_ ? &*trigger_context_collector_ : nullptr); + &query_execution->notifications); } else if (utils::Downcast<ExplainQuery>(parsed_query.query)) { prepared_query = PrepareExplainQuery(std::move(parsed_query), &query_execution->summary, interpreter_context_, &*execution_db_accessor_, &query_execution->execution_memory_with_exception); @@ -1960,13 +1577,9 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string, } else if (utils::Downcast<FreeMemoryQuery>(parsed_query.query)) { prepared_query = PrepareFreeMemoryQuery(std::move(parsed_query), in_explicit_transaction_, interpreter_context_); } else if (utils::Downcast<TriggerQuery>(parsed_query.query)) { - prepared_query = - PrepareTriggerQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->notifications, - interpreter_context_, &*execution_db_accessor_, params, username); + throw std::runtime_error("Unimplemented"); } else if (utils::Downcast<StreamQuery>(parsed_query.query)) { - prepared_query = - PrepareStreamQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->notifications, - interpreter_context_, &*execution_db_accessor_, params, username); + throw std::runtime_error("unimplemented"); } else if (utils::Downcast<IsolationLevelQuery>(parsed_query.query)) { prepared_query = PrepareIsolationLevelQuery(std::move(parsed_query), in_explicit_transaction_, interpreter_context_, this); @@ -2007,35 +1620,8 @@ void Interpreter::Abort() { db_accessor_->Abort(); execution_db_accessor_.reset(); db_accessor_.reset(); - trigger_context_collector_.reset(); } -namespace { -void RunTriggersIndividually(const utils::SkipList<Trigger> &triggers, InterpreterContext *interpreter_context, - TriggerContext trigger_context) { - // Run the triggers - for (const auto &trigger : triggers.access()) { - utils::MonotonicBufferResource execution_memory{kExecutionMemoryBlockSize}; - - // create a new transaction for each trigger - auto storage_acc = interpreter_context->db->Access(coordinator::Hlc{}); - DbAccessor db_accessor{&storage_acc}; - - trigger_context.AdaptForAccessor(&db_accessor); - try { - trigger.Execute(&db_accessor, &execution_memory, interpreter_context->config.execution_timeout_sec, - &interpreter_context->is_shutting_down, trigger_context, interpreter_context->auth_checker); - } catch (const utils::BasicException &exception) { - spdlog::warn("Trigger '{}' failed with exception:\n{}", trigger.Name(), exception.what()); - db_accessor.Abort(); - continue; - } - - db_accessor.Commit(); - } -} -} // namespace - void Interpreter::Commit() { // It's possible that some queries did not finish because the user did // not pull all of the results from the query. @@ -2044,49 +1630,12 @@ void Interpreter::Commit() { // a query. if (!db_accessor_) return; - std::optional<TriggerContext> trigger_context = std::nullopt; - if (trigger_context_collector_) { - trigger_context.emplace(std::move(*trigger_context_collector_).TransformToTriggerContext()); - trigger_context_collector_.reset(); - } - - if (trigger_context) { - // Run the triggers - for (const auto &trigger : interpreter_context_->trigger_store.BeforeCommitTriggers().access()) { - utils::MonotonicBufferResource execution_memory{kExecutionMemoryBlockSize}; - AdvanceCommand(); - try { - trigger.Execute(&*execution_db_accessor_, &execution_memory, interpreter_context_->config.execution_timeout_sec, - &interpreter_context_->is_shutting_down, *trigger_context, interpreter_context_->auth_checker); - } catch (const utils::BasicException &e) { - throw utils::BasicException( - fmt::format("Trigger '{}' caused the transaction to fail.\nException: {}", trigger.Name(), e.what())); - } - } - SPDLOG_DEBUG("Finished executing before commit triggers"); - } - const auto reset_necessary_members = [this]() { execution_db_accessor_.reset(); db_accessor_.reset(); - trigger_context_collector_.reset(); }; db_accessor_->Commit(coordinator::Hlc{}); - // The ordered execution of after commit triggers is heavily depending on the exclusiveness of db_accessor_->Commit(): - // only one of the transactions can be commiting at the same time, so when the commit is finished, that transaction - // probably will schedule its after commit triggers, because the other transactions that want to commit are still - // waiting for commiting or one of them just started commiting its changes. - // This means the ordered execution of after commit triggers are not guaranteed. - if (trigger_context && interpreter_context_->trigger_store.AfterCommitTriggers().size() > 0) { - interpreter_context_->after_commit_trigger_pool.AddTask( - [trigger_context = std::move(*trigger_context), interpreter_context = this->interpreter_context_, - user_transaction = std::shared_ptr(std::move(db_accessor_))]() mutable { - RunTriggersIndividually(interpreter_context->trigger_store.AfterCommitTriggers(), interpreter_context, - std::move(trigger_context)); - SPDLOG_DEBUG("Finished executing after commit triggers"); // NOLINT(bugprone-lambda-function-name) - }); - } reset_necessary_members(); diff --git a/src/query/v2/interpreter.hpp b/src/query/v2/interpreter.hpp index d789c3a84..dcc9bf2a7 100644 --- a/src/query/v2/interpreter.hpp +++ b/src/query/v2/interpreter.hpp @@ -27,8 +27,6 @@ #include "query/v2/plan/operator.hpp" #include "query/v2/plan/read_write_type_checker.hpp" #include "query/v2/stream.hpp" -#include "query/v2/stream/streams.hpp" -#include "query/v2/trigger.hpp" #include "storage/v3/isolation_level.hpp" #include "storage/v3/name_id_mapper.hpp" #include "utils/event_counter.hpp" @@ -180,13 +178,8 @@ struct InterpreterContext { utils::SkipList<QueryCacheEntry> ast_cache; utils::SkipList<PlanCacheEntry> plan_cache; - TriggerStore trigger_store; - utils::ThreadPool after_commit_trigger_pool{1}; - const InterpreterConfig config; - query::v2::stream::Streams streams; - storage::v3::LabelId NameToLabelId(std::string_view label_name) { return storage::v3::LabelId::FromUint(query_id_mapper.NameToId(label_name)); } @@ -334,7 +327,6 @@ class Interpreter final { // move this unique_ptr into a shrared_ptr. std::unique_ptr<storage::v3::Shard::Accessor> db_accessor_; std::optional<DbAccessor> execution_db_accessor_; - std::optional<TriggerContextCollector> trigger_context_collector_; bool in_explicit_transaction_{false}; bool expect_rollback_{false}; diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index 0c6ac1d56..320c2bfd0 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -26,6 +26,8 @@ #include <cppitertools/chain.hpp> #include <cppitertools/imap.hpp> +#include "expr/exceptions.hpp" +#include "query/v2/accessors.hpp" #include "query/v2/bindings/eval.hpp" #include "query/v2/bindings/symbol_table.hpp" #include "query/v2/context.hpp" @@ -35,8 +37,8 @@ #include "query/v2/path.hpp" #include "query/v2/plan/scoped_profile.hpp" #include "query/v2/procedure/cypher_types.hpp" -#include "query/v2/procedure/mg_procedure_impl.hpp" -#include "query/v2/procedure/module.hpp" +#include "query/v2/requests.hpp" +#include "query/v2/shard_request_manager.hpp" #include "storage/v3/conversions.hpp" #include "storage/v3/property_value.hpp" #include "utils/algorithm.hpp" @@ -56,6 +58,10 @@ #include "utils/temporal.hpp" #include "utils/variant_helpers.hpp" +using VertexAccessor = memgraph::query::v2::accessors::VertexAccessor; +using EdgeAccessor = memgraph::query::v2::accessors::EdgeAccessor; +using Path = memgraph::query::v2::accessors::Path; + // macro for the default implementation of LogicalOperator::Accept // that accepts the visitor and visits it's input_ operator #define ACCEPT_WITH_INPUT(class_name) \ @@ -175,63 +181,6 @@ void Once::OnceCursor::Reset() { did_pull_ = false; } CreateNode::CreateNode(const std::shared_ptr<LogicalOperator> &input, const NodeCreationInfo &node_info) : input_(input ? input : std::make_shared<Once>()), node_info_(node_info) {} -// Creates a vertex on this GraphDb. Returns a reference to vertex placed on the -// frame. -VertexAccessor &CreateLocalVertexAtomically(const NodeCreationInfo &node_info, Frame *frame, - ExecutionContext &context) { - auto &dba = *context.db_accessor; - // Evaluator should use the latest accessors, as modified in this query, when - // setting properties on new nodes. - ExpressionEvaluator evaluator(frame, context.symbol_table, context.evaluation_context, context.db_accessor, - storage::v3::View::NEW); - - std::vector<std::pair<storage::v3::PropertyId, storage::v3::PropertyValue>> properties; - if (const auto *node_info_properties = std::get_if<PropertiesMapList>(&node_info.properties)) { - properties.reserve(node_info_properties->size()); - for (const auto &[key, value_expression] : *node_info_properties) { - properties.emplace_back(key, storage::v3::TypedToPropertyValue(value_expression->Accept(evaluator))); - } - } else { - auto property_map = evaluator.Visit(*std::get<ParameterLookup *>(node_info.properties)).ValueMap(); - properties.reserve(property_map.size()); - - for (const auto &[key, value] : property_map) { - auto property_id = dba.NameToProperty(key); - properties.emplace_back(property_id, storage::v3::TypedToPropertyValue(value)); - } - } - - if (node_info.labels.empty()) { - throw QueryRuntimeException("Primary label must be defined!"); - } - const auto primary_label = node_info.labels[0]; - std::vector<storage::v3::LabelId> secondary_labels(node_info.labels.begin() + 1, node_info.labels.end()); - auto maybe_new_node = dba.InsertVertexAndValidate(primary_label, secondary_labels, properties); - if (maybe_new_node.HasError()) { - std::visit(utils::Overloaded{[&dba](const storage::v3::SchemaViolation &schema_violation) { - HandleSchemaViolation(schema_violation, dba); - }, - [](const storage::v3::Error error) { - switch (error) { - case storage::v3::Error::SERIALIZATION_ERROR: - throw TransactionSerializationException(); - case storage::v3::Error::DELETED_OBJECT: - throw QueryRuntimeException("Trying to set a label on a deleted node."); - case storage::v3::Error::VERTEX_HAS_EDGES: - case storage::v3::Error::PROPERTIES_DISABLED: - case storage::v3::Error::NONEXISTENT_OBJECT: - throw QueryRuntimeException("Unexpected error when setting a label."); - } - }}, - maybe_new_node.GetError()); - } - - context.execution_stats[ExecutionStats::Key::CREATED_NODES] += 1; - - (*frame)[node_info.symbol] = *maybe_new_node; - return (*frame)[node_info.symbol].ValueVertex(); -} - ACCEPT_WITH_INPUT(CreateNode) UniqueCursorPtr CreateNode::MakeCursor(utils::MemoryResource *mem) const { @@ -249,19 +198,7 @@ std::vector<Symbol> CreateNode::ModifiedSymbols(const SymbolTable &table) const CreateNode::CreateNodeCursor::CreateNodeCursor(const CreateNode &self, utils::MemoryResource *mem) : self_(self), input_cursor_(self.input_->MakeCursor(mem)) {} -bool CreateNode::CreateNodeCursor::Pull(Frame &frame, ExecutionContext &context) { - SCOPED_PROFILE_OP("CreateNode"); - - if (input_cursor_->Pull(frame, context)) { - auto created_vertex = CreateLocalVertexAtomically(self_.node_info_, &frame, context); - if (context.trigger_context_collector) { - context.trigger_context_collector->RegisterCreatedObject(created_vertex); - } - return true; - } - - return false; -} +bool CreateNode::CreateNodeCursor::Pull(Frame & /*frame*/, ExecutionContext & /*context*/) { return false; } void CreateNode::CreateNodeCursor::Shutdown() { input_cursor_->Shutdown(); } @@ -293,105 +230,12 @@ std::vector<Symbol> CreateExpand::ModifiedSymbols(const SymbolTable &table) cons CreateExpand::CreateExpandCursor::CreateExpandCursor(const CreateExpand &self, utils::MemoryResource *mem) : self_(self), input_cursor_(self.input_->MakeCursor(mem)) {} -namespace { - -EdgeAccessor CreateEdge(const EdgeCreationInfo &edge_info, DbAccessor *dba, VertexAccessor *from, VertexAccessor *to, - Frame *frame, ExpressionEvaluator *evaluator) { - auto maybe_edge = dba->InsertEdge(from, to, edge_info.edge_type); - if (maybe_edge.HasValue()) { - auto &edge = *maybe_edge; - if (const auto *properties = std::get_if<PropertiesMapList>(&edge_info.properties)) { - for (const auto &[key, value_expression] : *properties) { - PropsSetChecked(&edge, *dba, key, value_expression->Accept(*evaluator)); - } - } else { - auto property_map = evaluator->Visit(*std::get<ParameterLookup *>(edge_info.properties)); - for (const auto &[key, value] : property_map.ValueMap()) { - auto property_id = dba->NameToProperty(key); - PropsSetChecked(&edge, *dba, property_id, value); - } - } - - (*frame)[edge_info.symbol] = edge; - } else { - switch (maybe_edge.GetError()) { - case storage::v3::Error::SERIALIZATION_ERROR: - throw TransactionSerializationException(); - case storage::v3::Error::DELETED_OBJECT: - throw QueryRuntimeException("Trying to create an edge on a deleted node."); - case storage::v3::Error::VERTEX_HAS_EDGES: - case storage::v3::Error::PROPERTIES_DISABLED: - case storage::v3::Error::NONEXISTENT_OBJECT: - throw QueryRuntimeException("Unexpected error when creating an edge."); - } - } - - return *maybe_edge; -} - -} // namespace - -bool CreateExpand::CreateExpandCursor::Pull(Frame &frame, ExecutionContext &context) { - SCOPED_PROFILE_OP("CreateExpand"); - - if (!input_cursor_->Pull(frame, context)) return false; - - // get the origin vertex - TypedValue &vertex_value = frame[self_.input_symbol_]; - ExpectType(self_.input_symbol_, vertex_value, TypedValue::Type::Vertex); - auto &v1 = vertex_value.ValueVertex(); - - // Similarly to CreateNode, newly created edges and nodes should use the - // storage::v3::View::NEW. - // E.g. we pickup new properties: `CREATE (n {p: 42}) -[:r {ep: n.p}]-> ()` - ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor, - storage::v3::View::NEW); - - // get the destination vertex (possibly an existing node) - auto &v2 = OtherVertex(frame, context); - - // create an edge between the two nodes - auto *dba = context.db_accessor; - - auto created_edge = [&] { - switch (self_.edge_info_.direction) { - case EdgeAtom::Direction::IN: - return CreateEdge(self_.edge_info_, dba, &v2, &v1, &frame, &evaluator); - case EdgeAtom::Direction::OUT: - // in the case of an undirected CreateExpand we choose an arbitrary - // direction. this is used in the MERGE clause - // it is not allowed in the CREATE clause, and the semantic - // checker needs to ensure it doesn't reach this point - case EdgeAtom::Direction::BOTH: - return CreateEdge(self_.edge_info_, dba, &v1, &v2, &frame, &evaluator); - } - }(); - - context.execution_stats[ExecutionStats::Key::CREATED_EDGES] += 1; - if (context.trigger_context_collector) { - context.trigger_context_collector->RegisterCreatedObject(created_edge); - } - - return true; -} +bool CreateExpand::CreateExpandCursor::Pull(Frame & /*frame*/, ExecutionContext & /*context*/) { return false; } void CreateExpand::CreateExpandCursor::Shutdown() { input_cursor_->Shutdown(); } void CreateExpand::CreateExpandCursor::Reset() { input_cursor_->Reset(); } -VertexAccessor &CreateExpand::CreateExpandCursor::OtherVertex(Frame &frame, ExecutionContext &context) { - if (self_.existing_node_) { - TypedValue &dest_node_value = frame[self_.node_info_.symbol]; - ExpectType(self_.node_info_.symbol, dest_node_value, TypedValue::Type::Vertex); - return dest_node_value.ValueVertex(); - } - auto &created_vertex = CreateLocalVertexAtomically(self_.node_info_, &frame, context); - if (context.trigger_context_collector) { - context.trigger_context_collector->RegisterCreatedObject(created_vertex); - } - return created_vertex; -} - template <class TVerticesFun> class ScanAllCursor : public Cursor { public: @@ -402,28 +246,7 @@ class ScanAllCursor : public Cursor { get_vertices_(std::move(get_vertices)), op_name_(op_name) {} - bool Pull(Frame &frame, ExecutionContext &context) override { - SCOPED_PROFILE_OP(op_name_); - - if (MustAbort(context)) throw HintedAbortError(); - - while (!vertices_ || vertices_it_.value() == vertices_.value().end()) { - if (!input_cursor_->Pull(frame, context)) return false; - // We need a getter function, because in case of exhausting a lazy - // iterable, we cannot simply reset it by calling begin(). - auto next_vertices = get_vertices_(frame, context); - if (!next_vertices) continue; - // Since vertices iterator isn't nothrow_move_assignable, we have to use - // the roundabout assignment + emplace, instead of simple: - // vertices _ = get_vertices_(frame, context); - vertices_.emplace(std::move(next_vertices.value())); - vertices_it_.emplace(vertices_.value().begin()); - } - - frame[output_symbol_] = *vertices_it_.value(); - ++vertices_it_.value(); - return true; - } + bool Pull(Frame & /*frame*/, ExecutionContext & /*context*/) override { return false; } void Shutdown() override { input_cursor_->Shutdown(); } @@ -440,6 +263,8 @@ class ScanAllCursor : public Cursor { std::optional<typename std::result_of<TVerticesFun(Frame &, ExecutionContext &)>::type::value_type> vertices_; std::optional<decltype(vertices_.value().begin())> vertices_it_; const char *op_name_; + std::vector<msgs::ScanVerticesResponse> current_batch; + msgs::ExecutionState<msgs::ScanVerticesRequest> request_state; }; ScanAll::ScanAll(const std::shared_ptr<LogicalOperator> &input, Symbol output_symbol, storage::v3::View view) @@ -450,7 +275,7 @@ ACCEPT_WITH_INPUT(ScanAll) UniqueCursorPtr ScanAll::MakeCursor(utils::MemoryResource *mem) const { EventCounter::IncrementCounter(EventCounter::ScanAllOperator); - auto vertices = [this](Frame &, ExecutionContext &context) { + auto vertices = [this](Frame & /*unused*/, ExecutionContext &context) { auto *db = context.db_accessor; return std::make_optional(db->Vertices(view_)); }; @@ -473,7 +298,7 @@ ACCEPT_WITH_INPUT(ScanAllByLabel) UniqueCursorPtr ScanAllByLabel::MakeCursor(utils::MemoryResource *mem) const { EventCounter::IncrementCounter(EventCounter::ScanAllByLabelOperator); - auto vertices = [this](Frame &, ExecutionContext &context) { + auto vertices = [this](Frame & /*unused*/, ExecutionContext &context) { auto *db = context.db_accessor; return std::make_optional(db->Vertices(view_, label_)); }; @@ -572,9 +397,6 @@ UniqueCursorPtr ScanAllByLabelPropertyValue::MakeCursor(utils::MemoryResource *m ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor, view_); auto value = expression_->Accept(evaluator); if (value.IsNull()) return std::nullopt; - // if (!value.IsPropertyValue()) { - // throw QueryRuntimeException("'{}' cannot be used as a property value.", value.type()); - // } return std::make_optional(db->Vertices(view_, label_, property_, storage::v3::TypedToPropertyValue(value))); }; return MakeUniqueCursorPtr<ScanAllCursor<decltype(vertices)>>(mem, output_symbol_, input_->MakeCursor(mem), @@ -591,7 +413,7 @@ ACCEPT_WITH_INPUT(ScanAllByLabelProperty) UniqueCursorPtr ScanAllByLabelProperty::MakeCursor(utils::MemoryResource *mem) const { EventCounter::IncrementCounter(EventCounter::ScanAllByLabelPropertyOperator); - auto vertices = [this](Frame &frame, ExecutionContext &context) { + auto vertices = [this](Frame & /*frame*/, ExecutionContext &context) { auto *db = context.db_accessor; return std::make_optional(db->Vertices(view_, label_, property_)); }; @@ -625,12 +447,6 @@ UniqueCursorPtr ScanAllById::MakeCursor(utils::MemoryResource *mem) const { } namespace { -bool CheckExistingNode(const VertexAccessor &new_node, const Symbol &existing_node_sym, Frame &frame) { - const TypedValue &existing_node = frame[existing_node_sym]; - if (existing_node.IsNull()) return false; - ExpectType(existing_node_sym, existing_node, TypedValue::Type::Vertex); - return existing_node.ValueVertex() == new_node; -} template <class TEdges> auto UnwrapEdgesResult(storage::v3::Result<TEdges> &&result) { @@ -677,53 +493,7 @@ std::vector<Symbol> Expand::ModifiedSymbols(const SymbolTable &table) const { Expand::ExpandCursor::ExpandCursor(const Expand &self, utils::MemoryResource *mem) : self_(self), input_cursor_(self.input_->MakeCursor(mem)) {} -bool Expand::ExpandCursor::Pull(Frame &frame, ExecutionContext &context) { - SCOPED_PROFILE_OP("Expand"); - - // A helper function for expanding a node from an edge. - auto pull_node = [this, &frame](const EdgeAccessor &new_edge, EdgeAtom::Direction direction) { - if (self_.common_.existing_node) return; - switch (direction) { - case EdgeAtom::Direction::IN: - frame[self_.common_.node_symbol] = new_edge.From(); - break; - case EdgeAtom::Direction::OUT: - frame[self_.common_.node_symbol] = new_edge.To(); - break; - case EdgeAtom::Direction::BOTH: - LOG_FATAL("Must indicate exact expansion direction here"); - } - }; - - while (true) { - if (MustAbort(context)) throw HintedAbortError(); - // attempt to get a value from the incoming edges - if (in_edges_ && *in_edges_it_ != in_edges_->end()) { - auto edge = *(*in_edges_it_)++; - frame[self_.common_.edge_symbol] = edge; - pull_node(edge, EdgeAtom::Direction::IN); - return true; - } - - // attempt to get a value from the outgoing edges - if (out_edges_ && *out_edges_it_ != out_edges_->end()) { - auto edge = *(*out_edges_it_)++; - // when expanding in EdgeAtom::Direction::BOTH directions - // we should do only one expansion for cycles, and it was - // already done in the block above - if (self_.common_.direction == EdgeAtom::Direction::BOTH && edge.IsCycle()) continue; - frame[self_.common_.edge_symbol] = edge; - pull_node(edge, EdgeAtom::Direction::OUT); - return true; - } - - // If we are here, either the edges have not been initialized, - // or they have been exhausted. Attempt to initialize the edges. - if (!InitEdges(frame, context)) return false; - - // we have re-initialized the edges, continue with the loop - } -} +bool Expand::ExpandCursor::Pull(Frame & /*frame*/, ExecutionContext & /*context*/) { return false; } void Expand::ExpandCursor::Shutdown() { input_cursor_->Shutdown(); } @@ -735,57 +505,8 @@ void Expand::ExpandCursor::Reset() { out_edges_it_ = std::nullopt; } -bool Expand::ExpandCursor::InitEdges(Frame &frame, ExecutionContext &context) { - // Input Vertex could be null if it is created by a failed optional match. In - // those cases we skip that input pull and continue with the next. - while (true) { - if (!input_cursor_->Pull(frame, context)) return false; - TypedValue &vertex_value = frame[self_.input_symbol_]; - - // Null check due to possible failed optional match. - if (vertex_value.IsNull()) continue; - - ExpectType(self_.input_symbol_, vertex_value, TypedValue::Type::Vertex); - auto &vertex = vertex_value.ValueVertex(); - - auto direction = self_.common_.direction; - if (direction == EdgeAtom::Direction::IN || direction == EdgeAtom::Direction::BOTH) { - if (self_.common_.existing_node) { - TypedValue &existing_node = frame[self_.common_.node_symbol]; - // old_node_value may be Null when using optional matching - if (!existing_node.IsNull()) { - ExpectType(self_.common_.node_symbol, existing_node, TypedValue::Type::Vertex); - in_edges_.emplace( - UnwrapEdgesResult(vertex.InEdges(self_.view_, self_.common_.edge_types, existing_node.ValueVertex()))); - } - } else { - in_edges_.emplace(UnwrapEdgesResult(vertex.InEdges(self_.view_, self_.common_.edge_types))); - } - if (in_edges_) { - in_edges_it_.emplace(in_edges_->begin()); - } - } - - if (direction == EdgeAtom::Direction::OUT || direction == EdgeAtom::Direction::BOTH) { - if (self_.common_.existing_node) { - TypedValue &existing_node = frame[self_.common_.node_symbol]; - // old_node_value may be Null when using optional matching - if (!existing_node.IsNull()) { - ExpectType(self_.common_.node_symbol, existing_node, TypedValue::Type::Vertex); - out_edges_.emplace( - UnwrapEdgesResult(vertex.OutEdges(self_.view_, self_.common_.edge_types, existing_node.ValueVertex()))); - } - } else { - out_edges_.emplace(UnwrapEdgesResult(vertex.OutEdges(self_.view_, self_.common_.edge_types))); - } - if (out_edges_) { - out_edges_it_.emplace(out_edges_->begin()); - } - } - - return true; - } -} +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) +bool Expand::ExpandCursor::InitEdges(Frame & /*frame*/, ExecutionContext & /*context*/) { return true; } ExpandVariable::ExpandVariable(const std::shared_ptr<LogicalOperator> &input, Symbol input_symbol, Symbol node_symbol, Symbol edge_symbol, EdgeAtom::Type type, EdgeAtom::Direction direction, @@ -867,33 +588,7 @@ class ExpandVariableCursor : public Cursor { ExpandVariableCursor(const ExpandVariable &self, utils::MemoryResource *mem) : self_(self), input_cursor_(self.input_->MakeCursor(mem)), edges_(mem), edges_it_(mem) {} - bool Pull(Frame &frame, ExecutionContext &context) override { - SCOPED_PROFILE_OP("ExpandVariable"); - - ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor, - storage::v3::View::OLD); - while (true) { - if (Expand(frame, context)) return true; - - if (PullInput(frame, context)) { - // if lower bound is zero we also yield empty paths - if (lower_bound_ == 0) { - auto &start_vertex = frame[self_.input_symbol_].ValueVertex(); - if (!self_.common_.existing_node) { - frame[self_.common_.node_symbol] = start_vertex; - return true; - } else if (CheckExistingNode(start_vertex, self_.common_.node_symbol, frame)) { - return true; - } - } - // if lower bound is not zero, we just continue, the next - // loop iteration will attempt to expand and we're good - } else - return false; - // else continue with the loop, try to expand again - // because we succesfully pulled from the input - } - } + bool Pull(Frame & /*frame*/, ExecutionContext & /*context*/) override { return false; } void Shutdown() override { input_cursor_->Shutdown(); } @@ -922,70 +617,6 @@ class ExpandVariableCursor : public Cursor { // an iterator indicating the position in the corresponding edges_ element utils::pmr::vector<decltype(edges_.begin()->begin())> edges_it_; - /** - * Helper function that Pulls from the input vertex and - * makes iteration over it's edges possible. - * - * @return If the Pull succeeded. If not, this VariableExpandCursor - * is exhausted. - */ - bool PullInput(Frame &frame, ExecutionContext &context) { - // Input Vertex could be null if it is created by a failed optional match. - // In those cases we skip that input pull and continue with the next. - while (true) { - if (MustAbort(context)) throw HintedAbortError(); - if (!input_cursor_->Pull(frame, context)) return false; - TypedValue &vertex_value = frame[self_.input_symbol_]; - - // Null check due to possible failed optional match. - if (vertex_value.IsNull()) continue; - - ExpectType(self_.input_symbol_, vertex_value, TypedValue::Type::Vertex); - auto &vertex = vertex_value.ValueVertex(); - - // Evaluate the upper and lower bounds. - ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor, - storage::v3::View::OLD); - auto calc_bound = [&evaluator](auto &bound) { - auto value = expr::EvaluateInt(&evaluator, bound, "Variable expansion bound"); - if (value < 0) throw QueryRuntimeException("Variable expansion bound must be a non-negative integer."); - return value; - }; - - lower_bound_ = self_.lower_bound_ ? calc_bound(self_.lower_bound_) : 1; - upper_bound_ = self_.upper_bound_ ? calc_bound(self_.upper_bound_) : std::numeric_limits<int64_t>::max(); - - if (upper_bound_ > 0) { - auto *memory = edges_.get_allocator().GetMemoryResource(); - edges_.emplace_back(ExpandFromVertex(vertex, self_.common_.direction, self_.common_.edge_types, memory)); - edges_it_.emplace_back(edges_.back().begin()); - } - - // reset the frame value to an empty edge list - auto *pull_memory = context.evaluation_context.memory; - frame[self_.common_.edge_symbol] = TypedValue::TVector(pull_memory); - - return true; - } - } - - // Helper function for appending an edge to the list on the frame. - void AppendEdge(const EdgeAccessor &new_edge, utils::pmr::vector<TypedValue> *edges_on_frame) { - // We are placing an edge on the frame. It is possible that there already - // exists an edge on the frame for this level. If so first remove it. - DMG_ASSERT(edges_.size() > 0, "Edges are empty"); - if (self_.is_reverse_) { - // TODO: This is innefficient, we should look into replacing - // vector with something else for TypedValue::List. - size_t diff = edges_on_frame->size() - std::min(edges_on_frame->size(), edges_.size() - 1U); - if (diff > 0U) edges_on_frame->erase(edges_on_frame->begin(), edges_on_frame->begin() + diff); - edges_on_frame->emplace(edges_on_frame->begin(), new_edge); - } else { - edges_on_frame->resize(std::min(edges_on_frame->size(), edges_.size() - 1U)); - edges_on_frame->emplace_back(new_edge); - } - } - /** * Performs a single expansion for the current state of this * VariableExpansionCursor. @@ -995,81 +626,8 @@ class ExpandVariableCursor : public Cursor { * case no more expansions are available from the current input * vertex and another Pull from the input cursor should be performed. */ - bool Expand(Frame &frame, ExecutionContext &context) { - ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor, - storage::v3::View::OLD); - // Some expansions might not be valid due to edge uniqueness and - // existing_node criterions, so expand in a loop until either the input - // vertex is exhausted or a valid variable-length expansion is available. - while (true) { - if (MustAbort(context)) throw HintedAbortError(); - // pop from the stack while there is stuff to pop and the current - // level is exhausted - while (!edges_.empty() && edges_it_.back() == edges_.back().end()) { - edges_.pop_back(); - edges_it_.pop_back(); - } - - // check if we exhausted everything, if so return false - if (edges_.empty()) return false; - - // we use this a lot - auto &edges_on_frame = frame[self_.common_.edge_symbol].ValueList(); - - // it is possible that edges_on_frame does not contain as many - // elements as edges_ due to edge-uniqueness (when a whole layer - // gets exhausted but no edges are valid). for that reason only - // pop from edges_on_frame if they contain enough elements - if (self_.is_reverse_) { - auto diff = edges_on_frame.size() - std::min(edges_on_frame.size(), edges_.size()); - if (diff > 0) { - edges_on_frame.erase(edges_on_frame.begin(), edges_on_frame.begin() + diff); - } - } else { - edges_on_frame.resize(std::min(edges_on_frame.size(), edges_.size())); - } - - // if we are here, we have a valid stack, - // get the edge, increase the relevant iterator - auto current_edge = *edges_it_.back()++; - - // Check edge-uniqueness. - bool found_existing = - std::any_of(edges_on_frame.begin(), edges_on_frame.end(), - [¤t_edge](const TypedValue &edge) { return current_edge.first == edge.ValueEdge(); }); - if (found_existing) continue; - - AppendEdge(current_edge.first, &edges_on_frame); - VertexAccessor current_vertex = - current_edge.second == EdgeAtom::Direction::IN ? current_edge.first.From() : current_edge.first.To(); - - if (!self_.common_.existing_node) { - frame[self_.common_.node_symbol] = current_vertex; - } - - // Skip expanding out of filtered expansion. - frame[self_.filter_lambda_.inner_edge_symbol] = current_edge.first; - frame[self_.filter_lambda_.inner_node_symbol] = current_vertex; - if (self_.filter_lambda_.expression && !EvaluateFilter(evaluator, self_.filter_lambda_.expression)) continue; - - // we are doing depth-first search, so place the current - // edge's expansions onto the stack, if we should continue to expand - if (upper_bound_ > static_cast<int64_t>(edges_.size())) { - auto *memory = edges_.get_allocator().GetMemoryResource(); - edges_.emplace_back( - ExpandFromVertex(current_vertex, self_.common_.direction, self_.common_.edge_types, memory)); - edges_it_.emplace_back(edges_.back().begin()); - } - - if (self_.common_.existing_node && !CheckExistingNode(current_vertex, self_.common_.node_symbol, frame)) continue; - - // We only yield true if we satisfy the lower bound. - if (static_cast<int64_t>(edges_on_frame.size()) >= lower_bound_) - return true; - else - continue; - } - } + // NOLINTNEXTLINE(readability-convert-member-functions-to-static) + bool Expand(Frame & /*frame*/, ExecutionContext & /*context*/) { return false; } }; class STShortestPathCursor : public query::v2::plan::Cursor { @@ -1082,37 +640,7 @@ class STShortestPathCursor : public query::v2::plan::Cursor { "set!"); } - bool Pull(Frame &frame, ExecutionContext &context) override { - SCOPED_PROFILE_OP("STShortestPath"); - - ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor, - storage::v3::View::OLD); - while (input_cursor_->Pull(frame, context)) { - const auto &source_tv = frame[self_.input_symbol_]; - const auto &sink_tv = frame[self_.common_.node_symbol]; - - // It is possible that source or sink vertex is Null due to optional - // matching. - if (source_tv.IsNull() || sink_tv.IsNull()) continue; - - const auto &source = source_tv.ValueVertex(); - const auto &sink = sink_tv.ValueVertex(); - - int64_t lower_bound = - self_.lower_bound_ ? expr::EvaluateInt(&evaluator, self_.lower_bound_, "Min depth in breadth-first expansion") - : 1; - int64_t upper_bound = - self_.upper_bound_ ? expr::EvaluateInt(&evaluator, self_.upper_bound_, "Max depth in breadth-first expansion") - : std::numeric_limits<int64_t>::max(); - - if (upper_bound < 1 || lower_bound > upper_bound) continue; - - if (FindPath(*context.db_accessor, source, sink, lower_bound, upper_bound, &frame, &evaluator, context)) { - return true; - } - } - return false; - } + bool Pull(Frame & /*frame*/, ExecutionContext & /*context*/) override { return false; } void Shutdown() override { input_cursor_->Shutdown(); } @@ -1123,172 +651,6 @@ class STShortestPathCursor : public query::v2::plan::Cursor { UniqueCursorPtr input_cursor_; using VertexEdgeMapT = utils::pmr::unordered_map<VertexAccessor, std::optional<EdgeAccessor>>; - - void ReconstructPath(const VertexAccessor &midpoint, const VertexEdgeMapT &in_edge, const VertexEdgeMapT &out_edge, - Frame *frame, utils::MemoryResource *pull_memory) { - utils::pmr::vector<TypedValue> result(pull_memory); - auto last_vertex = midpoint; - while (true) { - const auto &last_edge = in_edge.at(last_vertex); - if (!last_edge) break; - last_vertex = last_edge->From() == last_vertex ? last_edge->To() : last_edge->From(); - result.emplace_back(*last_edge); - } - std::reverse(result.begin(), result.end()); - last_vertex = midpoint; - while (true) { - const auto &last_edge = out_edge.at(last_vertex); - if (!last_edge) break; - last_vertex = last_edge->From() == last_vertex ? last_edge->To() : last_edge->From(); - result.emplace_back(*last_edge); - } - frame->at(self_.common_.edge_symbol) = std::move(result); - } - - bool ShouldExpand(const VertexAccessor &vertex, const EdgeAccessor &edge, Frame *frame, - ExpressionEvaluator *evaluator) { - if (!self_.filter_lambda_.expression) return true; - - frame->at(self_.filter_lambda_.inner_node_symbol) = vertex; - frame->at(self_.filter_lambda_.inner_edge_symbol) = edge; - - TypedValue result = self_.filter_lambda_.expression->Accept(*evaluator); - if (result.IsNull()) return false; - if (result.IsBool()) return result.ValueBool(); - - throw QueryRuntimeException("Expansion condition must evaluate to boolean or null"); - } - - bool FindPath(const DbAccessor &dba, const VertexAccessor &source, const VertexAccessor &sink, int64_t lower_bound, - int64_t upper_bound, Frame *frame, ExpressionEvaluator *evaluator, const ExecutionContext &context) { - using utils::Contains; - - if (source == sink) return false; - - // We expand from both directions, both from the source and the sink. - // Expansions meet at the middle of the path if it exists. This should - // perform better for real-world like graphs where the expansion front - // grows exponentially, effectively reducing the exponent by half. - - auto *pull_memory = evaluator->GetMemoryResource(); - // Holds vertices at the current level of expansion from the source - // (sink). - utils::pmr::vector<VertexAccessor> source_frontier(pull_memory); - utils::pmr::vector<VertexAccessor> sink_frontier(pull_memory); - - // Holds vertices we can expand to from `source_frontier` - // (`sink_frontier`). - utils::pmr::vector<VertexAccessor> source_next(pull_memory); - utils::pmr::vector<VertexAccessor> sink_next(pull_memory); - - // Maps each vertex we visited expanding from the source (sink) to the - // edge used. Necessary for path reconstruction. - VertexEdgeMapT in_edge(pull_memory); - VertexEdgeMapT out_edge(pull_memory); - - size_t current_length = 0; - - source_frontier.emplace_back(source); - in_edge[source] = std::nullopt; - sink_frontier.emplace_back(sink); - out_edge[sink] = std::nullopt; - - while (true) { - if (MustAbort(context)) throw HintedAbortError(); - // Top-down step (expansion from the source). - ++current_length; - if (current_length > upper_bound) return false; - - for (const auto &vertex : source_frontier) { - if (self_.common_.direction != EdgeAtom::Direction::IN) { - auto out_edges = UnwrapEdgesResult(vertex.OutEdges(storage::v3::View::OLD, self_.common_.edge_types)); - for (const auto &edge : out_edges) { - if (ShouldExpand(edge.To(), edge, frame, evaluator) && !Contains(in_edge, edge.To())) { - in_edge.emplace(edge.To(), edge); - if (Contains(out_edge, edge.To())) { - if (current_length >= lower_bound) { - ReconstructPath(edge.To(), in_edge, out_edge, frame, pull_memory); - return true; - } else { - return false; - } - } - source_next.push_back(edge.To()); - } - } - } - if (self_.common_.direction != EdgeAtom::Direction::OUT) { - auto in_edges = UnwrapEdgesResult(vertex.InEdges(storage::v3::View::OLD, self_.common_.edge_types)); - for (const auto &edge : in_edges) { - if (ShouldExpand(edge.From(), edge, frame, evaluator) && !Contains(in_edge, edge.From())) { - in_edge.emplace(edge.From(), edge); - if (Contains(out_edge, edge.From())) { - if (current_length >= lower_bound) { - ReconstructPath(edge.From(), in_edge, out_edge, frame, pull_memory); - return true; - } else { - return false; - } - } - source_next.push_back(edge.From()); - } - } - } - } - - if (source_next.empty()) return false; - source_frontier.clear(); - std::swap(source_frontier, source_next); - - // Bottom-up step (expansion from the sink). - ++current_length; - if (current_length > upper_bound) return false; - - // When expanding from the sink we have to be careful which edge - // endpoint we pass to `should_expand`, because everything is - // reversed. - for (const auto &vertex : sink_frontier) { - if (self_.common_.direction != EdgeAtom::Direction::OUT) { - auto out_edges = UnwrapEdgesResult(vertex.OutEdges(storage::v3::View::OLD, self_.common_.edge_types)); - for (const auto &edge : out_edges) { - if (ShouldExpand(vertex, edge, frame, evaluator) && !Contains(out_edge, edge.To())) { - out_edge.emplace(edge.To(), edge); - if (Contains(in_edge, edge.To())) { - if (current_length >= lower_bound) { - ReconstructPath(edge.To(), in_edge, out_edge, frame, pull_memory); - return true; - } else { - return false; - } - } - sink_next.push_back(edge.To()); - } - } - } - if (self_.common_.direction != EdgeAtom::Direction::IN) { - auto in_edges = UnwrapEdgesResult(vertex.InEdges(storage::v3::View::OLD, self_.common_.edge_types)); - for (const auto &edge : in_edges) { - if (ShouldExpand(vertex, edge, frame, evaluator) && !Contains(out_edge, edge.From())) { - out_edge.emplace(edge.From(), edge); - if (Contains(in_edge, edge.From())) { - if (current_length >= lower_bound) { - ReconstructPath(edge.From(), in_edge, out_edge, frame, pull_memory); - return true; - } else { - return false; - } - } - sink_next.push_back(edge.From()); - } - } - } - } - - if (sink_next.empty()) return false; - sink_frontier.clear(); - std::swap(sink_frontier, sink_next); - } - } }; class SingleSourceShortestPathCursor : public query::v2::plan::Cursor { @@ -1306,119 +668,7 @@ class SingleSourceShortestPathCursor : public query::v2::plan::Cursor { "should be used instead!"); } - bool Pull(Frame &frame, ExecutionContext &context) override { - SCOPED_PROFILE_OP("SingleSourceShortestPath"); - - ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor, - storage::v3::View::OLD); - - // for the given (edge, vertex) pair checks if they satisfy the - // "where" condition. if so, places them in the to_visit_ structure. - auto expand_pair = [this, &evaluator, &frame](EdgeAccessor edge, VertexAccessor vertex) { - // if we already processed the given vertex it doesn't get expanded - if (processed_.find(vertex) != processed_.end()) return; - - frame[self_.filter_lambda_.inner_edge_symbol] = edge; - frame[self_.filter_lambda_.inner_node_symbol] = vertex; - - if (self_.filter_lambda_.expression) { - TypedValue result = self_.filter_lambda_.expression->Accept(evaluator); - switch (result.type()) { - case TypedValue::Type::Null: - return; - case TypedValue::Type::Bool: - if (!result.ValueBool()) return; - break; - default: - throw QueryRuntimeException("Expansion condition must evaluate to boolean or null."); - } - } - to_visit_next_.emplace_back(edge, vertex); - processed_.emplace(vertex, edge); - }; - - // populates the to_visit_next_ structure with expansions - // from the given vertex. skips expansions that don't satisfy - // the "where" condition. - auto expand_from_vertex = [this, &expand_pair](const auto &vertex) { - if (self_.common_.direction != EdgeAtom::Direction::IN) { - auto out_edges = UnwrapEdgesResult(vertex.OutEdges(storage::v3::View::OLD, self_.common_.edge_types)); - for (const auto &edge : out_edges) expand_pair(edge, edge.To()); - } - if (self_.common_.direction != EdgeAtom::Direction::OUT) { - auto in_edges = UnwrapEdgesResult(vertex.InEdges(storage::v3::View::OLD, self_.common_.edge_types)); - for (const auto &edge : in_edges) expand_pair(edge, edge.From()); - } - }; - - // do it all in a loop because we skip some elements - while (true) { - if (MustAbort(context)) throw HintedAbortError(); - // if we have nothing to visit on the current depth, switch to next - if (to_visit_current_.empty()) to_visit_current_.swap(to_visit_next_); - - // if current is still empty, it means both are empty, so pull from - // input - if (to_visit_current_.empty()) { - if (!input_cursor_->Pull(frame, context)) return false; - - to_visit_current_.clear(); - to_visit_next_.clear(); - processed_.clear(); - - const auto &vertex_value = frame[self_.input_symbol_]; - // it is possible that the vertex is Null due to optional matching - if (vertex_value.IsNull()) continue; - lower_bound_ = self_.lower_bound_ - ? expr::EvaluateInt(&evaluator, self_.lower_bound_, "Min depth in breadth-first expansion") - : 1; - upper_bound_ = self_.upper_bound_ - ? expr::EvaluateInt(&evaluator, self_.upper_bound_, "Max depth in breadth-first expansion") - : std::numeric_limits<int64_t>::max(); - - if (upper_bound_ < 1 || lower_bound_ > upper_bound_) continue; - - const auto &vertex = vertex_value.ValueVertex(); - processed_.emplace(vertex, std::nullopt); - expand_from_vertex(vertex); - - // go back to loop start and see if we expanded anything - continue; - } - - // take the next expansion from the queue - auto expansion = to_visit_current_.back(); - to_visit_current_.pop_back(); - - // create the frame value for the edges - auto *pull_memory = context.evaluation_context.memory; - utils::pmr::vector<TypedValue> edge_list(pull_memory); - edge_list.emplace_back(expansion.first); - auto last_vertex = expansion.second; - while (true) { - const EdgeAccessor &last_edge = edge_list.back().ValueEdge(); - last_vertex = last_edge.From() == last_vertex ? last_edge.To() : last_edge.From(); - // origin_vertex must be in processed - const auto &previous_edge = processed_.find(last_vertex)->second; - if (!previous_edge) break; - - edge_list.emplace_back(previous_edge.value()); - } - - // expand only if what we've just expanded is less then max depth - if (static_cast<int64_t>(edge_list.size()) < upper_bound_) expand_from_vertex(expansion.second); - - if (static_cast<int64_t>(edge_list.size()) < lower_bound_) continue; - - frame[self_.common_.node_symbol] = expansion.second; - - // place edges on the frame in the correct order - std::reverse(edge_list.begin(), edge_list.end()); - frame[self_.common_.edge_symbol] = std::move(edge_list); - - return true; - } - } + bool Pull(Frame & /*frame*/, ExecutionContext & /*context*/) override { return true; } void Shutdown() override { input_cursor_->Shutdown(); } @@ -1457,184 +707,7 @@ class ExpandWeightedShortestPathCursor : public query::v2::plan::Cursor { yielded_vertices_(mem), pq_(mem) {} - bool Pull(Frame &frame, ExecutionContext &context) override { - SCOPED_PROFILE_OP("ExpandWeightedShortestPath"); - - ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor, - storage::v3::View::OLD); - auto create_state = [this](const VertexAccessor &vertex, int64_t depth) { - return std::make_pair(vertex, upper_bound_set_ ? depth : 0); - }; - - // For the given (edge, vertex, weight, depth) tuple checks if they - // satisfy the "where" condition. if so, places them in the priority - // queue. - auto expand_pair = [this, &evaluator, &frame, &create_state](const EdgeAccessor &edge, const VertexAccessor &vertex, - const TypedValue &total_weight, int64_t depth) { - auto *memory = evaluator.GetMemoryResource(); - if (self_.filter_lambda_.expression) { - frame[self_.filter_lambda_.inner_edge_symbol] = edge; - frame[self_.filter_lambda_.inner_node_symbol] = vertex; - - if (!EvaluateFilter(evaluator, self_.filter_lambda_.expression)) return; - } - - frame[self_.weight_lambda_->inner_edge_symbol] = edge; - frame[self_.weight_lambda_->inner_node_symbol] = vertex; - - TypedValue current_weight = self_.weight_lambda_->expression->Accept(evaluator); - - if (!current_weight.IsNumeric() && !current_weight.IsDuration()) { - throw QueryRuntimeException("Calculated weight must be numeric or a Duration, got {}.", current_weight.type()); - } - - const auto is_valid_numeric = [&] { - return current_weight.IsNumeric() && (current_weight >= TypedValue(0, memory)).ValueBool(); - }; - - const auto is_valid_duration = [&] { - return current_weight.IsDuration() && (current_weight >= TypedValue(utils::Duration(0), memory)).ValueBool(); - }; - - if (!is_valid_numeric() && !is_valid_duration()) { - throw QueryRuntimeException("Calculated weight must be non-negative!"); - } - - auto next_state = create_state(vertex, depth); - - TypedValue next_weight = std::invoke([&] { - if (total_weight.IsNull()) { - return current_weight; - } - - ValidateWeightTypes(current_weight, total_weight); - - return TypedValue(current_weight, memory) + total_weight; - }); - - auto found_it = total_cost_.find(next_state); - if (found_it != total_cost_.end() && (found_it->second.IsNull() || (found_it->second <= next_weight).ValueBool())) - return; - - pq_.push({next_weight, depth + 1, vertex, edge}); - }; - - // Populates the priority queue structure with expansions - // from the given vertex. skips expansions that don't satisfy - // the "where" condition. - auto expand_from_vertex = [this, &expand_pair](const VertexAccessor &vertex, const TypedValue &weight, - int64_t depth) { - if (self_.common_.direction != EdgeAtom::Direction::IN) { - auto out_edges = UnwrapEdgesResult(vertex.OutEdges(storage::v3::View::OLD, self_.common_.edge_types)); - for (const auto &edge : out_edges) { - expand_pair(edge, edge.To(), weight, depth); - } - } - if (self_.common_.direction != EdgeAtom::Direction::OUT) { - auto in_edges = UnwrapEdgesResult(vertex.InEdges(storage::v3::View::OLD, self_.common_.edge_types)); - for (const auto &edge : in_edges) { - expand_pair(edge, edge.From(), weight, depth); - } - } - }; - - while (true) { - if (MustAbort(context)) throw HintedAbortError(); - if (pq_.empty()) { - if (!input_cursor_->Pull(frame, context)) return false; - const auto &vertex_value = frame[self_.input_symbol_]; - if (vertex_value.IsNull()) continue; - auto vertex = vertex_value.ValueVertex(); - if (self_.common_.existing_node) { - const auto &node = frame[self_.common_.node_symbol]; - // Due to optional matching the existing node could be null. - // Skip expansion for such nodes. - if (node.IsNull()) continue; - } - if (self_.upper_bound_) { - upper_bound_ = - expr::EvaluateInt(&evaluator, self_.upper_bound_, "Max depth in weighted shortest path expansion"); - upper_bound_set_ = true; - } else { - upper_bound_ = std::numeric_limits<int64_t>::max(); - upper_bound_set_ = false; - } - if (upper_bound_ < 1) - throw QueryRuntimeException( - "Maximum depth in weighted shortest path expansion must be at " - "least 1."); - - // Clear existing data structures. - previous_.clear(); - total_cost_.clear(); - yielded_vertices_.clear(); - - pq_.push({TypedValue(), 0, vertex, std::nullopt}); - // We are adding the starting vertex to the set of yielded vertices - // because we don't want to yield paths that end with the starting - // vertex. - yielded_vertices_.insert(vertex); - } - - while (!pq_.empty()) { - if (MustAbort(context)) throw HintedAbortError(); - auto [current_weight, current_depth, current_vertex, current_edge] = pq_.top(); - pq_.pop(); - - auto current_state = create_state(current_vertex, current_depth); - - // Check if the vertex has already been processed. - if (total_cost_.find(current_state) != total_cost_.end()) { - continue; - } - previous_.emplace(current_state, current_edge); - total_cost_.emplace(current_state, current_weight); - - // Expand only if what we've just expanded is less than max depth. - if (current_depth < upper_bound_) expand_from_vertex(current_vertex, current_weight, current_depth); - - // If we yielded a path for a vertex already, make the expansion but - // don't return the path again. - if (yielded_vertices_.find(current_vertex) != yielded_vertices_.end()) continue; - - // Reconstruct the path. - auto last_vertex = current_vertex; - auto last_depth = current_depth; - auto *pull_memory = context.evaluation_context.memory; - utils::pmr::vector<TypedValue> edge_list(pull_memory); - while (true) { - // Origin_vertex must be in previous. - const auto &previous_edge = previous_.find(create_state(last_vertex, last_depth))->second; - if (!previous_edge) break; - last_vertex = previous_edge->From() == last_vertex ? previous_edge->To() : previous_edge->From(); - last_depth--; - edge_list.emplace_back(previous_edge.value()); - } - - // Place destination node on the frame, handle existence flag. - if (self_.common_.existing_node) { - const auto &node = frame[self_.common_.node_symbol]; - if ((node != TypedValue(current_vertex, pull_memory)).ValueBool()) - continue; - else - // Prevent expanding other paths, because we found the - // shortest to existing node. - ClearQueue(); - } else { - frame[self_.common_.node_symbol] = current_vertex; - } - - if (!self_.is_reverse_) { - // Place edges on the frame in the correct order. - std::reverse(edge_list.begin(), edge_list.end()); - } - frame[self_.common_.edge_symbol] = std::move(edge_list); - frame[self_.total_weight_.value()] = current_weight; - yielded_vertices_.insert(current_vertex); - return true; - } - } - } + bool Pull(Frame & /*frame*/, ExecutionContext & /*context*/) override { return false; } void Shutdown() override { input_cursor_->Shutdown(); } @@ -1733,71 +806,7 @@ class ConstructNamedPathCursor : public Cursor { ConstructNamedPathCursor(const ConstructNamedPath &self, utils::MemoryResource *mem) : self_(self), input_cursor_(self_.input()->MakeCursor(mem)) {} - bool Pull(Frame &frame, ExecutionContext &context) override { - SCOPED_PROFILE_OP("ConstructNamedPath"); - - if (!input_cursor_->Pull(frame, context)) return false; - - auto symbol_it = self_.path_elements_.begin(); - DMG_ASSERT(symbol_it != self_.path_elements_.end(), "Named path must contain at least one node"); - - const auto &start_vertex = frame[*symbol_it++]; - auto *pull_memory = context.evaluation_context.memory; - // In an OPTIONAL MATCH everything could be Null. - if (start_vertex.IsNull()) { - frame[self_.path_symbol_] = TypedValue(pull_memory); - return true; - } - - DMG_ASSERT(start_vertex.IsVertex(), "First named path element must be a vertex"); - query::v2::Path path(start_vertex.ValueVertex(), pull_memory); - - // If the last path element symbol was for an edge list, then - // the next symbol is a vertex and it should not append to the path - // because - // expansion already did it. - bool last_was_edge_list = false; - - for (; symbol_it != self_.path_elements_.end(); symbol_it++) { - const auto &expansion = frame[*symbol_it]; - // We can have Null (OPTIONAL MATCH), a vertex, an edge, or an edge - // list (variable expand or BFS). - switch (expansion.type()) { - case TypedValue::Type::Null: - frame[self_.path_symbol_] = TypedValue(pull_memory); - return true; - case TypedValue::Type::Vertex: - if (!last_was_edge_list) path.Expand(expansion.ValueVertex()); - last_was_edge_list = false; - break; - case TypedValue::Type::Edge: - path.Expand(expansion.ValueEdge()); - break; - case TypedValue::Type::List: { - last_was_edge_list = true; - // We need to expand all edges in the list and intermediary - // vertices. - const auto &edges = expansion.ValueList(); - for (const auto &edge_value : edges) { - const auto &edge = edge_value.ValueEdge(); - const auto &from = edge.From(); - if (path.vertices().back() == from) - path.Expand(edge, edge.To()); - else - path.Expand(edge, from); - } - break; - } - default: - LOG_FATAL("Unsupported type in named path construction"); - - break; - } - } - - frame[self_.path_symbol_] = path; - return true; - } + bool Pull(Frame & /*frame*/, ExecutionContext & /*context*/) override { return false; } void Shutdown() override { input_cursor_->Shutdown(); } @@ -1914,121 +923,7 @@ std::vector<Symbol> Delete::ModifiedSymbols(const SymbolTable &table) const { re Delete::DeleteCursor::DeleteCursor(const Delete &self, utils::MemoryResource *mem) : self_(self), input_cursor_(self_.input_->MakeCursor(mem)) {} -bool Delete::DeleteCursor::Pull(Frame &frame, ExecutionContext &context) { - SCOPED_PROFILE_OP("Delete"); - - if (!input_cursor_->Pull(frame, context)) return false; - - // Delete should get the latest information, this way it is also possible - // to delete newly added nodes and edges. - ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor, - storage::v3::View::NEW); - auto *pull_memory = context.evaluation_context.memory; - // collect expressions results so edges can get deleted before vertices - // this is necessary because an edge that gets deleted could block vertex - // deletion - utils::pmr::vector<TypedValue> expression_results(pull_memory); - expression_results.reserve(self_.expressions_.size()); - for (Expression *expression : self_.expressions_) { - expression_results.emplace_back(expression->Accept(evaluator)); - } - - auto &dba = *context.db_accessor; - // delete edges first - for (TypedValue &expression_result : expression_results) { - if (MustAbort(context)) throw HintedAbortError(); - if (expression_result.type() == TypedValue::Type::Edge) { - auto maybe_value = dba.RemoveEdge(&expression_result.ValueEdge()); - if (maybe_value.HasError()) { - switch (maybe_value.GetError()) { - case storage::v3::Error::SERIALIZATION_ERROR: - throw TransactionSerializationException(); - case storage::v3::Error::DELETED_OBJECT: - case storage::v3::Error::VERTEX_HAS_EDGES: - case storage::v3::Error::PROPERTIES_DISABLED: - case storage::v3::Error::NONEXISTENT_OBJECT: - throw QueryRuntimeException("Unexpected error when deleting an edge."); - } - } - context.execution_stats[ExecutionStats::Key::DELETED_EDGES] += 1; - if (context.trigger_context_collector && maybe_value.GetValue()) { - context.trigger_context_collector->RegisterDeletedObject(*maybe_value.GetValue()); - } - } - } - - // delete vertices - for (TypedValue &expression_result : expression_results) { - if (MustAbort(context)) throw HintedAbortError(); - switch (expression_result.type()) { - case TypedValue::Type::Vertex: { - auto &va = expression_result.ValueVertex(); - if (self_.detach_) { - auto res = dba.DetachRemoveVertex(&va); - if (res.HasError()) { - switch (res.GetError()) { - case storage::v3::Error::SERIALIZATION_ERROR: - throw TransactionSerializationException(); - case storage::v3::Error::DELETED_OBJECT: - case storage::v3::Error::VERTEX_HAS_EDGES: - case storage::v3::Error::PROPERTIES_DISABLED: - case storage::v3::Error::NONEXISTENT_OBJECT: - throw QueryRuntimeException("Unexpected error when deleting a node."); - } - } - - context.execution_stats[ExecutionStats::Key::DELETED_NODES] += 1; - if (*res) { - context.execution_stats[ExecutionStats::Key::DELETED_EDGES] += static_cast<int64_t>((*res)->second.size()); - } - std::invoke([&] { - if (!context.trigger_context_collector || !*res) { - return; - } - - context.trigger_context_collector->RegisterDeletedObject((*res)->first); - if (!context.trigger_context_collector->ShouldRegisterDeletedObject<query::v2::EdgeAccessor>()) { - return; - } - for (const auto &edge : (*res)->second) { - context.trigger_context_collector->RegisterDeletedObject(edge); - } - }); - } else { - auto res = dba.RemoveVertex(&va); - if (res.HasError()) { - switch (res.GetError()) { - case storage::v3::Error::SERIALIZATION_ERROR: - throw TransactionSerializationException(); - case storage::v3::Error::VERTEX_HAS_EDGES: - throw RemoveAttachedVertexException(); - case storage::v3::Error::DELETED_OBJECT: - case storage::v3::Error::PROPERTIES_DISABLED: - case storage::v3::Error::NONEXISTENT_OBJECT: - throw QueryRuntimeException("Unexpected error when deleting a node."); - } - } - context.execution_stats[ExecutionStats::Key::DELETED_NODES] += 1; - if (context.trigger_context_collector && res.GetValue()) { - context.trigger_context_collector->RegisterDeletedObject(*res.GetValue()); - } - } - break; - } - - // skip Edges (already deleted) and Nulls (can occur in optional - // match) - case TypedValue::Type::Edge: - case TypedValue::Type::Null: - break; - // check we're not trying to delete anything except vertices and edges - default: - throw QueryRuntimeException("Only edges and vertices can be deleted."); - } - } - - return true; -} +bool Delete::DeleteCursor::Pull(Frame & /*frame*/, ExecutionContext & /*context*/) { return false; } void Delete::DeleteCursor::Shutdown() { input_cursor_->Shutdown(); } @@ -2053,54 +948,7 @@ std::vector<Symbol> SetProperty::ModifiedSymbols(const SymbolTable &table) const SetProperty::SetPropertyCursor::SetPropertyCursor(const SetProperty &self, utils::MemoryResource *mem) : self_(self), input_cursor_(self.input_->MakeCursor(mem)) {} -bool SetProperty::SetPropertyCursor::Pull(Frame &frame, ExecutionContext &context) { - SCOPED_PROFILE_OP("SetProperty"); - - if (!input_cursor_->Pull(frame, context)) return false; - - // Set, just like Create needs to see the latest changes. - ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor, - storage::v3::View::NEW); - TypedValue lhs = self_.lhs_->expression_->Accept(evaluator); - TypedValue rhs = self_.rhs_->Accept(evaluator); - - switch (lhs.type()) { - case TypedValue::Type::Vertex: { - auto old_value = storage::v3::PropertyToTypedValue<TypedValue>( - PropsSetChecked(&lhs.ValueVertex(), *context.db_accessor, self_.property_, rhs)); - context.execution_stats[ExecutionStats::Key::UPDATED_PROPERTIES] += 1; - if (context.trigger_context_collector) { - // rhs cannot be moved because it was created with the allocator that is only valid during current pull - context.trigger_context_collector->RegisterSetObjectProperty(lhs.ValueVertex(), self_.property_, - TypedValue{std::move(old_value)}, TypedValue{rhs}); - } - break; - } - case TypedValue::Type::Edge: { - auto old_value = storage::v3::PropertyToTypedValue<TypedValue>( - PropsSetChecked(&lhs.ValueEdge(), *context.db_accessor, self_.property_, rhs)); - context.execution_stats[ExecutionStats::Key::UPDATED_PROPERTIES] += 1; - if (context.trigger_context_collector) { - // rhs cannot be moved because it was created with the allocator that is only valid during current pull - context.trigger_context_collector->RegisterSetObjectProperty(lhs.ValueEdge(), self_.property_, - TypedValue{std::move(old_value)}, TypedValue{rhs}); - } - break; - } - case TypedValue::Type::Null: - // Skip setting properties on Null (can occur in optional match). - break; - case TypedValue::Type::Map: - // Semantically modifying a map makes sense, but it's not supported due - // to all the copying we do (when PropertyValue -> TypedValue and in - // ExpressionEvaluator). So even though we set a map property here, that - // is never visible to the user and it's not stored. - // TODO: fix above described bug - default: - throw QueryRuntimeException("Properties can only be set on edges and vertices."); - } - return true; -} +bool SetProperty::SetPropertyCursor::Pull(Frame & /*frame*/, ExecutionContext & /*context*/) { return false; } void SetProperty::SetPropertyCursor::Shutdown() { input_cursor_->Shutdown(); } @@ -2135,157 +983,34 @@ concept AccessorWithProperties = requires(T value, storage::v3::PropertyId prope {value.SetProperty(property_id, property_value)}; }; -/// Helper function that sets the given values on either a Vertex or an Edge. -/// -/// @tparam TRecordAccessor Either RecordAccessor<Vertex> or -/// RecordAccessor<Edge> -template <RecordAccessor TRecordAccessor> -void SetPropertiesOnRecord(TRecordAccessor *record, const TypedValue &rhs, SetProperties::Op op, - ExecutionContext *context) { - std::optional<std::map<storage::v3::PropertyId, storage::v3::PropertyValue>> old_values; - const bool should_register_change = - context->trigger_context_collector && - context->trigger_context_collector->ShouldRegisterObjectPropertyChange<TRecordAccessor>(); - if (op == SetProperties::Op::REPLACE) { - auto maybe_value = record->ClearProperties(); - if (maybe_value.HasError()) { - switch (maybe_value.GetError()) { - case storage::v3::Error::DELETED_OBJECT: - throw QueryRuntimeException("Trying to set properties on a deleted graph element."); - case storage::v3::Error::SERIALIZATION_ERROR: - throw TransactionSerializationException(); - case storage::v3::Error::PROPERTIES_DISABLED: - throw QueryRuntimeException("Can't set property because properties on edges are disabled."); - case storage::v3::Error::VERTEX_HAS_EDGES: - case storage::v3::Error::NONEXISTENT_OBJECT: - throw QueryRuntimeException("Unexpected error when setting properties."); - } - } - - if (should_register_change) { - old_values.emplace(std::move(*maybe_value)); - } - } - - auto get_props = [](const auto &record) { - auto maybe_props = record.Properties(storage::v3::View::NEW); - if (maybe_props.HasError()) { - switch (maybe_props.GetError()) { - case storage::v3::Error::DELETED_OBJECT: - throw QueryRuntimeException("Trying to get properties from a deleted object."); - case storage::v3::Error::NONEXISTENT_OBJECT: - throw query::v2::QueryRuntimeException("Trying to get properties from an object that doesn't exist."); - case storage::v3::Error::SERIALIZATION_ERROR: - case storage::v3::Error::VERTEX_HAS_EDGES: - case storage::v3::Error::PROPERTIES_DISABLED: - throw QueryRuntimeException("Unexpected error when getting properties."); - } - } - return *maybe_props; - }; - - auto register_set_property = [&](auto &&returned_old_value, auto key, auto &&new_value) { - auto old_value = storage::v3::PropertyToTypedValue<TypedValue>([&]() -> storage::v3::PropertyValue { - if (!old_values) { - return std::forward<decltype(returned_old_value)>(returned_old_value); - } - - if (auto it = old_values->find(key); it != old_values->end()) { - return std::move(it->second); - } - - return {}; - }()); - context->trigger_context_collector->RegisterSetObjectProperty( - *record, key, std::move(old_value), memgraph::storage::v3::PropertyToTypedValue<TypedValue>(new_value)); - }; - - auto set_props = [&, record](auto properties) { - for (auto &kv : properties) { - if constexpr (AccessorWithSetPropertyAndValidate<TRecordAccessor>) { - const auto maybe_error = record->SetPropertyAndValidate(kv.first, storage::v3::PropertyValue(kv.second)); - if (maybe_error.HasError()) { - std::visit(utils::Overloaded{[](const storage::v3::Error error) { HandleErrorOnPropertyUpdate(error); }, - [&context](const storage::v3::SchemaViolation &schema_violation) { - HandleSchemaViolation(schema_violation, *context->db_accessor); - }}, - maybe_error.GetError()); - } - if (should_register_change) { - register_set_property(std::move(*maybe_error), kv.first, std::move(kv.second)); - } - } else { - auto maybe_error = record->SetProperty(kv.first, kv.second); - if (maybe_error.HasError()) { - HandleErrorOnPropertyUpdate(maybe_error.GetError()); - } - if (should_register_change) { - register_set_property(std::move(*maybe_error), kv.first, std::move(kv.second)); - } - } - } - }; - - switch (rhs.type()) { - case TypedValue::Type::Edge: - set_props(get_props(rhs.ValueEdge())); - break; - case TypedValue::Type::Vertex: - set_props(get_props(rhs.ValueVertex())); - break; - case TypedValue::Type::Map: { - for (const auto &kv : rhs.ValueMap()) { - auto key = context->db_accessor->NameToProperty(kv.first); - auto old_value = PropsSetChecked(record, *context->db_accessor, key, kv.second); - if (should_register_change) { - register_set_property(std::move(old_value), key, storage::v3::TypedToPropertyValue(kv.second)); - } - } - break; - } - default: - throw QueryRuntimeException( - "Right-hand side in SET expression must be a node, an edge or a " - "map."); - } - - if (should_register_change && old_values) { - // register removed properties - for (auto &[property_id, property_value] : *old_values) { - context->trigger_context_collector->RegisterRemovedObjectProperty( - *record, property_id, storage::v3::PropertyToTypedValue<TypedValue>(property_value)); - } - } -} - } // namespace bool SetProperties::SetPropertiesCursor::Pull(Frame &frame, ExecutionContext &context) { SCOPED_PROFILE_OP("SetProperties"); - - if (!input_cursor_->Pull(frame, context)) return false; - - TypedValue &lhs = frame[self_.input_symbol_]; - - // Set, just like Create needs to see the latest changes. - ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor, - storage::v3::View::NEW); - TypedValue rhs = self_.rhs_->Accept(evaluator); - - switch (lhs.type()) { - case TypedValue::Type::Vertex: - SetPropertiesOnRecord(&lhs.ValueVertex(), rhs, self_.op_, &context); - break; - case TypedValue::Type::Edge: - SetPropertiesOnRecord(&lhs.ValueEdge(), rhs, self_.op_, &context); - break; - case TypedValue::Type::Null: - // Skip setting properties on Null (can occur in optional match). - break; - default: - throw QueryRuntimeException("Properties can only be set on edges and vertices."); - } - return true; + return false; + // if (!input_cursor_->Pull(frame, context)) return false; + // + // TypedValue &lhs = frame[self_.input_symbol_]; + // + // // Set, just like Create needs to see the latest changes. + // ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor, + // storage::v3::View::NEW); + // TypedValue rhs = self_.rhs_->Accept(evaluator); + // + // switch (lhs.type()) { + // case TypedValue::Type::Vertex: + // SetPropertiesOnRecord(&lhs.ValueVertex(), rhs, self_.op_, &context); + // break; + // case TypedValue::Type::Edge: + // SetPropertiesOnRecord(&lhs.ValueEdge(), rhs, self_.op_, &context); + // break; + // case TypedValue::Type::Null: + // // Skip setting properties on Null (can occur in optional match). + // break; + // default: + // throw QueryRuntimeException("Properties can only be set on edges and vertices."); + // } + // return true; } void SetProperties::SetPropertiesCursor::Shutdown() { input_cursor_->Shutdown(); } @@ -2313,44 +1038,44 @@ SetLabels::SetLabelsCursor::SetLabelsCursor(const SetLabels &self, utils::Memory bool SetLabels::SetLabelsCursor::Pull(Frame &frame, ExecutionContext &context) { SCOPED_PROFILE_OP("SetLabels"); - - if (!input_cursor_->Pull(frame, context)) return false; - - TypedValue &vertex_value = frame[self_.input_symbol_]; - // Skip setting labels on Null (can occur in optional match). - if (vertex_value.IsNull()) return true; - ExpectType(self_.input_symbol_, vertex_value, TypedValue::Type::Vertex); - - auto &dba = *context.db_accessor; - auto &vertex = vertex_value.ValueVertex(); - for (const auto label : self_.labels_) { - auto maybe_value = vertex.AddLabelAndValidate(label); - if (maybe_value.HasError()) { - std::visit(utils::Overloaded{[](const storage::v3::Error error) { - switch (error) { - case storage::v3::Error::SERIALIZATION_ERROR: - throw TransactionSerializationException(); - case storage::v3::Error::DELETED_OBJECT: - throw QueryRuntimeException("Trying to set a label on a deleted node."); - case storage::v3::Error::VERTEX_HAS_EDGES: - case storage::v3::Error::PROPERTIES_DISABLED: - case storage::v3::Error::NONEXISTENT_OBJECT: - throw QueryRuntimeException("Unexpected error when setting a label."); - } - }, - [&dba](const storage::v3::SchemaViolation schema_violation) { - HandleSchemaViolation(schema_violation, dba); - }}, - maybe_value.GetError()); - } - - context.execution_stats[ExecutionStats::Key::CREATED_LABELS]++; - if (context.trigger_context_collector && *maybe_value) { - context.trigger_context_collector->RegisterSetVertexLabel(vertex, label); - } - } - - return true; + return false; + // if (!input_cursor_->Pull(frame, context)) return false; + // + // TypedValue &vertex_value = frame[self_.input_symbol_]; + // // Skip setting labels on Null (can occur in optional match). + // if (vertex_value.IsNull()) return true; + // ExpectType(self_.input_symbol_, vertex_value, TypedValue::Type::Vertex); + // + // auto &dba = *context.db_accessor; + // auto &vertex = vertex_value.ValueVertex(); + // for (const auto label : self_.labels_) { + // auto maybe_value = vertex.AddLabelAndValidate(label); + // if (maybe_value.HasError()) { + // std::visit(utils::Overloaded{[](const storage::v3::Error error) { + // switch (error) { + // case storage::v3::Error::SERIALIZATION_ERROR: + // throw TransactionSerializationException(); + // case storage::v3::Error::DELETED_OBJECT: + // throw QueryRuntimeException("Trying to set a label on a deleted node."); + // case storage::v3::Error::VERTEX_HAS_EDGES: + // case storage::v3::Error::PROPERTIES_DISABLED: + // case storage::v3::Error::NONEXISTENT_OBJECT: + // throw QueryRuntimeException("Unexpected error when setting a label."); + // } + // }, + // [&dba](const storage::v3::SchemaViolation schema_violation) { + // HandleSchemaViolation(schema_violation, dba); + // }}, + // maybe_value.GetError()); + // } + // + // context.execution_stats[ExecutionStats::Key::CREATED_LABELS]++; + // if (context.trigger_context_collector && *maybe_value) { + // context.trigger_context_collector->RegisterSetVertexLabel(vertex, label); + // } + // } + // + // return true; } void SetLabels::SetLabelsCursor::Shutdown() { input_cursor_->Shutdown(); } @@ -2378,37 +1103,37 @@ RemoveProperty::RemovePropertyCursor::RemovePropertyCursor(const RemoveProperty bool RemoveProperty::RemovePropertyCursor::Pull(Frame &frame, ExecutionContext &context) { SCOPED_PROFILE_OP("RemoveProperty"); - - if (!input_cursor_->Pull(frame, context)) return false; - - // Remove, just like Delete needs to see the latest changes. - ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor, - storage::v3::View::NEW); - TypedValue lhs = self_.lhs_->expression_->Accept(evaluator); - - auto remove_prop = [property = self_.property_, &context](auto *record) { - auto old_value = PropsSetChecked(record, *context.db_accessor, property, TypedValue{}); - - if (context.trigger_context_collector) { - context.trigger_context_collector->RegisterRemovedObjectProperty( - *record, property, storage::v3::PropertyToTypedValue<TypedValue>(std::move(old_value))); - } - }; - - switch (lhs.type()) { - case TypedValue::Type::Vertex: - remove_prop(&lhs.ValueVertex()); - break; - case TypedValue::Type::Edge: - remove_prop(&lhs.ValueEdge()); - break; - case TypedValue::Type::Null: - // Skip removing properties on Null (can occur in optional match). - break; - default: - throw QueryRuntimeException("Properties can only be removed from vertices and edges."); - } - return true; + return false; + // if (!input_cursor_->Pull(frame, context)) return false; + // + // // Remove, just like Delete needs to see the latest changes. + // ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor, + // storage::v3::View::NEW); + // TypedValue lhs = self_.lhs_->expression_->Accept(evaluator); + // + // auto remove_prop = [property = self_.property_, &context](auto *record) { + // auto old_value = PropsSetChecked(record, *context.db_accessor, property, TypedValue{}); + // + // if (context.trigger_context_collector) { + // context.trigger_context_collector->RegisterRemovedObjectProperty( + // *record, property, storage::v3::PropertyToTypedValue<TypedValue>(std::move(old_value))); + // } + // }; + // + // switch (lhs.type()) { + // case TypedValue::Type::Vertex: + // remove_prop(&lhs.ValueVertex()); + // break; + // case TypedValue::Type::Edge: + // remove_prop(&lhs.ValueEdge()); + // break; + // case TypedValue::Type::Null: + // // Skip removing properties on Null (can occur in optional match). + // break; + // default: + // throw QueryRuntimeException("Properties can only be removed from vertices and edges."); + // } + // return true; } void RemoveProperty::RemovePropertyCursor::Shutdown() { input_cursor_->Shutdown(); } @@ -2436,43 +1161,45 @@ RemoveLabels::RemoveLabelsCursor::RemoveLabelsCursor(const RemoveLabels &self, u bool RemoveLabels::RemoveLabelsCursor::Pull(Frame &frame, ExecutionContext &context) { SCOPED_PROFILE_OP("RemoveLabels"); - - if (!input_cursor_->Pull(frame, context)) return false; - - TypedValue &vertex_value = frame[self_.input_symbol_]; - // Skip removing labels on Null (can occur in optional match). - if (vertex_value.IsNull()) return true; - ExpectType(self_.input_symbol_, vertex_value, TypedValue::Type::Vertex); - auto &vertex = vertex_value.ValueVertex(); - for (auto label : self_.labels_) { - auto maybe_value = vertex.RemoveLabelAndValidate(label); - if (maybe_value.HasError()) { - std::visit( - utils::Overloaded{[](const storage::v3::Error error) { - switch (error) { - case storage::v3::Error::SERIALIZATION_ERROR: - throw TransactionSerializationException(); - case storage::v3::Error::DELETED_OBJECT: - throw QueryRuntimeException("Trying to remove labels from a deleted node."); - case storage::v3::Error::VERTEX_HAS_EDGES: - case storage::v3::Error::PROPERTIES_DISABLED: - case storage::v3::Error::NONEXISTENT_OBJECT: - throw QueryRuntimeException("Unexpected error when removing labels from a node."); - } - }, - [&context](const storage::v3::SchemaViolation &schema_violation) { - HandleSchemaViolation(schema_violation, *context.db_accessor); - }}, - maybe_value.GetError()); - } - - context.execution_stats[ExecutionStats::Key::DELETED_LABELS] += 1; - if (context.trigger_context_collector && *maybe_value) { - context.trigger_context_collector->RegisterRemovedVertexLabel(vertex, label); - } - } - - return true; + return false; + // + // if (!input_cursor_->Pull(frame, context)) return false; + // + // TypedValue &vertex_value = frame[self_.input_symbol_]; + // // Skip removing labels on Null (can occur in optional match). + // if (vertex_value.IsNull()) return true; + // ExpectType(self_.input_symbol_, vertex_value, TypedValue::Type::Vertex); + // auto &vertex = vertex_value.ValueVertex(); + // for (auto label : self_.labels_) { + // auto maybe_value = vertex.RemoveLabelAndValidate(label); + // if (maybe_value.HasError()) { + // std::visit( + // utils::Overloaded{[](const storage::v3::Error error) { + // switch (error) { + // case storage::v3::Error::SERIALIZATION_ERROR: + // throw TransactionSerializationException(); + // case storage::v3::Error::DELETED_OBJECT: + // throw QueryRuntimeException("Trying to remove labels from a deleted node."); + // case storage::v3::Error::VERTEX_HAS_EDGES: + // case storage::v3::Error::PROPERTIES_DISABLED: + // case storage::v3::Error::NONEXISTENT_OBJECT: + // throw QueryRuntimeException("Unexpected error when removing labels from a + // node."); + // } + // }, + // [&context](const storage::v3::SchemaViolation &schema_violation) { + // HandleSchemaViolation(schema_violation, *context.db_accessor); + // }}, + // maybe_value.GetError()); + // } + // + // context.execution_stats[ExecutionStats::Key::DELETED_LABELS] += 1; + // if (context.trigger_context_collector && *maybe_value) { + // context.trigger_context_collector->RegisterRemovedVertexLabel(vertex, label); + // } + // } + // + // return true; } void RemoveLabels::RemoveLabelsCursor::Shutdown() { input_cursor_->Shutdown(); } @@ -3721,46 +2448,6 @@ std::unordered_map<std::string, int64_t> CallProcedure::GetAndResetCounters() { return ret; } -namespace { - -void CallCustomProcedure(const std::string_view fully_qualified_procedure_name, const mgp_proc &proc, - const std::vector<Expression *> &args, mgp_graph &graph, ExpressionEvaluator *evaluator, - utils::MemoryResource *memory, std::optional<size_t> memory_limit, mgp_result *result) { - static_assert(std::uses_allocator_v<mgp_value, utils::Allocator<mgp_value>>, - "Expected mgp_value to use custom allocator and makes STL " - "containers aware of that"); - // Build and type check procedure arguments. - mgp_list proc_args(memory); - std::vector<TypedValue> args_list; - args_list.reserve(args.size()); - for (auto *expression : args) { - args_list.emplace_back(expression->Accept(*evaluator)); - } - procedure::ConstructArguments(args_list, proc, fully_qualified_procedure_name, proc_args, graph); - if (memory_limit) { - SPDLOG_INFO("Running '{}' with memory limit of {}", fully_qualified_procedure_name, - utils::GetReadableSize(*memory_limit)); - utils::LimitedMemoryResource limited_mem(memory, *memory_limit); - mgp_memory proc_memory{&limited_mem}; - MG_ASSERT(result->signature == &proc.results); - // TODO: What about cross library boundary exceptions? OMG C++?! - proc.cb(&proc_args, &graph, result, &proc_memory); - size_t leaked_bytes = limited_mem.GetAllocatedBytes(); - if (leaked_bytes > 0U) { - spdlog::warn("Query procedure '{}' leaked {} *tracked* bytes", fully_qualified_procedure_name, leaked_bytes); - } - } else { - // TODO: Add a tracking MemoryResource without limits, so that we report - // memory leaks in procedure. - mgp_memory proc_memory{memory}; - MG_ASSERT(result->signature == &proc.results); - // TODO: What about cross library boundary exceptions? OMG C++?! - proc.cb(&proc_args, &graph, result, &proc_memory); - } -} - -} // namespace - class CallProcedureCursor : public Cursor { const CallProcedure *self_; UniqueCursorPtr input_cursor_; @@ -3779,90 +2466,7 @@ class CallProcedureCursor : public Cursor { MG_ASSERT(self_->result_fields_.size() == self_->result_symbols_.size(), "Incorrectly constructed CallProcedure"); } - bool Pull(Frame &frame, ExecutionContext &context) override { - SCOPED_PROFILE_OP("CallProcedure"); - - if (MustAbort(context)) throw HintedAbortError(); - - // We need to fetch new procedure results after pulling from input. - // TODO: Look into openCypher's distinction between procedures returning an - // empty result set vs procedures which return `void`. We currently don't - // have procedures registering what they return. - // This `while` loop will skip over empty results. - while (result_row_it_ == result_.rows.end()) { - if (!input_cursor_->Pull(frame, context)) return false; - result_.signature = nullptr; - result_.rows.clear(); - result_.error_msg.reset(); - // It might be a good idea to resolve the procedure name once, at the - // start. Unfortunately, this could deadlock if we tried to invoke a - // procedure from a module (read lock) and reload a module (write lock) - // inside the same execution thread. Also, our RWLock is setup so that - // it's not possible for a single thread to request multiple read locks. - // Builtin module registration in query/procedure/module.cpp depends on - // this locking scheme. - const auto &maybe_found = procedure::FindProcedure(procedure::gModuleRegistry, self_->procedure_name_, - context.evaluation_context.memory); - if (!maybe_found) { - throw QueryRuntimeException("There is no procedure named '{}'.", self_->procedure_name_); - } - const auto &[module, proc] = *maybe_found; - if (proc->info.is_write != self_->is_write_) { - auto get_proc_type_str = [](bool is_write) { return is_write ? "write" : "read"; }; - throw QueryRuntimeException("The procedure named '{}' was a {} procedure, but changed to be a {} procedure.", - self_->procedure_name_, get_proc_type_str(self_->is_write_), - get_proc_type_str(proc->info.is_write)); - } - const auto graph_view = proc->info.is_write ? storage::v3::View::NEW : storage::v3::View::OLD; - ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor, - graph_view); - - result_.signature = &proc->results; - // Use evaluation memory, as invoking a procedure is akin to a simple - // evaluation of an expression. - // TODO: This will probably need to be changed when we add support for - // generator like procedures which yield a new result on each invocation. - auto *memory = context.evaluation_context.memory; - auto memory_limit = expr::EvaluateMemoryLimit(&evaluator, self_->memory_limit_, self_->memory_scale_); - auto graph = mgp_graph::WritableGraph(*context.db_accessor, graph_view, context); - CallCustomProcedure(self_->procedure_name_, *proc, self_->arguments_, graph, &evaluator, memory, memory_limit, - &result_); - - // Reset result_.signature to nullptr, because outside of this scope we - // will no longer hold a lock on the `module`. If someone were to reload - // it, the pointer would be invalid. - result_signature_size_ = result_.signature->size(); - result_.signature = nullptr; - if (result_.error_msg) { - throw QueryRuntimeException("{}: {}", self_->procedure_name_, *result_.error_msg); - } - result_row_it_ = result_.rows.begin(); - } - - const auto &values = result_row_it_->values; - // Check that the row has all fields as required by the result signature. - // C API guarantees that it's impossible to set fields which are not part of - // the result record, but it does not gurantee that some may be missing. See - // `mgp_result_record_insert`. - if (values.size() != result_signature_size_) { - throw QueryRuntimeException( - "Procedure '{}' did not yield all fields as required by its " - "signature.", - self_->procedure_name_); - } - for (size_t i = 0; i < self_->result_fields_.size(); ++i) { - std::string_view field_name(self_->result_fields_[i]); - auto result_it = values.find(field_name); - if (result_it == values.end()) { - throw QueryRuntimeException("Procedure '{}' did not yield a record with '{}' field.", self_->procedure_name_, - field_name); - } - frame[self_->result_symbols_[i]] = result_it->second; - } - ++result_row_it_; - - return true; - } + bool Pull(Frame & /*frame*/, ExecutionContext & /*context*/) override { return false; } void Reset() override { result_.rows.clear(); @@ -4099,4 +2703,125 @@ bool Foreach::Accept(HierarchicalLogicalOperatorVisitor &visitor) { return visitor.PostVisit(*this); } +class DistributedScanAllCursor : public Cursor { + public: + explicit DistributedScanAllCursor(Symbol output_symbol, UniqueCursorPtr input_cursor, const char *op_name) + : output_symbol_(output_symbol), input_cursor_(std::move(input_cursor)), op_name_(op_name) {} + + using VertexAccessor = accessors::VertexAccessor; + + bool MakeRequest(msgs::ShardRequestManagerInterface &shard_manager) { + current_batch = shard_manager.Request(request_state_); + current_vertex_it = current_batch.begin(); + return !current_batch.empty(); + } + + bool Pull(Frame &frame, ExecutionContext &context) override { + SCOPED_PROFILE_OP(op_name_); + auto &shard_manager = *context.shard_request_manager; + if (MustAbort(context)) throw HintedAbortError(); + using State = msgs::ExecutionState<msgs::ScanVerticesRequest>; + + if (request_state_.state == State::INITIALIZING) { + if (!input_cursor_->Pull(frame, context)) return false; + } + + if (current_vertex_it == current_batch.end()) { + if (request_state_.state == State::COMPLETED || !MakeRequest(shard_manager)) { + ResetExecutionState(); + return Pull(frame, context); + } + } + + frame[output_symbol_] = TypedValue(std::move(*current_vertex_it)); + ++current_vertex_it; + return true; + } + + void Shutdown() override { input_cursor_->Shutdown(); } + + void ResetExecutionState() { + current_batch.clear(); + current_vertex_it = current_batch.end(); + request_state_ = msgs::ExecutionState<msgs::ScanVerticesRequest>{}; + } + + void Reset() override { + input_cursor_->Reset(); + ResetExecutionState(); + } + + private: + const Symbol output_symbol_; + const UniqueCursorPtr input_cursor_; + const char *op_name_; + std::vector<VertexAccessor> current_batch; + decltype(std::vector<VertexAccessor>().begin()) current_vertex_it; + msgs::ExecutionState<msgs::ScanVerticesRequest> request_state_; +}; + +class DistributedCreateNodeCursor : public Cursor { + public: + using InputOperator = std::shared_ptr<memgraph::query::v2::plan::LogicalOperator>; + DistributedCreateNodeCursor(const InputOperator &op, utils::MemoryResource *mem, + std::vector<NodeCreationInfo> nodes_info) + : input_cursor_(op->MakeCursor(mem)), nodes_info_(std::move(nodes_info)) {} + + bool Pull(Frame &frame, ExecutionContext &context) override { + SCOPED_PROFILE_OP("CreateNode"); + if (input_cursor_->Pull(frame, context)) { + auto &shard_manager = context.shard_request_manager; + shard_manager->Request(state_, NodeCreationInfoToRequest(context, frame)); + return true; + } + + return false; + } + + void Shutdown() override { input_cursor_->Shutdown(); } + + void Reset() override { state_ = {}; } + + std::vector<msgs::NewVertex> NodeCreationInfoToRequest(ExecutionContext &context, Frame &frame) const { + std::vector<msgs::NewVertex> requests; + for (const auto &node_info : nodes_info_) { + msgs::NewVertex rqst; + std::map<msgs::PropertyId, msgs::Value> properties; + ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, nullptr, + storage::v3::View::NEW); + if (const auto *node_info_properties = std::get_if<PropertiesMapList>(&node_info.properties)) { + for (const auto &[key, value_expression] : *node_info_properties) { + TypedValue val = value_expression->Accept(evaluator); + properties[key] = TypedValueToValue(val); + if (context.shard_request_manager->IsPrimaryKey(key)) { + rqst.primary_key.push_back(storage::v3::TypedValueToValue(val)); + } + } + } else { + auto property_map = evaluator.Visit(*std::get<ParameterLookup *>(node_info.properties)).ValueMap(); + for (const auto &[key, value] : property_map) { + auto key_str = std::string(key); + auto property_id = context.shard_request_manager->NameToProperty(key_str); + properties[property_id] = TypedValueToValue(value); + if (context.shard_request_manager->IsPrimaryKey(property_id)) { + rqst.primary_key.push_back(storage::v3::TypedValueToValue(value)); + } + } + } + + if (node_info.labels.empty()) { + throw QueryRuntimeException("Primary label must be defined!"); + } + // TODO(kostasrim) Copy non primary labels as well + rqst.label_ids.push_back(msgs::Label{node_info.labels[0]}); + requests.push_back(std::move(rqst)); + } + return requests; + } + + private: + const UniqueCursorPtr input_cursor_; + std::vector<NodeCreationInfo> nodes_info_; + msgs::ExecutionState<msgs::CreateVerticesRequest> state_; +}; } // namespace memgraph::query::v2::plan diff --git a/src/query/v2/plan/operator.lcp b/src/query/v2/plan/operator.lcp index 99deeabd4..154adf753 100644 --- a/src/query/v2/plan/operator.lcp +++ b/src/query/v2/plan/operator.lcp @@ -25,15 +25,20 @@ #include "query/v2/common.hpp" #include "query/v2/frontend/ast/ast.hpp" #include "expr/semantic/symbol.hpp" -#include "query/v2//bindings/typed_value.hpp" -#include "query/v2//bindings/frame.hpp" -#include "query/v2//bindings/symbol_table.hpp" +#include "query/v2/bindings/typed_value.hpp" +#include "query/v2/bindings/frame.hpp" +#include "query/v2/bindings/symbol_table.hpp" #include "storage/v3/id_types.hpp" #include "utils/bound.hpp" #include "utils/fnv.hpp" #include "utils/memory.hpp" #include "utils/visitor.hpp" #include "utils/logging.hpp" +#include "query/v2/accessors.hpp" + +using VertexAccessor = memgraph::query::v2::accessors::VertexAccessor; +using EdgeAccessor = memgraph::query::v2::accessors::EdgeAccessor; +using Path = memgraph::query::v2::accessors::Path; cpp<# (lcp:namespace memgraph) diff --git a/src/query/v2/plan/rule_based_planner.hpp b/src/query/v2/plan/rule_based_planner.hpp index 62bbe7689..884693c2f 100644 --- a/src/query/v2/plan/rule_based_planner.hpp +++ b/src/query/v2/plan/rule_based_planner.hpp @@ -272,6 +272,7 @@ class RuleBasedPlanner { PropertiesMapList vector_props; vector_props.reserve(node_properties->size()); for (const auto &kv : *node_properties) { + // TODO(kostasrim) GetProperty should be implemented in terms of ShardRequestManager NameToProperty vector_props.push_back({GetProperty(kv.first), kv.second}); } return std::move(vector_props); diff --git a/src/query/v2/requests.hpp b/src/query/v2/requests.hpp index 05dc6f7a9..87a8930d8 100644 --- a/src/query/v2/requests.hpp +++ b/src/query/v2/requests.hpp @@ -14,6 +14,7 @@ #include <chrono> #include <iostream> #include <map> +#include <memory> #include <optional> #include <unordered_map> #include <utility> @@ -33,32 +34,45 @@ struct Value; struct Label { LabelId id; + friend bool operator==(const Label &lhs, const Label &rhs) { return lhs.id == rhs.id; } }; // TODO(kostasrim) update this with CompoundKey, same for the rest of the file. using PrimaryKey = std::vector<Value>; using VertexId = std::pair<Label, PrimaryKey>; + +inline bool operator==(const VertexId &lhs, const VertexId &rhs) { + return (lhs.first == rhs.first) && (lhs.second == rhs.second); +} + using Gid = size_t; using PropertyId = memgraph::storage::v3::PropertyId; struct EdgeType { uint64_t id; + friend bool operator==(const EdgeType &lhs, const EdgeType &rhs) = default; }; struct EdgeId { Gid gid; }; +struct Vertex { + VertexId id; + std::vector<Label> labels; + friend bool operator==(const Vertex &lhs, const Vertex &rhs) { + return (lhs.id == rhs.id) && (lhs.labels == rhs.labels); + } +}; + struct Edge { VertexId src; VertexId dst; EdgeId id; EdgeType type; -}; - -struct Vertex { - VertexId id; - std::vector<Label> labels; + friend bool operator==(const Edge &lhs, const Edge &rhs) { + return (lhs.src == rhs.src) && (lhs.dst == rhs.dst) && (lhs.type == rhs.type); + } }; struct PathPart { @@ -81,6 +95,7 @@ struct Value { explicit Value(const double val) : type(Type::Double), double_v(val) {} explicit Value(const Vertex val) : type(Type::Vertex), vertex_v(val) {} + explicit Value(const Edge val) : type(Type::Edge), edge_v(val) {} explicit Value(const std::string &val) : type(Type::String) { new (&string_v) std::string(val); } explicit Value(const char *val) : type(Type::String) { new (&string_v) std::string(val); } @@ -300,6 +315,34 @@ struct Value { Edge edge_v; Path path_v; }; + + friend bool operator==(const Value &lhs, const Value &rhs) { + if (lhs.type != rhs.type) { + return false; + } + switch (lhs.type) { + case Value::Type::Null: + return true; + case Value::Type::Bool: + return lhs.bool_v == rhs.bool_v; + case Value::Type::Int64: + return lhs.int_v == rhs.int_v; + case Value::Type::Double: + return lhs.double_v == rhs.double_v; + case Value::Type::String: + return lhs.string_v == rhs.string_v; + case Value::Type::List: + return lhs.list_v == rhs.list_v; + case Value::Type::Map: + return lhs.map_v == rhs.map_v; + case Value::Type::Vertex: + return lhs.vertex_v == rhs.vertex_v; + case Value::Type::Edge: + return lhs.edge_v == rhs.edge_v; + case Value::Type::Path: + return true; + } + } }; struct ValuesMap { @@ -343,9 +386,9 @@ struct ScanVerticesRequest { }; struct ScanResultRow { - Value vertex; + Vertex vertex; // empty() is no properties returned - std::map<PropertyId, Value> props; + std::vector<std::pair<PropertyId, Value>> props; }; struct ScanVerticesResponse { @@ -374,6 +417,11 @@ struct GetPropertiesResponse { enum class EdgeDirection : uint8_t { OUT = 1, IN = 2, BOTH = 3 }; +struct VertexEdgeId { + VertexId vertex_id; + std::optional<EdgeId> next_id; +}; + struct ExpandOneRequest { Hlc transaction_id; std::vector<VertexId> src_vertices; @@ -437,7 +485,14 @@ struct NewVertex { std::vector<std::pair<PropertyId, Value>> properties; }; +struct NewVertexLabel { + std::string label; + PrimaryKey primary_key; + std::vector<std::pair<PropertyId, Value>> properties; +}; + struct CreateVerticesRequest { + std::string label; Hlc transaction_id; std::vector<NewVertex> new_vertices; }; diff --git a/src/query/v2/shard_request_manager.hpp b/src/query/v2/shard_request_manager.hpp new file mode 100644 index 000000000..2fb8f685f --- /dev/null +++ b/src/query/v2/shard_request_manager.hpp @@ -0,0 +1,405 @@ +// 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. + +#pragma once + +#include <chrono> +#include <deque> +#include <iostream> +#include <map> +#include <optional> +#include <random> +#include <set> +#include <stdexcept> +#include <thread> +#include <unordered_map> +#include <vector> + +#include "coordinator/coordinator_client.hpp" +#include "coordinator/coordinator_rsm.hpp" +#include "coordinator/shard_map.hpp" +#include "io/address.hpp" +#include "io/errors.hpp" +#include "io/rsm/raft.hpp" +#include "io/rsm/rsm_client.hpp" +#include "io/rsm/shard_rsm.hpp" +#include "io/simulator/simulator.hpp" +#include "io/simulator/simulator_transport.hpp" +#include "query/v2/accessors.hpp" +#include "query/v2/requests.hpp" +#include "storage/v3/id_types.hpp" +#include "storage/v3/value_conversions.hpp" +#include "utils/result.hpp" + +namespace memgraph::msgs { +template <typename TStorageClient> +class RsmStorageClientManager { + public: + using CompoundKey = memgraph::io::rsm::ShardRsmKey; + using Shard = memgraph::coordinator::Shard; + using LabelId = memgraph::storage::v3::LabelId; + RsmStorageClientManager() = default; + RsmStorageClientManager(const RsmStorageClientManager &) = delete; + RsmStorageClientManager(RsmStorageClientManager &&) = delete; + RsmStorageClientManager &operator=(const RsmStorageClientManager &) = delete; + RsmStorageClientManager &operator=(RsmStorageClientManager &&) = delete; + ~RsmStorageClientManager() = default; + + void AddClient(const LabelId label_id, Shard key, TStorageClient client) { + cli_cache_[label_id].insert({std::move(key), std::move(client)}); + } + + bool Exists(const LabelId label_id, const Shard &key) { return cli_cache_[label_id].contains(key); } + + void PurgeCache() { cli_cache_.clear(); } + + TStorageClient &GetClient(const LabelId label_id, const Shard &key) { return cli_cache_[label_id].find(key)->second; } + + private: + std::map<LabelId, std::map<Shard, TStorageClient>> cli_cache_; +}; + +template <typename TRequest> +struct ExecutionState { + using CompoundKey = memgraph::io::rsm::ShardRsmKey; + using Shard = memgraph::coordinator::Shard; + + enum State : int8_t { INITIALIZING, EXECUTING, COMPLETED }; + // label is optional because some operators can create/remove etc, vertices. These kind of requests contain the label + // on the request itself. + std::optional<std::string> label; + // CompoundKey is optional because some operators require to iterate over all the available keys + // of a shard. One example is ScanAll, where we only require the field label. + std::optional<CompoundKey> key; + // Transaction id to be filled by the ShardRequestManager implementation + memgraph::coordinator::Hlc transaction_id; + // Initialized by ShardRequestManager implementation. This vector is filled with the shards that + // the ShardRequestManager impl will send requests to. When a request to a shard exhausts it, meaning that + // it pulled all the requested data from the given Shard, it will be removed from the Vector. When the Vector becomes + // empty, it means that all of the requests have completed succefully. + std::vector<Shard> shard_cache; + // 1-1 mapping with `shard_cache`. + // A vector that tracks request metatdata for each shard (For example, next_id for a ScanAll on Shard A) + std::vector<TRequest> requests; + State state = INITIALIZING; +}; + +class ShardRequestManagerInterface { + public: + using VertexAccessor = memgraph::query::v2::accessors::VertexAccessor; + ShardRequestManagerInterface() = default; + ShardRequestManagerInterface(const ShardRequestManagerInterface &) = delete; + ShardRequestManagerInterface(ShardRequestManagerInterface &&) = delete; + ShardRequestManagerInterface &operator=(const ShardRequestManagerInterface &) = delete; + ShardRequestManagerInterface &&operator=(ShardRequestManagerInterface &&) = delete; + + virtual ~ShardRequestManagerInterface() = default; + + virtual void StartTransaction() = 0; + virtual std::vector<VertexAccessor> Request(ExecutionState<ScanVerticesRequest> &state) = 0; + virtual std::vector<CreateVerticesResponse> Request(ExecutionState<CreateVerticesRequest> &state, + std::vector<NewVertex> new_vertices) = 0; + virtual std::vector<ExpandOneResponse> Request(ExecutionState<ExpandOneRequest> &state) = 0; + virtual memgraph::storage::v3::PropertyId NameToProperty(const std::string &name) const = 0; + virtual memgraph::storage::v3::LabelId LabelNameToLabelId(const std::string &name) const = 0; + virtual bool IsPrimaryKey(PropertyId name) const = 0; +}; + +// TODO(kostasrim)rename this class template +template <typename TTransport> +class ShardRequestManager : public ShardRequestManagerInterface { + public: + using WriteRequests = CreateVerticesRequest; + using WriteResponses = CreateVerticesResponse; + using ReadRequests = std::variant<ScanVerticesRequest, ExpandOneRequest>; + using ReadResponses = std::variant<ScanVerticesResponse, ExpandOneResponse>; + using StorageClient = + memgraph::coordinator::RsmClient<TTransport, WriteRequests, WriteResponses, ReadRequests, ReadResponses>; + using CoordinatorClient = memgraph::coordinator::CoordinatorClient<TTransport>; + using Address = memgraph::io::Address; + using Shard = memgraph::coordinator::Shard; + using ShardMap = memgraph::coordinator::ShardMap; + using CompoundKey = memgraph::coordinator::PrimaryKey; + using VertexAccessor = memgraph::query::v2::accessors::VertexAccessor; + ShardRequestManager(CoordinatorClient coord, memgraph::io::Io<TTransport> &&io) + : coord_cli_(std::move(coord)), io_(std::move(io)) {} + + ShardRequestManager(const ShardRequestManager &) = delete; + ShardRequestManager(ShardRequestManager &&) = delete; + ShardRequestManager &operator=(const ShardRequestManager &) = delete; + ShardRequestManager &operator=(ShardRequestManager &&) = delete; + + ~ShardRequestManager() override {} + + void StartTransaction() override { + memgraph::coordinator::HlcRequest req{.last_shard_map_version = shards_map_.GetHlc()}; + auto write_res = coord_cli_.SendWriteRequest(req); + if (write_res.HasError()) { + throw std::runtime_error("HLC request failed"); + } + auto coordinator_write_response = write_res.GetValue(); + auto hlc_response = std::get<memgraph::coordinator::HlcResponse>(coordinator_write_response); + + // Transaction ID to be used later... + transaction_id_ = hlc_response.new_hlc; + + if (hlc_response.fresher_shard_map) { + shards_map_ = hlc_response.fresher_shard_map.value(); + } + } + + memgraph::storage::v3::PropertyId NameToProperty(const std::string &name) const override { + return *shards_map_.GetPropertyId(name); + } + + memgraph::storage::v3::LabelId LabelNameToLabelId(const std::string &name) const override { + return shards_map_.GetLabelId(name); + } + + bool IsPrimaryKey(const PropertyId name) const override { + return std::find_if(shards_map_.properties.begin(), shards_map_.properties.end(), + [name](auto &pr) { return pr.second == name; }) != shards_map_.properties.end(); + } + + // TODO(kostasrim) Simplify return result + std::vector<VertexAccessor> Request(ExecutionState<ScanVerticesRequest> &state) override { + MaybeInitializeExecutionState(state); + std::vector<ScanVerticesResponse> responses; + auto &shard_cache_ref = state.shard_cache; + size_t id = 0; + for (auto shard_it = shard_cache_ref.begin(); shard_it != shard_cache_ref.end(); ++id) { + auto &storage_client = GetStorageClientForShard( + *state.label, storage::conversions::ConvertPropertyVector(state.requests[id].start_id.second)); + // TODO(kostasrim) Currently requests return the result directly. Adjust this when the API works MgFuture + // instead. + auto read_response_result = storage_client.SendReadRequest(state.requests[id]); + // RETRY on timeouts? + // Sometimes this produces a timeout. Temporary solution is to use a while(true) as was done in shard_map test + if (read_response_result.HasError()) { + throw std::runtime_error("ScanAll request timedout"); + } + auto &response = std::get<ScanVerticesResponse>(read_response_result.GetValue()); + if (!response.success) { + throw std::runtime_error("ScanAll request did not succeed"); + } + if (!response.next_start_id) { + shard_it = shard_cache_ref.erase(shard_it); + } else { + state.requests[id].start_id.second = response.next_start_id->second; + ++shard_it; + } + responses.push_back(std::move(response)); + } + // We are done with this state + MaybeCompleteState(state); + // TODO(kostasrim) Before returning start prefetching the batch (this shall be done once we get MgFuture as return + // result of storage_client.SendReadRequest()). + return PostProcess(std::move(responses)); + } + + std::vector<CreateVerticesResponse> Request(ExecutionState<CreateVerticesRequest> &state, + std::vector<NewVertex> new_vertices) override { + MG_ASSERT(!new_vertices.empty()); + MaybeInitializeExecutionState(state, new_vertices); + std::vector<CreateVerticesResponse> responses; + auto &shard_cache_ref = state.shard_cache; + size_t id = 0; + for (auto shard_it = shard_cache_ref.begin(); shard_it != shard_cache_ref.end(); ++id) { + // This is fine because all new_vertices of each request end up on the same shard + const auto labels = state.requests[id].new_vertices[0].label_ids; + auto primary_key = state.requests[id].new_vertices[0].primary_key; + auto &storage_client = GetStorageClientForShard(*shard_it, labels[0].id); + auto write_response_result = storage_client.SendWriteRequest(state.requests[id]); + // RETRY on timeouts? + // Sometimes this produces a timeout. Temporary solution is to use a while(true) as was done in shard_map test + if (write_response_result.HasError()) { + throw std::runtime_error("CreateVertices request timedout"); + } + if (!write_response_result.GetValue().success) { + throw std::runtime_error("CreateVertices request did not succeed"); + } + responses.push_back(write_response_result.GetValue()); + shard_it = shard_cache_ref.erase(shard_it); + } + // We are done with this state + MaybeCompleteState(state); + // TODO(kostasrim) Before returning start prefetching the batch (this shall be done once we get MgFuture as return + // result of storage_client.SendReadRequest()). + return responses; + } + + std::vector<ExpandOneResponse> Request(ExecutionState<ExpandOneRequest> &state) override { + // TODO(kostasrim)Update to limit the batch size here + // Expansions of the destination must be handled by the caller. For example + // match (u:L1 { prop : 1 })-[:Friend]-(v:L1) + // For each vertex U, the ExpandOne will result in <U, Edges>. The destination vertex and its properties + // must be fetched again with an ExpandOne(Edges.dst) + MaybeInitializeExecutionState(state); + std::vector<ExpandOneResponse> responses; + auto &shard_cache_ref = state.shard_cache; + size_t id = 0; + // pending_requests on shards + for (auto shard_it = shard_cache_ref.begin(); shard_it != shard_cache_ref.end(); ++id) { + const Label primary_label = state.requests[id].src_vertices[0].first; + auto &storage_client = GetStorageClientForShard(*shard_it, primary_label.id); + auto read_response_result = storage_client.SendReadRequest(state.requests[id]); + // RETRY on timeouts? + // Sometimes this produces a timeout. Temporary solution is to use a while(true) as was done in shard_map + if (read_response_result.HasError()) { + throw std::runtime_error("ExpandOne request timedout"); + } + auto &response = std::get<ExpandOneResponse>(read_response_result.GetValue()); + responses.push_back(std::move(response)); + } + return responses; + } + + private: + std::vector<VertexAccessor> PostProcess(std::vector<ScanVerticesResponse> &&responses) const { + std::vector<VertexAccessor> accessors; + for (auto &response : responses) { + for (auto &result_row : response.results) { + accessors.emplace_back(VertexAccessor(std::move(result_row.vertex), std::move(result_row.props))); + } + } + return accessors; + } + + template <typename ExecutionState> + void ThrowIfStateCompleted(ExecutionState &state) const { + if (state.state == ExecutionState::COMPLETED) [[unlikely]] { + throw std::runtime_error("State is completed and must be reset"); + } + } + + template <typename ExecutionState> + void MaybeCompleteState(ExecutionState &state) const { + if (state.requests.empty()) { + state.state = ExecutionState::COMPLETED; + } + } + + template <typename ExecutionState> + bool ShallNotInitializeState(ExecutionState &state) const { + return state.state != ExecutionState::INITIALIZING; + } + + void MaybeInitializeExecutionState(ExecutionState<CreateVerticesRequest> &state, + std::vector<NewVertex> new_vertices) { + ThrowIfStateCompleted(state); + if (ShallNotInitializeState(state)) { + return; + } + state.transaction_id = transaction_id_; + + std::map<Shard, CreateVerticesRequest> per_shard_request_table; + + for (auto &new_vertex : new_vertices) { + auto shard = shards_map_.GetShardForKey(new_vertex.label_ids[0].id, + storage::conversions::ConvertPropertyVector(new_vertex.primary_key)); + if (!per_shard_request_table.contains(shard)) { + CreateVerticesRequest create_v_rqst{.transaction_id = transaction_id_}; + per_shard_request_table.insert(std::pair(shard, std::move(create_v_rqst))); + state.shard_cache.push_back(shard); + } + per_shard_request_table[shard].new_vertices.push_back(std::move(new_vertex)); + } + + for (auto &[shard, rqst] : per_shard_request_table) { + state.requests.push_back(std::move(rqst)); + } + state.state = ExecutionState<CreateVerticesRequest>::EXECUTING; + } + + void MaybeInitializeExecutionState(ExecutionState<ScanVerticesRequest> &state) { + ThrowIfStateCompleted(state); + if (ShallNotInitializeState(state)) { + return; + } + state.transaction_id = transaction_id_; + auto shards = shards_map_.GetShards(*state.label); + for (auto &[key, shard] : shards) { + state.shard_cache.push_back(std::move(shard)); + ScanVerticesRequest rqst; + rqst.transaction_id = transaction_id_; + rqst.start_id.second = storage::conversions::ConvertValueVector(key); + state.requests.push_back(std::move(rqst)); + } + state.state = ExecutionState<ScanVerticesRequest>::EXECUTING; + } + + void MaybeInitializeExecutionState(ExecutionState<ExpandOneRequest> &state) { + ThrowIfStateCompleted(state); + if (ShallNotInitializeState(state)) { + return; + } + state.transaction_id = transaction_id_; + + std::map<Shard, ExpandOneRequest> per_shard_request_table; + MG_ASSERT(state.requests.size() == 1); + auto top_level_rqst = std::move(*state.requests.begin()); + auto top_level_rqst_template = top_level_rqst; + top_level_rqst_template.src_vertices.clear(); + top_level_rqst_template.edge_types.clear(); + state.requests.clear(); + size_t id = 0; + for (const auto &vertex : top_level_rqst.src_vertices) { + auto shard = + shards_map_.GetShardForKey(vertex.first.id, storage::conversions::ConvertPropertyVector(vertex.second)); + if (!per_shard_request_table.contains(shard)) { + ExpandOneRequest expand_v_rqst = top_level_rqst_template; + per_shard_request_table.insert(std::pair(shard, std::move(expand_v_rqst))); + state.shard_cache.push_back(shard); + } + per_shard_request_table[shard].src_vertices.push_back(vertex); + per_shard_request_table[shard].edge_types.push_back(top_level_rqst.edge_types[id]); + ++id; + } + + for (auto &[shard, rqst] : per_shard_request_table) { + state.requests.push_back(std::move(rqst)); + } + state.state = ExecutionState<ExpandOneRequest>::EXECUTING; + } + + StorageClient &GetStorageClientForShard(Shard shard, LabelId label_id) { + if (!storage_cli_manager_.Exists(label_id, shard)) { + AddStorageClientToManager(shard, label_id); + } + return storage_cli_manager_.GetClient(label_id, shard); + } + + StorageClient &GetStorageClientForShard(const std::string &label, const CompoundKey &key) { + auto shard = shards_map_.GetShardForKey(label, key); + auto label_id = shards_map_.GetLabelId(label); + return GetStorageClientForShard(std::move(shard), label_id); + } + + void AddStorageClientToManager(Shard target_shard, const LabelId &label_id) { + MG_ASSERT(!target_shard.empty()); + auto leader_addr = target_shard.front(); + std::vector<Address> addresses; + addresses.reserve(target_shard.size()); + for (auto &address : target_shard) { + addresses.push_back(std::move(address.address)); + } + auto cli = StorageClient(io_, std::move(leader_addr.address), std::move(addresses)); + storage_cli_manager_.AddClient(label_id, target_shard, std::move(cli)); + } + + ShardMap shards_map_; + CoordinatorClient coord_cli_; + RsmStorageClientManager<StorageClient> storage_cli_manager_; + memgraph::io::Io<TTransport> io_; + memgraph::coordinator::Hlc transaction_id_; + // TODO(kostasrim) Add batch prefetching +}; +} // namespace memgraph::msgs diff --git a/src/storage/v3/conversions.hpp b/src/storage/v3/conversions.hpp index 7c5cdb700..4c1090164 100644 --- a/src/storage/v3/conversions.hpp +++ b/src/storage/v3/conversions.hpp @@ -10,6 +10,7 @@ // licenses/APL.txt. #include "expr/typed_value.hpp" +#include "query/v2/requests.hpp" #include "storage/v3/property_value.hpp" #include "utils/memory.hpp" @@ -165,4 +166,40 @@ storage::v3::PropertyValue TypedToPropertyValue(const TTypedValue &value) { } throw expr::TypedValueException("Unsupported conversion from TTypedValue to PropertyValue"); } + +template <typename TTypedValue> +msgs::Value TypedValueToValue(const TTypedValue &value) { + using Value = msgs::Value; + switch (value.type()) { + case TTypedValue::Type::Null: + return {}; + case TTypedValue::Type::Bool: + return Value(value.ValueBool()); + case TTypedValue::Type::Int: + return Value(value.ValueInt()); + case TTypedValue::Type::Double: + return Value(value.ValueDouble()); + case TTypedValue::Type::String: + return Value(std::string(value.ValueString())); + case TTypedValue::Type::List: { + const auto &src = value.ValueList(); + std::vector<msgs::Value> dst; + dst.reserve(src.size()); + std::transform(src.begin(), src.end(), std::back_inserter(dst), + [](const auto &val) { return TypedValueToValue(val); }); + return Value(std::move(dst)); + } + case TTypedValue::Type::Map: { + const auto &src = value.ValueMap(); + std::map<std::string, Value> dst; + for (const auto &elem : src) { + dst.insert({std::string(elem.first), TypedValueToValue(elem.second)}); + } + return Value(std::move(dst)); + } + default: + break; + } + throw expr::TypedValueException("Unsupported conversion from TTypedValue to PropertyValue"); +} } // namespace memgraph::storage::v3 diff --git a/src/storage/v3/shard_rsm.cpp b/src/storage/v3/shard_rsm.cpp index d2d1423dd..1122da874 100644 --- a/src/storage/v3/shard_rsm.cpp +++ b/src/storage/v3/shard_rsm.cpp @@ -14,6 +14,7 @@ #include "query/v2/requests.hpp" #include "storage/v3/shard_rsm.hpp" +#include "storage/v3/value_conversions.hpp" #include "storage/v3/vertex_accessor.hpp" using memgraph::msgs::Label; @@ -21,83 +22,12 @@ using memgraph::msgs::PropertyId; using memgraph::msgs::Value; using memgraph::msgs::VertexId; +using memgraph::storage::conversions::ConvertPropertyVector; +using memgraph::storage::conversions::ConvertValueVector; +using memgraph::storage::conversions::ToPropertyValue; +using memgraph::storage::conversions::ToValue; + namespace { -// TODO(gvolfing use come algorithm instead of explicit for loops) -memgraph::storage::v3::PropertyValue ToPropertyValue(Value &&value) { - using PV = memgraph::storage::v3::PropertyValue; - PV ret; - switch (value.type) { - case Value::Type::Null: - return PV{}; - case Value::Type::Bool: - return PV(value.bool_v); - case Value::Type::Int64: - return PV(static_cast<int64_t>(value.int_v)); - case Value::Type::Double: - return PV(value.double_v); - case Value::Type::String: - return PV(value.string_v); - case Value::Type::List: { - std::vector<PV> list; - for (auto &elem : value.list_v) { - list.emplace_back(ToPropertyValue(std::move(elem))); - } - return PV(list); - } - case Value::Type::Map: { - std::map<std::string, PV> map; - for (auto &[key, value] : value.map_v) { - map.emplace(std::make_pair(key, ToPropertyValue(std::move(value)))); - } - return PV(map); - } - // These are not PropertyValues - case Value::Type::Vertex: - case Value::Type::Edge: - case Value::Type::Path: - MG_ASSERT(false, "Not PropertyValue"); - } - return ret; -} - -Value ToValue(const memgraph::storage::v3::PropertyValue &pv) { - using memgraph::storage::v3::PropertyValue; - - switch (pv.type()) { - case PropertyValue::Type::Bool: - return Value(pv.ValueBool()); - case PropertyValue::Type::Double: - return Value(pv.ValueDouble()); - case PropertyValue::Type::Int: - return Value(pv.ValueInt()); - case PropertyValue::Type::List: { - std::vector<Value> list(pv.ValueList().size()); - for (const auto &elem : pv.ValueList()) { - list.emplace_back(ToValue(elem)); - } - - return Value(list); - } - case PropertyValue::Type::Map: { - std::map<std::string, Value> map; - for (const auto &[key, val] : pv.ValueMap()) { - // maybe use std::make_pair once the && issue is resolved. - map.emplace(key, ToValue(val)); - } - - return Value(map); - } - case PropertyValue::Type::Null: - return Value{}; - case PropertyValue::Type::String: - return Value(pv.ValueString()); - case PropertyValue::Type::TemporalData: { - // TBD -> we need to specify this in the messages, not a priority. - MG_ASSERT(false, "Temporal datatypes are not yet implemented on Value!"); - return Value{}; - } - } -} std::vector<std::pair<memgraph::storage::v3::PropertyId, memgraph::storage::v3::PropertyValue>> ConvertPropertyMap( std::vector<std::pair<PropertyId, Value>> &&properties) { @@ -111,23 +41,13 @@ std::vector<std::pair<memgraph::storage::v3::PropertyId, memgraph::storage::v3:: return ret; } -std::vector<memgraph::storage::v3::PropertyValue> ConvertPropertyVector(std::vector<Value> &&vec) { - std::vector<memgraph::storage::v3::PropertyValue> ret; - ret.reserve(vec.size()); +std::vector<std::pair<memgraph::storage::v3::PropertyId, Value>> FromMap( + const std::map<PropertyId, Value> &properties) { + std::vector<std::pair<memgraph::storage::v3::PropertyId, Value>> ret; + ret.reserve(properties.size()); - for (auto &elem : vec) { - ret.push_back(ToPropertyValue(std::move(elem))); - } - - return ret; -} - -std::vector<Value> ConvertValueVector(const std::vector<memgraph::storage::v3::PropertyValue> &vec) { - std::vector<Value> ret; - ret.reserve(vec.size()); - - for (const auto &elem : vec) { - ret.push_back(ToValue(elem)); + for (const auto &[key, value] : properties) { + ret.emplace_back(std::make_pair(key, value)); } return ret; @@ -338,10 +258,12 @@ msgs::ReadResponses ShardRsm::HandleRead(msgs::ScanVerticesRequest &&req) { bool did_reach_starting_point = false; uint64_t sample_counter = 0; + const auto start_ids = ConvertPropertyVector(std::move(req.start_id.second)); + for (auto it = vertex_iterable.begin(); it != vertex_iterable.end(); ++it) { const auto &vertex = *it; - if (ConvertPropertyVector(std::move(req.start_id.second)) == vertex.PrimaryKey(View(req.storage_view)).GetValue()) { + if (start_ids == vertex.PrimaryKey(View(req.storage_view)).GetValue()) { did_reach_starting_point = true; } @@ -358,8 +280,8 @@ msgs::ReadResponses ShardRsm::HandleRead(msgs::ScanVerticesRequest &&req) { continue; } - results.emplace_back( - msgs::ScanResultRow{.vertex = ConstructValueVertex(vertex, view), .props = found_props.value()}); + results.emplace_back(msgs::ScanResultRow{.vertex = ConstructValueVertex(vertex, view).vertex_v, + .props = FromMap(found_props.value())}); ++sample_counter; if (sample_counter == req.batch_limit) { diff --git a/src/storage/v3/value_conversions.hpp b/src/storage/v3/value_conversions.hpp new file mode 100644 index 000000000..4a50780aa --- /dev/null +++ b/src/storage/v3/value_conversions.hpp @@ -0,0 +1,131 @@ +// Copyright 2022 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. +#include "query/v2/requests.hpp" +#include "storage/v3/property_value.hpp" +#include "utils/logging.hpp" + +#include <map> +#include <string> +#include <utility> +#include <vector> + +#pragma once + +// TODO(kostasrim) Think about long term sustainability + +// This should not be put under v3 because ADL will mess that up. +namespace memgraph::storage::conversions { + +using memgraph::msgs::PropertyId; +using memgraph::msgs::Value; +using memgraph::msgs::VertexId; + +// TODO(gvolfing use come algorithm instead of explicit for loops) +inline memgraph::storage::v3::PropertyValue ToPropertyValue(Value value) { + using PV = memgraph::storage::v3::PropertyValue; + PV ret; + switch (value.type) { + case Value::Type::Null: + return PV{}; + case Value::Type::Bool: + return PV(value.bool_v); + case Value::Type::Int64: + return PV(static_cast<int64_t>(value.int_v)); + case Value::Type::Double: + return PV(value.double_v); + case Value::Type::String: + return PV(value.string_v); + case Value::Type::List: { + std::vector<PV> list; + for (auto &elem : value.list_v) { + list.emplace_back(ToPropertyValue(std::move(elem))); + } + return PV(list); + } + case Value::Type::Map: { + std::map<std::string, PV> map; + for (auto &[key, value] : value.map_v) { + map.emplace(std::make_pair(key, ToPropertyValue(std::move(value)))); + } + return PV(map); + } + // These are not PropertyValues + case Value::Type::Vertex: + case Value::Type::Edge: + case Value::Type::Path: + MG_ASSERT(false, "Not PropertyValue"); + } + return ret; +} + +inline Value ToValue(const memgraph::storage::v3::PropertyValue &pv) { + using memgraph::storage::v3::PropertyValue; + + switch (pv.type()) { + case PropertyValue::Type::Bool: + return Value(pv.ValueBool()); + case PropertyValue::Type::Double: + return Value(pv.ValueDouble()); + case PropertyValue::Type::Int: + return Value(pv.ValueInt()); + case PropertyValue::Type::List: { + std::vector<Value> list; + list.reserve(pv.ValueList().size()); + for (const auto &elem : pv.ValueList()) { + list.emplace_back(ToValue(elem)); + } + + return Value(list); + } + case PropertyValue::Type::Map: { + std::map<std::string, Value> map; + for (const auto &[key, val] : pv.ValueMap()) { + // maybe use std::make_pair once the && issue is resolved. + map.emplace(key, ToValue(val)); + } + + return Value(map); + } + case PropertyValue::Type::Null: + return Value{}; + case PropertyValue::Type::String: + return Value(pv.ValueString()); + case PropertyValue::Type::TemporalData: { + // TBD -> we need to specify this in the messages, not a priority. + MG_ASSERT(false, "Temporal datatypes are not yet implemented on Value!"); + return Value{}; + } + } +} + +inline std::vector<memgraph::storage::v3::PropertyValue> ConvertPropertyVector(std::vector<Value> vec) { + std::vector<memgraph::storage::v3::PropertyValue> ret; + ret.reserve(vec.size()); + + for (auto &elem : vec) { + ret.push_back(ToPropertyValue(std::move(elem))); + } + + return ret; +} + +inline std::vector<Value> ConvertValueVector(const std::vector<memgraph::storage::v3::PropertyValue> &vec) { + std::vector<Value> ret; + ret.reserve(vec.size()); + + for (const auto &elem : vec) { + ret.push_back(ToValue(elem)); + } + + return ret; +} + +} // namespace memgraph::storage::conversions diff --git a/tests/simulation/CMakeLists.txt b/tests/simulation/CMakeLists.txt index 8812d8130..3e8e9879d 100644 --- a/tests/simulation/CMakeLists.txt +++ b/tests/simulation/CMakeLists.txt @@ -16,15 +16,21 @@ function(add_simulation_test test_cpp) # used to help create two targets of the same name even though CMake # requires unique logical target names set_target_properties(${target_name} PROPERTIES OUTPUT_NAME ${exec_name}) - target_link_libraries(${target_name} mg-storage-v3 mg-communication gtest gmock mg-utils mg-io mg-io-simulator mg-coordinator Boost::headers) + + # sanitize + target_compile_options(${target_name} PRIVATE -fsanitize=${san}) + target_link_options(${target_name} PRIVATE -fsanitize=${san}) + + target_link_libraries(${target_name} mg-storage-v3 mg-communication gtest gmock mg-utils mg-io mg-io-simulator mg-coordinator Boost::headers mg-query-v2) # register test add_test(${target_name} ${exec_name}) add_dependencies(memgraph__simulation ${target_name}) endfunction(add_simulation_test) -add_simulation_test(basic_request.cpp) -add_simulation_test(raft.cpp) -add_simulation_test(trial_query_storage/query_storage_test.cpp) -add_simulation_test(sharded_map.cpp) +add_simulation_test(basic_request.cpp address) +add_simulation_test(raft.cpp address) +add_simulation_test(trial_query_storage/query_storage_test.cpp address) +add_simulation_test(sharded_map.cpp address) +add_simulation_test(shard_request_manager.cpp address) add_simulation_test(shard_rsm.cpp) diff --git a/tests/simulation/common.hpp b/tests/simulation/common.hpp new file mode 100644 index 000000000..9dd21536d --- /dev/null +++ b/tests/simulation/common.hpp @@ -0,0 +1,126 @@ +// 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. + +#pragma once + +/// The ShardRsm is a simple in-memory raft-backed kv store that can be used for simple testing +/// and implementation of some query engine logic before storage engines are fully implemented. +/// +/// To implement multiple read and write commands, change the StorageRead* and StorageWrite* requests +/// and responses to a std::variant of the different options, and route them to specific handlers in +/// the ShardRsm's Read and Apply methods. Remember that Read is called immediately when the Raft +/// leader receives the request, and does not replicate anything over Raft. Apply is called only +/// AFTER the StorageWriteRequest is replicated to a majority of Raft peers, and the result of calling +/// ShardRsm::Apply(StorageWriteRequest) is returned to the client that submitted the request. + +#include <algorithm> +#include <deque> +#include <iostream> +#include <map> +#include <optional> +#include <set> +#include <thread> +#include <vector> + +#include <iostream> +#include "coordinator/hybrid_logical_clock.hpp" +#include "io/address.hpp" +#include "io/rsm/raft.hpp" +#include "io/rsm/shard_rsm.hpp" +#include "io/simulator/simulator.hpp" +#include "io/simulator/simulator_transport.hpp" +#include "query/v2/requests.hpp" +#include "storage/v3/id_types.hpp" +#include "storage/v3/property_value.hpp" +#include "storage/v3/value_conversions.hpp" +#include "utils/logging.hpp" + +using memgraph::coordinator::Hlc; +using memgraph::io::rsm::StorageWriteRequest; +using memgraph::io::rsm::StorageWriteResponse; +using memgraph::io::simulator::Simulator; +using memgraph::io::simulator::SimulatorConfig; +using memgraph::io::simulator::SimulatorStats; +using memgraph::io::simulator::SimulatorTransport; +using memgraph::msgs::CreateVerticesRequest; +using memgraph::msgs::CreateVerticesResponse; +using memgraph::msgs::ExpandOneRequest; +using memgraph::msgs::ExpandOneResponse; +using memgraph::msgs::ListedValues; +using memgraph::msgs::ScanVerticesRequest; +using memgraph::msgs::ScanVerticesResponse; +using memgraph::msgs::Value; +using memgraph::msgs::VertexId; +using memgraph::storage::v3::LabelId; +using memgraph::storage::v3::PropertyValue; + +using ShardRsmKey = std::vector<memgraph::storage::v3::PropertyValue>; + +class MockedShardRsm { + std::map<ShardRsmKey, int> state_; + ShardRsmKey minimum_key_; + std::optional<ShardRsmKey> maximum_key_{std::nullopt}; + Hlc shard_map_version_; + + // The key is not located in this shard + bool IsKeyInRange(const ShardRsmKey &key) { + if (maximum_key_) [[likely]] { + return (key >= minimum_key_ && key <= maximum_key_); + } + return key >= minimum_key_; + } + + public: + // ExpandOneResponse Read(ExpandOneRequest rqst); + // GetPropertiesResponse Read(GetPropertiesRequest rqst); + ScanVerticesResponse ReadImpl(ScanVerticesRequest rqst) { + ScanVerticesResponse ret; + auto as_prop_val = memgraph::storage::conversions::ConvertPropertyVector(rqst.start_id.second); + if (!IsKeyInRange(as_prop_val)) { + ret.success = false; + } else if (as_prop_val == ShardRsmKey{PropertyValue(0), PropertyValue(0)}) { + Value val(int64_t(0)); + ret.next_start_id = std::make_optional<VertexId>(); + ret.next_start_id->second = + memgraph::storage::conversions::ConvertValueVector(ShardRsmKey{PropertyValue(1), PropertyValue(0)}); + memgraph::msgs::ScanResultRow result; + result.props.push_back(std::make_pair(memgraph::msgs::PropertyId::FromUint(0), val)); + ret.results.push_back(std::move(result)); + ret.success = true; + } else if (as_prop_val == ShardRsmKey{PropertyValue(1), PropertyValue(0)}) { + memgraph::msgs::ScanResultRow result; + Value val(int64_t(1)); + result.props.push_back(std::make_pair(memgraph::msgs::PropertyId::FromUint(0), val)); + ret.results.push_back(std::move(result)); + ret.success = true; + } else if (as_prop_val == ShardRsmKey{PropertyValue(12), PropertyValue(13)}) { + memgraph::msgs::ScanResultRow result; + Value val(int64_t(444)); + result.props.push_back(std::make_pair(memgraph::msgs::PropertyId::FromUint(0), val)); + ret.results.push_back(std::move(result)); + ret.success = true; + } else { + ret.success = false; + } + return ret; + } + + ExpandOneResponse ReadImpl(ExpandOneRequest rqst) { return {}; } + using ReadRequests = std::variant<ScanVerticesRequest, ExpandOneRequest>; + using ReadResponses = std::variant<ScanVerticesResponse, ExpandOneResponse>; + + ReadResponses Read(ReadRequests read_requests) { + return {std::visit([this](auto &&request) { return ReadResponses{ReadImpl(std::move(request))}; }, + std::move(read_requests))}; + } + + CreateVerticesResponse Apply(CreateVerticesRequest request) { return CreateVerticesResponse{.success = true}; } +}; diff --git a/tests/simulation/shard_request_manager.cpp b/tests/simulation/shard_request_manager.cpp new file mode 100644 index 000000000..d1882fe60 --- /dev/null +++ b/tests/simulation/shard_request_manager.cpp @@ -0,0 +1,320 @@ +// Copyright 2022 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include <chrono> +#include <deque> +#include <iostream> +#include <map> +#include <optional> +#include <set> +#include <thread> +#include <vector> + +#include "common.hpp" +#include "common/types.hpp" +#include "coordinator/coordinator_client.hpp" +#include "coordinator/coordinator_rsm.hpp" +#include "io/address.hpp" +#include "io/errors.hpp" +#include "io/rsm/raft.hpp" +#include "io/rsm/rsm_client.hpp" +#include "io/rsm/shard_rsm.hpp" +#include "io/simulator/simulator.hpp" +#include "io/simulator/simulator_transport.hpp" +#include "query/v2/accessors.hpp" +#include "query/v2/conversions.hpp" +#include "query/v2/requests.hpp" +#include "query/v2/shard_request_manager.hpp" +#include "storage/v3/property_value.hpp" +#include "utils/result.hpp" + +using memgraph::coordinator::AddressAndStatus; +using CompoundKey = memgraph::coordinator::PrimaryKey; +using memgraph::coordinator::Coordinator; +using memgraph::coordinator::CoordinatorClient; +using memgraph::coordinator::CoordinatorRsm; +using memgraph::coordinator::HlcRequest; +using memgraph::coordinator::HlcResponse; +using memgraph::coordinator::Shard; +using memgraph::coordinator::ShardMap; +using memgraph::coordinator::Shards; +using memgraph::coordinator::Status; +using memgraph::io::Address; +using memgraph::io::Io; +using memgraph::io::ResponseEnvelope; +using memgraph::io::ResponseFuture; +using memgraph::io::Time; +using memgraph::io::TimedOut; +using memgraph::io::rsm::Raft; +using memgraph::io::rsm::ReadRequest; +using memgraph::io::rsm::ReadResponse; +using memgraph::io::rsm::StorageReadRequest; +using memgraph::io::rsm::StorageReadResponse; +using memgraph::io::rsm::StorageWriteRequest; +using memgraph::io::rsm::StorageWriteResponse; +using memgraph::io::rsm::WriteRequest; +using memgraph::io::rsm::WriteResponse; +using memgraph::io::simulator::Simulator; +using memgraph::io::simulator::SimulatorConfig; +using memgraph::io::simulator::SimulatorStats; +using memgraph::io::simulator::SimulatorTransport; +using memgraph::msgs::CreateVerticesRequest; +using memgraph::msgs::CreateVerticesResponse; +using memgraph::msgs::ListedValues; +using memgraph::msgs::NewVertexLabel; +using memgraph::msgs::ScanVerticesRequest; +using memgraph::msgs::ScanVerticesResponse; +using memgraph::storage::v3::LabelId; +using memgraph::storage::v3::SchemaProperty; +using memgraph::utils::BasicResult; + +namespace { + +ShardMap CreateDummyShardmap(memgraph::coordinator::Address a_io_1, memgraph::coordinator::Address a_io_2, + memgraph::coordinator::Address a_io_3, memgraph::coordinator::Address b_io_1, + memgraph::coordinator::Address b_io_2, memgraph::coordinator::Address b_io_3) { + static const std::string label_name = std::string("test_label"); + ShardMap sm; + + // register new properties + const std::vector<std::string> property_names = {"property_1", "property_2"}; + const auto properties = sm.AllocatePropertyIds(property_names); + const auto property_id_1 = properties.at("property_1"); + const auto property_id_2 = properties.at("property_2"); + const auto type_1 = memgraph::common::SchemaType::INT; + const auto type_2 = memgraph::common::SchemaType::INT; + + // register new label space + std::vector<SchemaProperty> schema = { + SchemaProperty{.property_id = property_id_1, .type = type_1}, + SchemaProperty{.property_id = property_id_2, .type = type_2}, + }; + + auto label_success = sm.InitializeNewLabel(label_name, schema, 1, sm.shard_map_version); + MG_ASSERT(label_success); + + const LabelId label_id = sm.labels.at(label_name); + auto &label_space = sm.label_spaces.at(label_id); + Shards &shards_for_label = label_space.shards; + + // add first shard at [0, 0] + AddressAndStatus aas1_1{.address = a_io_1, .status = Status::CONSENSUS_PARTICIPANT}; + AddressAndStatus aas1_2{.address = a_io_2, .status = Status::CONSENSUS_PARTICIPANT}; + AddressAndStatus aas1_3{.address = a_io_3, .status = Status::CONSENSUS_PARTICIPANT}; + + Shard shard1 = {aas1_1, aas1_2, aas1_3}; + + auto key1 = memgraph::storage::v3::PropertyValue(0); + auto key2 = memgraph::storage::v3::PropertyValue(0); + CompoundKey compound_key_1 = {key1, key2}; + shards_for_label[compound_key_1] = shard1; + + // add second shard at [12, 13] + AddressAndStatus aas2_1{.address = b_io_1, .status = Status::CONSENSUS_PARTICIPANT}; + AddressAndStatus aas2_2{.address = b_io_2, .status = Status::CONSENSUS_PARTICIPANT}; + AddressAndStatus aas2_3{.address = b_io_3, .status = Status::CONSENSUS_PARTICIPANT}; + + Shard shard2 = {aas2_1, aas2_2, aas2_3}; + + auto key3 = memgraph::storage::v3::PropertyValue(12); + auto key4 = memgraph::storage::v3::PropertyValue(13); + CompoundKey compound_key_2 = {key3, key4}; + shards_for_label[compound_key_2] = shard2; + + return sm; +} + +} // namespace + +using WriteRequests = CreateVerticesRequest; +using WriteResponses = CreateVerticesResponse; +using ReadRequests = std::variant<ScanVerticesRequest, ExpandOneRequest>; +using ReadResponses = std::variant<ScanVerticesResponse, ExpandOneResponse>; + +using ConcreteCoordinatorRsm = CoordinatorRsm<SimulatorTransport>; +using ConcreteStorageRsm = + Raft<SimulatorTransport, MockedShardRsm, WriteRequests, WriteResponses, ReadRequests, ReadResponses>; + +template <typename IoImpl> +void RunStorageRaft(Raft<IoImpl, MockedShardRsm, WriteRequests, WriteResponses, ReadRequests, ReadResponses> server) { + server.Run(); +} + +template <typename ShardRequestManager> +void TestScanAll(ShardRequestManager &io) { + memgraph::msgs::ExecutionState<ScanVerticesRequest> state{.label = "test_label"}; + + auto result = io.Request(state); + MG_ASSERT(result.size() == 2); + { + auto prop = result[0].GetProperty(memgraph::msgs::PropertyId::FromUint(0)); + MG_ASSERT(prop.int_v == 0); + prop = result[1].GetProperty(memgraph::msgs::PropertyId::FromUint(0)); + MG_ASSERT(prop.int_v == 444); + } + + result = io.Request(state); + { + MG_ASSERT(result.size() == 1); + auto prop = result[0].GetProperty(memgraph::msgs::PropertyId::FromUint(0)); + MG_ASSERT(prop.int_v == 1); + } + + // Exhaust it, request should be empty + result = io.Request(state); + MG_ASSERT(result.size() == 0); +} + +template <typename ShardRequestManager> +void TestCreateVertices(ShardRequestManager &io) { + using PropVal = memgraph::msgs::Value; + memgraph::msgs::ExecutionState<CreateVerticesRequest> state; + std::vector<memgraph::msgs::NewVertex> new_vertices; + auto label_id = io.LabelNameToLabelId("test_label"); + memgraph::msgs::NewVertex a1{.primary_key = {PropVal(int64_t(1)), PropVal(int64_t(0))}}; + a1.label_ids.push_back({label_id}); + memgraph::msgs::NewVertex a2{.primary_key = {PropVal(int64_t(13)), PropVal(int64_t(13))}}; + a2.label_ids.push_back({label_id}); + new_vertices.push_back(std::move(a1)); + new_vertices.push_back(std::move(a2)); + + auto result = io.Request(state, std::move(new_vertices)); + MG_ASSERT(result.size() == 2); +} + +template <typename ShardRequestManager> +void TestExpand(ShardRequestManager &io) {} + +template <typename ShardRequestManager> +void TestAggregate(ShardRequestManager &io) {} + +int main() { + SimulatorConfig config{ + .drop_percent = 0, + .perform_timeouts = false, + .scramble_messages = false, + .rng_seed = 0, + .start_time = Time::min() + std::chrono::microseconds{256 * 1024}, + .abort_time = Time::min() + std::chrono::microseconds{2 * 8 * 1024 * 1024}, + }; + + auto simulator = Simulator(config); + const auto one_second = std::chrono::seconds(1); + + Io<SimulatorTransport> cli_io = simulator.RegisterNew(); + cli_io.SetDefaultTimeout(one_second); + + // Register + Io<SimulatorTransport> a_io_1 = simulator.RegisterNew(); + a_io_1.SetDefaultTimeout(one_second); + Io<SimulatorTransport> a_io_2 = simulator.RegisterNew(); + a_io_2.SetDefaultTimeout(one_second); + Io<SimulatorTransport> a_io_3 = simulator.RegisterNew(); + a_io_3.SetDefaultTimeout(one_second); + + Io<SimulatorTransport> b_io_1 = simulator.RegisterNew(); + b_io_1.SetDefaultTimeout(one_second); + Io<SimulatorTransport> b_io_2 = simulator.RegisterNew(); + b_io_2.SetDefaultTimeout(one_second); + Io<SimulatorTransport> b_io_3 = simulator.RegisterNew(); + b_io_3.SetDefaultTimeout(one_second); + + // Preconfigure coordinator with kv shard 'A' and 'B' + auto sm1 = CreateDummyShardmap(a_io_1.GetAddress(), a_io_2.GetAddress(), a_io_3.GetAddress(), b_io_1.GetAddress(), + b_io_2.GetAddress(), b_io_3.GetAddress()); + auto sm2 = CreateDummyShardmap(a_io_1.GetAddress(), a_io_2.GetAddress(), a_io_3.GetAddress(), b_io_1.GetAddress(), + b_io_2.GetAddress(), b_io_3.GetAddress()); + auto sm3 = CreateDummyShardmap(a_io_1.GetAddress(), a_io_2.GetAddress(), a_io_3.GetAddress(), b_io_1.GetAddress(), + b_io_2.GetAddress(), b_io_3.GetAddress()); + + // Spin up shard A + std::vector<Address> a_addrs = {a_io_1.GetAddress(), a_io_2.GetAddress(), a_io_3.GetAddress()}; + + std::vector<Address> a_1_peers = {a_addrs[1], a_addrs[2]}; + std::vector<Address> a_2_peers = {a_addrs[0], a_addrs[2]}; + std::vector<Address> a_3_peers = {a_addrs[0], a_addrs[1]}; + + ConcreteStorageRsm a_1{std::move(a_io_1), a_1_peers, MockedShardRsm{}}; + ConcreteStorageRsm a_2{std::move(a_io_2), a_2_peers, MockedShardRsm{}}; + ConcreteStorageRsm a_3{std::move(a_io_3), a_3_peers, MockedShardRsm{}}; + + auto a_thread_1 = std::jthread(RunStorageRaft<SimulatorTransport>, std::move(a_1)); + simulator.IncrementServerCountAndWaitForQuiescentState(a_addrs[0]); + + auto a_thread_2 = std::jthread(RunStorageRaft<SimulatorTransport>, std::move(a_2)); + simulator.IncrementServerCountAndWaitForQuiescentState(a_addrs[1]); + + auto a_thread_3 = std::jthread(RunStorageRaft<SimulatorTransport>, std::move(a_3)); + simulator.IncrementServerCountAndWaitForQuiescentState(a_addrs[2]); + + // Spin up shard B + std::vector<Address> b_addrs = {b_io_1.GetAddress(), b_io_2.GetAddress(), b_io_3.GetAddress()}; + + std::vector<Address> b_1_peers = {b_addrs[1], b_addrs[2]}; + std::vector<Address> b_2_peers = {b_addrs[0], b_addrs[2]}; + std::vector<Address> b_3_peers = {b_addrs[0], b_addrs[1]}; + + ConcreteStorageRsm b_1{std::move(b_io_1), b_1_peers, MockedShardRsm{}}; + ConcreteStorageRsm b_2{std::move(b_io_2), b_2_peers, MockedShardRsm{}}; + ConcreteStorageRsm b_3{std::move(b_io_3), b_3_peers, MockedShardRsm{}}; + + auto b_thread_1 = std::jthread(RunStorageRaft<SimulatorTransport>, std::move(b_1)); + simulator.IncrementServerCountAndWaitForQuiescentState(b_addrs[0]); + + auto b_thread_2 = std::jthread(RunStorageRaft<SimulatorTransport>, std::move(b_2)); + simulator.IncrementServerCountAndWaitForQuiescentState(b_addrs[1]); + + auto b_thread_3 = std::jthread(RunStorageRaft<SimulatorTransport>, std::move(b_3)); + simulator.IncrementServerCountAndWaitForQuiescentState(b_addrs[2]); + + // Spin up coordinators + + Io<SimulatorTransport> c_io_1 = simulator.RegisterNew(); + c_io_1.SetDefaultTimeout(one_second); + Io<SimulatorTransport> c_io_2 = simulator.RegisterNew(); + c_io_2.SetDefaultTimeout(one_second); + Io<SimulatorTransport> c_io_3 = simulator.RegisterNew(); + c_io_3.SetDefaultTimeout(one_second); + + std::vector<Address> c_addrs = {c_io_1.GetAddress(), c_io_2.GetAddress(), c_io_3.GetAddress()}; + + std::vector<Address> c_1_peers = {c_addrs[1], c_addrs[2]}; + std::vector<Address> c_2_peers = {c_addrs[0], c_addrs[2]}; + std::vector<Address> c_3_peers = {c_addrs[0], c_addrs[1]}; + + ConcreteCoordinatorRsm c_1{std::move(c_io_1), c_1_peers, Coordinator{(sm1)}}; + ConcreteCoordinatorRsm c_2{std::move(c_io_2), c_2_peers, Coordinator{(sm2)}}; + ConcreteCoordinatorRsm c_3{std::move(c_io_3), c_3_peers, Coordinator{(sm3)}}; + + auto c_thread_1 = std::jthread([c_1]() mutable { c_1.Run(); }); + simulator.IncrementServerCountAndWaitForQuiescentState(c_addrs[0]); + + auto c_thread_2 = std::jthread([c_2]() mutable { c_2.Run(); }); + simulator.IncrementServerCountAndWaitForQuiescentState(c_addrs[1]); + + auto c_thread_3 = std::jthread([c_3]() mutable { c_3.Run(); }); + simulator.IncrementServerCountAndWaitForQuiescentState(c_addrs[2]); + + std::cout << "beginning test after servers have become quiescent" << std::endl; + + // Have client contact coordinator RSM for a new transaction ID and + // also get the current shard map + CoordinatorClient<SimulatorTransport> coordinator_client(cli_io, c_addrs[0], c_addrs); + + memgraph::msgs::ShardRequestManager<SimulatorTransport> io(std::move(coordinator_client), std::move(cli_io)); + + io.StartTransaction(); + TestScanAll(io); + TestCreateVertices(io); + + simulator.ShutDown(); + return 0; +} diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 4cbc070fe..028eed26a 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -331,6 +331,41 @@ target_link_libraries(${test_prefix}storage_v3_expr mg-storage-v3 mg-expr) add_unit_test(storage_v3_schema.cpp) target_link_libraries(${test_prefix}storage_v3_schema mg-storage-v3) +# Test mg-query-v2 +# These are commented out because of the new TypedValue in the query engine +#add_unit_test(query_v2_interpreter.cpp ${CMAKE_SOURCE_DIR}/src/glue/v2/communication.cpp) +#target_link_libraries(${test_prefix}query_v2_interpreter mg-storage-v3 mg-query-v2 mg-communication) +# +#add_unit_test(query_v2_query_plan_accumulate_aggregate.cpp) +#target_link_libraries(${test_prefix}query_v2_query_plan_accumulate_aggregate mg-query-v2) +# +#add_unit_test(query_v2_query_plan_create_set_remove_delete.cpp) +#target_link_libraries(${test_prefix}query_v2_query_plan_create_set_remove_delete mg-query-v2 mg-expr) +# +#add_unit_test(query_v2_query_plan_bag_semantics.cpp) +#target_link_libraries(${test_prefix}query_v2_query_plan_bag_semantics mg-query-v2) +# +#add_unit_test(query_v2_query_plan_edge_cases.cpp ${CMAKE_SOURCE_DIR}/src/glue/v2/communication.cpp) +#target_link_libraries(${test_prefix}query_v2_query_plan_edge_cases mg-communication mg-query-v2) +# +#add_unit_test(query_v2_query_plan_v2_create_set_remove_delete.cpp) +#target_link_libraries(${test_prefix}query_v2_query_plan_v2_create_set_remove_delete mg-query-v2) +# +#add_unit_test(query_v2_query_plan_match_filter_return.cpp) +#target_link_libraries(${test_prefix}query_v2_query_plan_match_filter_return mg-query-v2) +# +#add_unit_test(query_v2_cypher_main_visitor.cpp) +#target_link_libraries(${test_prefix}query_v2_cypher_main_visitor mg-query-v2) +# +#add_unit_test(query_v2_query_required_privileges.cpp) +#target_link_libraries(${test_prefix}query_v2_query_required_privileges mg-query-v2) +# +#add_unit_test(replication_persistence_helper.cpp) +#target_link_libraries(${test_prefix}replication_persistence_helper mg-storage-v2) + +add_unit_test(query_v2_dummy_test.cpp) +target_link_libraries(${test_prefix}query_v2_dummy_test mg-query-v2) + add_unit_test(storage_v3_property_store.cpp) target_link_libraries(${test_prefix}storage_v3_property_store mg-storage-v3 fmt) @@ -349,33 +384,6 @@ target_link_libraries(${test_prefix}storage_v3_edge mg-storage-v3) add_unit_test(storage_v3_isolation_level.cpp) target_link_libraries(${test_prefix}storage_v3_isolation_level mg-storage-v3) -# Test mg-query-v2 -add_unit_test(query_v2_interpreter.cpp ${CMAKE_SOURCE_DIR}/src/glue/v2/communication.cpp) -target_link_libraries(${test_prefix}query_v2_interpreter mg-storage-v3 mg-query-v2 mg-communication) - -# add_unit_test(query_v2_query_plan_accumulate_aggregate.cpp) -# target_link_libraries(${test_prefix}query_v2_query_plan_accumulate_aggregate mg-query-v2) - -# # add_unit_test(query_v2_query_plan_create_set_remove_delete.cpp) -# # target_link_libraries(${test_prefix}query_v2_query_plan_create_set_remove_delete mg-query-v2 mg-expr) - -# add_unit_test(query_v2_query_plan_bag_semantics.cpp) -# target_link_libraries(${test_prefix}query_v2_query_plan_bag_semantics mg-query-v2) - -# add_unit_test(query_v2_query_plan_edge_cases.cpp ${CMAKE_SOURCE_DIR}/src/glue/v2/communication.cpp) -# target_link_libraries(${test_prefix}query_v2_query_plan_edge_cases mg-communication mg-query-v2) - -# add_unit_test(query_v2_query_plan_v2_create_set_remove_delete.cpp) -# target_link_libraries(${test_prefix}query_v2_query_plan_v2_create_set_remove_delete mg-query-v2) - -# add_unit_test(query_v2_query_plan_match_filter_return.cpp) -# target_link_libraries(${test_prefix}query_v2_query_plan_match_filter_return mg-query-v2) - -# add_unit_test(query_v2_cypher_main_visitor.cpp) -# target_link_libraries(${test_prefix}query_v2_cypher_main_visitor mg-query-v2) - -# add_unit_test(query_v2_query_required_privileges.cpp) -# target_link_libraries(${test_prefix}query_v2_query_required_privileges mg-query-v2) add_unit_test(replication_persistence_helper.cpp) target_link_libraries(${test_prefix}replication_persistence_helper mg-storage-v2) diff --git a/tests/unit/query_v2_dummy_test.cpp b/tests/unit/query_v2_dummy_test.cpp new file mode 100644 index 000000000..302de4b3a --- /dev/null +++ b/tests/unit/query_v2_dummy_test.cpp @@ -0,0 +1,36 @@ +// Copyright 2022 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include <iterator> +#include <memory> +#include <variant> +#include <vector> + +#include "common/types.hpp" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "query/v2/bindings/frame.hpp" +#include "query/v2/bindings/typed_value.hpp" +#include "query/v2/context.hpp" +#include "query/v2/db_accessor.hpp" +#include "query/v2/exceptions.hpp" +#include "query/v2/plan/operator.hpp" + +#include "query/v2/plan/operator.hpp" +#include "query_v2_query_plan_common.hpp" + +class Dummy : public testing::Test { + protected: + void SetUp() override {} +}; + +TEST_F(Dummy, DummyTest) { ASSERT_EQ(true, true); } diff --git a/tests/unit/storage_v3_expr.cpp b/tests/unit/storage_v3_expr.cpp index 5f165a405..dbcb5cf68 100644 --- a/tests/unit/storage_v3_expr.cpp +++ b/tests/unit/storage_v3_expr.cpp @@ -130,8 +130,8 @@ namespace memgraph::storage::v3::test { class ExpressionEvaluatorTest : public ::testing::Test { protected: - LabelId primary_label{LabelId::FromInt(0)}; - PropertyId primary_property{PropertyId::FromInt(1)}; + LabelId primary_label{LabelId::FromInt(1)}; + PropertyId primary_property{PropertyId::FromInt(2)}; PrimaryKey min_pk{PropertyValue(0)}; Shard db{primary_label, min_pk, std::nullopt};