From 0876a8848d993071c7661b13f0a2861750338cb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Boris=20Ta=C5=A1evski?= <36607228+BorisTasevski@users.noreply.github.com> Date: Wed, 14 Sep 2022 18:36:21 +0200 Subject: [PATCH] Merge master to epic and fix differences (#552) --- src/glue/communication.cpp | 28 ++ src/glue/communication.hpp | 9 + src/query/CMakeLists.txt | 4 +- src/query/common.cpp | 1 + src/query/db_accessor.cpp | 148 ++++++++ src/query/db_accessor.hpp | 188 +++++++++-- src/query/frontend/ast/ast.lcp | 5 +- .../frontend/ast/cypher_main_visitor.cpp | 4 + src/query/graph.cpp | 73 ++++ src/query/graph.hpp | 114 +++++++ .../interpret/awesome_memgraph_functions.cpp | 2 + src/query/interpret/eval.hpp | 27 ++ src/query/path.hpp | 6 + src/query/plan/operator.cpp | 38 ++- src/query/procedure/mg_procedure_impl.cpp | 307 +++++++++++++---- src/query/procedure/mg_procedure_impl.hpp | 91 +++-- src/query/typed_value.cpp | 21 ++ src/query/typed_value.hpp | 22 +- tests/e2e/write_procedures/CMakeLists.txt | 1 + tests/e2e/write_procedures/procedures/read.py | 59 ++++ .../e2e/write_procedures/procedures/write.py | 53 ++- tests/e2e/write_procedures/read_subgraph.py | 319 ++++++++++++++++++ tests/e2e/write_procedures/workloads.yaml | 5 + .../memgraph_V1/features/aggregations.feature | 58 ++++ tests/unit/formatters.hpp | 2 + tests/unit/query_procedures_mgp_graph.cpp | 19 +- 26 files changed, 1452 insertions(+), 152 deletions(-) create mode 100644 src/query/db_accessor.cpp create mode 100644 src/query/graph.cpp create mode 100644 src/query/graph.hpp create mode 100644 tests/e2e/write_procedures/read_subgraph.py diff --git a/src/glue/communication.cpp b/src/glue/communication.cpp index 90cf87c56..c8f7c1879 100644 --- a/src/glue/communication.cpp +++ b/src/glue/communication.cpp @@ -127,6 +127,10 @@ storage::Result<Value> ToBoltValue(const query::TypedValue &value, const storage return Value(value.ValueLocalDateTime()); case query::TypedValue::Type::Duration: return Value(value.ValueDuration()); + case query::TypedValue::Type::Graph: + auto maybe_graph = ToBoltGraph(value.ValueGraph(), db, view); + if (maybe_graph.HasError()) return maybe_graph.GetError(); + return Value(std::move(*maybe_graph)); } } @@ -183,6 +187,30 @@ storage::Result<communication::bolt::Path> ToBoltPath(const query::Path &path, c return communication::bolt::Path(vertices, edges); } +storage::Result<std::map<std::string, Value>> ToBoltGraph(const query::Graph &graph, const storage::Storage &db, + storage::View view) { + std::map<std::string, Value> map; + std::vector<Value> vertices; + vertices.reserve(graph.vertices().size()); + for (const auto &v : graph.vertices()) { + auto maybe_vertex = ToBoltVertex(v, db, view); + if (maybe_vertex.HasError()) return maybe_vertex.GetError(); + vertices.emplace_back(Value(std::move(*maybe_vertex))); + } + map.emplace("nodes", Value(vertices)); + + std::vector<Value> edges; + edges.reserve(graph.edges().size()); + for (const auto &e : graph.edges()) { + auto maybe_edge = ToBoltEdge(e, db, view); + if (maybe_edge.HasError()) return maybe_edge.GetError(); + edges.emplace_back(Value(std::move(*maybe_edge))); + } + map.emplace("edges", Value(edges)); + + return std::move(map); +} + storage::PropertyValue ToPropertyValue(const Value &value) { switch (value.type()) { case Value::Type::Null: diff --git a/src/glue/communication.hpp b/src/glue/communication.hpp index 6e5fd32c5..0e3b39f4d 100644 --- a/src/glue/communication.hpp +++ b/src/glue/communication.hpp @@ -51,6 +51,15 @@ storage::Result<communication::bolt::Edge> ToBoltEdge(const storage::EdgeAccesso storage::Result<communication::bolt::Path> ToBoltPath(const query::Path &path, const storage::Storage &db, storage::View view); +/// @param query::Graph for converting to communication::bolt::Map. +/// @param storage::Storage for ToBoltVertex and ToBoltEdge. +/// @param storage::View for ToBoltVertex and ToBoltEdge. +/// +/// @throw std::bad_alloc +storage::Result<std::map<std::string, communication::bolt::Value>> ToBoltGraph(const query::Graph &graph, + const storage::Storage &db, + storage::View view); + /// @param query::TypedValue for converting to communication::bolt::Value. /// @param storage::Storage for ToBoltVertex and ToBoltEdge. /// @param storage::View for ToBoltVertex and ToBoltEdge. diff --git a/src/query/CMakeLists.txt b/src/query/CMakeLists.txt index 0303f2fa5..77a12a430 100644 --- a/src/query/CMakeLists.txt +++ b/src/query/CMakeLists.txt @@ -39,7 +39,9 @@ set(mg_query_sources stream/common.cpp trigger.cpp trigger_context.cpp - typed_value.cpp) + typed_value.cpp + graph.cpp + db_accessor.cpp) find_package(Boost REQUIRED) diff --git a/src/query/common.cpp b/src/query/common.cpp index 0bf56baa0..793ae8044 100644 --- a/src/query/common.cpp +++ b/src/query/common.cpp @@ -61,6 +61,7 @@ bool TypedValueCompare(const TypedValue &a, const TypedValue &b) { case TypedValue::Type::Vertex: case TypedValue::Type::Edge: case TypedValue::Type::Path: + case TypedValue::Type::Graph: throw QueryRuntimeException("Comparison is not defined for values of type {}.", a.type()); case TypedValue::Type::Null: LOG_FATAL("Invalid type"); diff --git a/src/query/db_accessor.cpp b/src/query/db_accessor.cpp new file mode 100644 index 000000000..bbb9ac09a --- /dev/null +++ b/src/query/db_accessor.cpp @@ -0,0 +1,148 @@ +// 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/db_accessor.hpp" + +#include "query/graph.hpp" + +#include <cppitertools/filter.hpp> +#include <cppitertools/imap.hpp> +#include "utils/pmr/unordered_set.hpp" + +namespace memgraph::query { +SubgraphDbAccessor::SubgraphDbAccessor(query::DbAccessor db_accessor, Graph *graph) + : db_accessor_(db_accessor), graph_(graph) {} + +storage::PropertyId SubgraphDbAccessor::NameToProperty(const std::string_view name) { + return db_accessor_.NameToProperty(name); +} + +storage::LabelId SubgraphDbAccessor::NameToLabel(const std::string_view name) { return db_accessor_.NameToLabel(name); } + +storage::EdgeTypeId SubgraphDbAccessor::NameToEdgeType(const std::string_view name) { + return db_accessor_.NameToEdgeType(name); +} + +const std::string &SubgraphDbAccessor::PropertyToName(storage::PropertyId prop) const { + return db_accessor_.PropertyToName(prop); +} + +const std::string &SubgraphDbAccessor::LabelToName(storage::LabelId label) const { + return db_accessor_.LabelToName(label); +} + +const std::string &SubgraphDbAccessor::EdgeTypeToName(storage::EdgeTypeId type) const { + return db_accessor_.EdgeTypeToName(type); +} + +storage::Result<std::optional<EdgeAccessor>> SubgraphDbAccessor::RemoveEdge(EdgeAccessor *edge) { + if (!this->graph_->ContainsEdge(*edge)) { + throw std::logic_error{"Projected graph must contain edge!"}; + } + auto result = db_accessor_.RemoveEdge(edge); + if (result.HasError() || !*result) { + return result; + } + return this->graph_->RemoveEdge(*edge); +} + +storage::Result<EdgeAccessor> SubgraphDbAccessor::InsertEdge(SubgraphVertexAccessor *from, SubgraphVertexAccessor *to, + const storage::EdgeTypeId &edge_type) { + VertexAccessor *from_impl = &from->impl_; + VertexAccessor *to_impl = &to->impl_; + if (!this->graph_->ContainsVertex(*from_impl) || !this->graph_->ContainsVertex(*to_impl)) { + throw std::logic_error{"Projected graph must contain both vertices to insert edge!"}; + } + auto result = db_accessor_.InsertEdge(from_impl, to_impl, edge_type); + if (result.HasError()) { + return result; + } + this->graph_->InsertEdge(*result); + return result; +} + +storage::Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> +SubgraphDbAccessor::DetachRemoveVertex( // NOLINT(readability-convert-member-functions-to-static) + SubgraphVertexAccessor *) { // NOLINT(hicpp-named-parameter) + throw std::logic_error{ + "Vertex holds only partial information about edges. Cannot detach delete safely while using projected graph."}; +} + +storage::Result<std::optional<VertexAccessor>> SubgraphDbAccessor::RemoveVertex( + SubgraphVertexAccessor *subgraphvertex_accessor) { + VertexAccessor *vertex_accessor = &subgraphvertex_accessor->impl_; + if (!this->graph_->ContainsVertex(*vertex_accessor)) { + throw std::logic_error{"Projected graph must contain vertex!"}; + } + auto result = db_accessor_.RemoveVertex(vertex_accessor); + if (result.HasError() || !*result) { + return result; + } + return this->graph_->RemoveVertex(*vertex_accessor); +} + +SubgraphVertexAccessor SubgraphDbAccessor::InsertVertex() { + VertexAccessor vertex = db_accessor_.InsertVertex(); + this->graph_->InsertVertex(vertex); + return SubgraphVertexAccessor(vertex, this->getGraph()); +} + +VerticesIterable SubgraphDbAccessor::Vertices(storage::View) { // NOLINT(hicpp-named-parameter) + return VerticesIterable(&graph_->vertices()); +} + +std::optional<VertexAccessor> SubgraphDbAccessor::FindVertex(storage::Gid gid, storage::View view) { + std::optional<VertexAccessor> maybe_vertex = db_accessor_.FindVertex(gid, view); + if (maybe_vertex && this->graph_->ContainsVertex(*maybe_vertex)) { + return *maybe_vertex; + } + return std::nullopt; +} + +query::Graph *SubgraphDbAccessor::getGraph() { return graph_; } + +VertexAccessor SubgraphVertexAccessor::GetVertexAccessor() const { return impl_; } + +auto SubgraphVertexAccessor::OutEdges(storage::View view) const -> decltype(impl_.OutEdges(view)) { + auto maybe_edges = impl_.impl_.OutEdges(view, {}); + if (maybe_edges.HasError()) return maybe_edges.GetError(); + auto edges = std::move(*maybe_edges); + auto graph_edges = graph_->edges(); + + std::vector<storage::EdgeAccessor> filteredOutEdges; + for (auto &edge : edges) { + auto edge_q = EdgeAccessor(edge); + if (graph_edges.contains(edge_q)) { + filteredOutEdges.push_back(edge); + } + } + + return iter::imap(VertexAccessor::MakeEdgeAccessor, std::move(filteredOutEdges)); +} + +auto SubgraphVertexAccessor::InEdges(storage::View view) const -> decltype(impl_.InEdges(view)) { + auto maybe_edges = impl_.impl_.InEdges(view, {}); + if (maybe_edges.HasError()) return maybe_edges.GetError(); + auto edges = std::move(*maybe_edges); + auto graph_edges = graph_->edges(); + + std::vector<storage::EdgeAccessor> filteredOutEdges; + for (auto &edge : edges) { + auto edge_q = EdgeAccessor(edge); + if (graph_edges.contains(edge_q)) { + filteredOutEdges.push_back(edge); + } + } + + return iter::imap(VertexAccessor::MakeEdgeAccessor, std::move(filteredOutEdges)); +} + +} // namespace memgraph::query diff --git a/src/query/db_accessor.hpp b/src/query/db_accessor.hpp index 25415e6a1..ecd719e78 100644 --- a/src/query/db_accessor.hpp +++ b/src/query/db_accessor.hpp @@ -20,6 +20,8 @@ #include "storage/v2/id_types.hpp" #include "storage/v2/property_value.hpp" #include "storage/v2/result.hpp" +#include "utils/pmr/unordered_set.hpp" +#include "utils/variant_helpers.hpp" /////////////////////////////////////////////////////////// // Our communication layer and query engine don't mix @@ -45,6 +47,7 @@ namespace memgraph::query { +class Graph; class VertexAccessor; class EdgeAccessor final { @@ -185,38 +188,123 @@ inline VertexAccessor EdgeAccessor::From() const { return VertexAccessor(impl_.F inline bool EdgeAccessor::IsCycle() const { return To() == From(); } -class DbAccessor final { - storage::Storage::Accessor *accessor_; +class SubgraphVertexAccessor final { + public: + query::VertexAccessor impl_; + query::Graph *graph_; - class VerticesIterable final { - storage::VerticesIterable iterable_; + explicit SubgraphVertexAccessor(query::VertexAccessor impl, query::Graph *graph_) : impl_(impl), graph_(graph_) {} + + bool operator==(const SubgraphVertexAccessor &v) const noexcept { + static_assert(noexcept(impl_ == v.impl_)); + return impl_ == v.impl_; + } + + auto InEdges(storage::View view) const -> decltype(impl_.OutEdges(view)); + + auto OutEdges(storage::View view) const -> decltype(impl_.OutEdges(view)); + + auto Labels(storage::View view) const { return impl_.Labels(view); } + + storage::Result<bool> AddLabel(storage::LabelId label) { return impl_.AddLabel(label); } + + storage::Result<bool> RemoveLabel(storage::LabelId label) { return impl_.RemoveLabel(label); } + + storage::Result<bool> HasLabel(storage::View view, storage::LabelId label) const { + return impl_.HasLabel(view, label); + } + + auto Properties(storage::View view) const { return impl_.Properties(view); } + + storage::Result<storage::PropertyValue> GetProperty(storage::View view, storage::PropertyId key) const { + return impl_.GetProperty(view, key); + } + + storage::Gid Gid() const noexcept { return impl_.Gid(); } + + storage::Result<storage::PropertyValue> SetProperty(storage::PropertyId key, const storage::PropertyValue &value) { + return impl_.SetProperty(key, value); + } + VertexAccessor GetVertexAccessor() const; +}; +} // namespace memgraph::query + +namespace std { + +template <> +struct hash<memgraph::query::VertexAccessor> { + size_t operator()(const memgraph::query::VertexAccessor &v) const { return std::hash<decltype(v.impl_)>{}(v.impl_); } +}; + +template <> +struct hash<memgraph::query::EdgeAccessor> { + size_t operator()(const memgraph::query::EdgeAccessor &e) const { return std::hash<decltype(e.impl_)>{}(e.impl_); } +}; + +} // namespace std + +namespace memgraph::query { + +class VerticesIterable final { + std::variant<storage::VerticesIterable, std::unordered_set<VertexAccessor, std::hash<VertexAccessor>, + std::equal_to<void>, utils::Allocator<VertexAccessor>> *> + iterable_; + + public: + class Iterator final { + std::variant<storage::VerticesIterable::Iterator, + std::unordered_set<VertexAccessor, std::hash<VertexAccessor>, std::equal_to<void>, + utils::Allocator<VertexAccessor>>::iterator> + it_; public: - class Iterator final { - storage::VerticesIterable::Iterator it_; + explicit Iterator(storage::VerticesIterable::Iterator it) : it_(it) {} + explicit Iterator(std::unordered_set<VertexAccessor, std::hash<VertexAccessor>, std::equal_to<void>, + utils::Allocator<VertexAccessor>>::iterator it) + : it_(it) {} - public: - explicit Iterator(storage::VerticesIterable::Iterator it) : it_(it) {} + VertexAccessor operator*() const { + return std::visit([](auto it_) { return VertexAccessor(*it_); }, it_); + } - VertexAccessor operator*() const { return VertexAccessor(*it_); } + Iterator &operator++() { + std::visit([this](auto it_) { this->it_ = ++it_; }, it_); + return *this; + } - Iterator &operator++() { - ++it_; - return *this; - } + bool operator==(const Iterator &other) const { return it_ == other.it_; } - bool operator==(const Iterator &other) const { return it_ == other.it_; } - - bool operator!=(const Iterator &other) const { return !(other == *this); } - }; - - explicit VerticesIterable(storage::VerticesIterable iterable) : iterable_(std::move(iterable)) {} - - Iterator begin() { return Iterator(iterable_.begin()); } - - Iterator end() { return Iterator(iterable_.end()); } + bool operator!=(const Iterator &other) const { return !(other == *this); } }; + explicit VerticesIterable(storage::VerticesIterable iterable) : iterable_(std::move(iterable)) {} + explicit VerticesIterable(std::unordered_set<VertexAccessor, std::hash<VertexAccessor>, std::equal_to<void>, + utils::Allocator<VertexAccessor>> *vertices) + : iterable_(vertices) {} + + Iterator begin() { + return std::visit(memgraph::utils::Overloaded{ + [](storage::VerticesIterable &iterable_) { return Iterator(iterable_.begin()); }, + [](std::unordered_set<VertexAccessor, std::hash<VertexAccessor>, std::equal_to<void>, + utils::Allocator<VertexAccessor>> *iterable_) { + return Iterator(iterable_->begin()); + }}, + iterable_); + } + + Iterator end() { + return std::visit( + memgraph::utils::Overloaded{ + [](storage::VerticesIterable &iterable_) { return Iterator(iterable_.end()); }, + [](std::unordered_set<VertexAccessor, std::hash<VertexAccessor>, std::equal_to<void>, + utils::Allocator<VertexAccessor>> *iterable_) { return Iterator(iterable_->end()); }}, + iterable_); + } +}; + +class DbAccessor final { + storage::Storage::Accessor *accessor_; + public: explicit DbAccessor(storage::Storage::Accessor *accessor) : accessor_(accessor) {} @@ -358,18 +446,44 @@ class DbAccessor final { storage::ConstraintsInfo ListAllConstraints() const { return accessor_->ListAllConstraints(); } }; +class SubgraphDbAccessor final { + DbAccessor db_accessor_; + Graph *graph_; + + public: + explicit SubgraphDbAccessor(DbAccessor db_accessor, Graph *graph); + + static SubgraphDbAccessor *MakeSubgraphDbAccessor(DbAccessor *db_accessor, Graph *graph); + + storage::PropertyId NameToProperty(std::string_view name); + + storage::LabelId NameToLabel(std::string_view name); + + storage::EdgeTypeId NameToEdgeType(std::string_view name); + + const std::string &PropertyToName(storage::PropertyId prop) const; + + const std::string &LabelToName(storage::LabelId label) const; + + const std::string &EdgeTypeToName(storage::EdgeTypeId type) const; + + storage::Result<std::optional<EdgeAccessor>> RemoveEdge(EdgeAccessor *edge); + + storage::Result<EdgeAccessor> InsertEdge(SubgraphVertexAccessor *from, SubgraphVertexAccessor *to, + const storage::EdgeTypeId &edge_type); + + storage::Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> DetachRemoveVertex( + SubgraphVertexAccessor *vertex_accessor); + + storage::Result<std::optional<VertexAccessor>> RemoveVertex(SubgraphVertexAccessor *vertex_accessor); + + SubgraphVertexAccessor InsertVertex(); + + VerticesIterable Vertices(storage::View view); + + std::optional<VertexAccessor> FindVertex(storage::Gid gid, storage::View view); + + Graph *getGraph(); +}; + } // namespace memgraph::query - -namespace std { - -template <> -struct hash<memgraph::query::VertexAccessor> { - size_t operator()(const memgraph::query::VertexAccessor &v) const { return std::hash<decltype(v.impl_)>{}(v.impl_); } -}; - -template <> -struct hash<memgraph::query::EdgeAccessor> { - size_t operator()(const memgraph::query::EdgeAccessor &e) const { return std::hash<decltype(e.impl_)>{}(e.impl_); } -}; - -} // namespace std diff --git a/src/query/frontend/ast/ast.lcp b/src/query/frontend/ast/ast.lcp index 5ced6ee2f..f78f5f5c3 100644 --- a/src/query/frontend/ast/ast.lcp +++ b/src/query/frontend/ast/ast.lcp @@ -464,7 +464,7 @@ cpp<# :documentation "Symbol table position of the symbol this Aggregation is mapped to.")) (:public (lcp:define-enum op - (count min max sum avg collect-list collect-map) + (count min max sum avg collect-list collect-map project) (:serialize)) #>cpp Aggregation() = default; @@ -475,10 +475,11 @@ cpp<# static const constexpr char *const kSum = "SUM"; static const constexpr char *const kAvg = "AVG"; static const constexpr char *const kCollect = "COLLECT"; + static const constexpr char *const kProject = "PROJECT"; static std::string OpToString(Op op) { const char *op_strings[] = {kCount, kMin, kMax, kSum, - kAvg, kCollect, kCollect}; + kAvg, kCollect, kCollect, kProject}; return op_strings[static_cast<int>(op)]; } diff --git a/src/query/frontend/ast/cypher_main_visitor.cpp b/src/query/frontend/ast/cypher_main_visitor.cpp index 01486cae2..d4c66a7a9 100644 --- a/src/query/frontend/ast/cypher_main_visitor.cpp +++ b/src/query/frontend/ast/cypher_main_visitor.cpp @@ -2250,6 +2250,10 @@ antlrcpp::Any CypherMainVisitor::visitFunctionInvocation(MemgraphCypher::Functio return static_cast<Expression *>( storage_->Create<Aggregation>(expressions[0], nullptr, Aggregation::Op::COLLECT_LIST)); } + if (function_name == Aggregation::kProject) { + return static_cast<Expression *>( + storage_->Create<Aggregation>(expressions[0], nullptr, Aggregation::Op::PROJECT)); + } } if (expressions.size() == 2U && function_name == Aggregation::kCollect) { diff --git a/src/query/graph.cpp b/src/query/graph.cpp new file mode 100644 index 000000000..8407c15a7 --- /dev/null +++ b/src/query/graph.cpp @@ -0,0 +1,73 @@ +// 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/graph.hpp" +#include "query/path.hpp" + +namespace memgraph::query { + +Graph::Graph(utils::MemoryResource *memory) : vertices_(memory), edges_(memory) {} + +Graph::Graph(const Graph &other, utils::MemoryResource *memory) + : vertices_(other.vertices_, memory), edges_(other.edges_, memory) {} + +Graph::Graph(Graph &&other) noexcept : Graph(std::move(other), other.GetMemoryResource()) {} + +Graph::Graph(const Graph &other) + : Graph(other, + std::allocator_traits<allocator_type>::select_on_container_copy_construction(other.GetMemoryResource()) + .GetMemoryResource()) {} + +Graph::Graph(Graph &&other, utils::MemoryResource *memory) + : vertices_(std::move(other.vertices_), memory), edges_(std::move(other.edges_), memory) {} + +void Graph::Expand(const Path &path) { + const auto &path_vertices_ = path.vertices(); + const auto &path_edges_ = path.edges(); + std::for_each(path_vertices_.begin(), path_vertices_.end(), [this](const VertexAccessor v) { vertices_.insert(v); }); + std::for_each(path_edges_.begin(), path_edges_.end(), [this](const EdgeAccessor e) { edges_.insert(e); }); +} + +void Graph::InsertVertex(const VertexAccessor &vertex) { vertices_.insert(vertex); } + +void Graph::InsertEdge(const EdgeAccessor &edge) { edges_.insert(edge); } + +bool Graph::ContainsVertex(const VertexAccessor &vertex) { return vertices_.contains(vertex); } + +bool Graph::ContainsEdge(const EdgeAccessor &edge) { return edges_.contains(edge); } + +std::optional<VertexAccessor> Graph::RemoveVertex(const VertexAccessor &vertex) { + if (!ContainsVertex(vertex)) { + return std::nullopt; + } + auto value = vertices_.erase(vertex); + if (value == 0) { + return std::nullopt; + } + return vertex; +} + +std::optional<EdgeAccessor> Graph::RemoveEdge(const EdgeAccessor &edge) { + auto value = edges_.erase(edge); + if (value == 0) { + return std::nullopt; + } + return edge; +} + +utils::pmr::unordered_set<VertexAccessor> &Graph::vertices() { return vertices_; } +utils::pmr::unordered_set<EdgeAccessor> &Graph::edges() { return edges_; } +const utils::pmr::unordered_set<VertexAccessor> &Graph::vertices() const { return vertices_; } +const utils::pmr::unordered_set<EdgeAccessor> &Graph::edges() const { return edges_; } + +utils::MemoryResource *Graph::GetMemoryResource() const { return vertices_.get_allocator().GetMemoryResource(); } + +} // namespace memgraph::query diff --git a/src/query/graph.hpp b/src/query/graph.hpp new file mode 100644 index 000000000..801a9f685 --- /dev/null +++ b/src/query/graph.hpp @@ -0,0 +1,114 @@ +// 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 <functional> +#include <utility> +#include "query/db_accessor.hpp" +#include "utils/logging.hpp" +#include "utils/memory.hpp" +#include "utils/pmr/unordered_set.hpp" +#include "utils/pmr/vector.hpp" + +namespace memgraph::query { + +class Path; +/** + * A data structure that holds a graph. A graph consists of at least one + * vertex, and zero or more edges. + */ +class Graph final { + public: + /** Allocator type so that STL containers are aware that we need one */ + using allocator_type = utils::Allocator<Graph>; + + /** + * Create the graph with no elements + * Allocations are done using the given MemoryResource. + */ + explicit Graph(utils::MemoryResource *memory); + + /** + * Construct a copy of other. + * utils::MemoryResource is obtained by calling + * std::allocator_traits<>:: + * select_on_container_copy_construction(other.GetMemoryResource()). + * Since we use utils::Allocator, which does not propagate, this means that we + * will default to utils::NewDeleteResource(). + */ + Graph(const Graph &other); + + /** Construct a copy using the given utils::MemoryResource */ + Graph(const Graph &other, utils::MemoryResource *memory); + + /** + * Construct with the value of other. + * utils::MemoryResource is obtained from other. After the move, other will be + * empty. + */ + Graph(Graph &&other) noexcept; + + /** + * Construct with the value of other, but use the given utils::MemoryResource. + * After the move, other may not be empty if `*memory != + * *other.GetMemoryResource()`, because an element-wise move will be + * performed. + */ + Graph(Graph &&other, utils::MemoryResource *memory); + + /** Expands the graph with the given path. */ + void Expand(const Path &path); + + /** Inserts the vertex in the graph. */ + void InsertVertex(const VertexAccessor &vertex); + + /** Inserts the edge in the graph. */ + void InsertEdge(const EdgeAccessor &edge); + + /** Checks whether the graph contains the vertex. */ + bool ContainsVertex(const VertexAccessor &vertex); + + /** Checks whether the graph contains the edge. */ + bool ContainsEdge(const EdgeAccessor &edge); + + /** Removes the vertex from the graph if the vertex is in the graph. */ + std::optional<VertexAccessor> RemoveVertex(const VertexAccessor &vertex); + + /** Removes the vertex from the graph if the vertex is in the graph. */ + std::optional<EdgeAccessor> RemoveEdge(const EdgeAccessor &edge); + + /** Return the out edges of the given vertex. */ + std::vector<EdgeAccessor> OutEdges(VertexAccessor vertex_accessor); + + /** Copy assign other, utils::MemoryResource of `this` is used */ + Graph &operator=(const Graph &) = default; + + /** Move assign other, utils::MemoryResource of `this` is used. */ + Graph &operator=(Graph &&) noexcept = default; + + ~Graph() = default; + + utils::pmr::unordered_set<VertexAccessor> &vertices(); + utils::pmr::unordered_set<EdgeAccessor> &edges(); + const utils::pmr::unordered_set<VertexAccessor> &vertices() const; + const utils::pmr::unordered_set<EdgeAccessor> &edges() const; + + utils::MemoryResource *GetMemoryResource() const; + + private: + // Contains all the vertices in the Graph. + utils::pmr::unordered_set<VertexAccessor> vertices_; + // Contains all the edges in the Graph + utils::pmr::unordered_set<EdgeAccessor> edges_; +}; + +} // namespace memgraph::query diff --git a/src/query/interpret/awesome_memgraph_functions.cpp b/src/query/interpret/awesome_memgraph_functions.cpp index 7fae2c190..21d045601 100644 --- a/src/query/interpret/awesome_memgraph_functions.cpp +++ b/src/query/interpret/awesome_memgraph_functions.cpp @@ -587,6 +587,8 @@ TypedValue ValueType(const TypedValue *args, int64_t nargs, const FunctionContex return TypedValue("LOCAL_DATE_TIME", ctx.memory); case TypedValue::Type::Duration: return TypedValue("DURATION", ctx.memory); + case TypedValue::Type::Graph: + throw QueryRuntimeException("Cannot fetch graph as it is not standardized openCypher type name"); } } diff --git a/src/query/interpret/eval.hpp b/src/query/interpret/eval.hpp index 398d25b5e..1ea20846d 100644 --- a/src/query/interpret/eval.hpp +++ b/src/query/interpret/eval.hpp @@ -313,6 +313,25 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> { } return std::nullopt; }; + auto maybe_graph = [this](const auto &graph, const auto &prop_name) -> std::optional<TypedValue> { + if (prop_name == "nodes") { + utils::pmr::vector<TypedValue> vertices(ctx_->memory); + vertices.reserve(graph.vertices().size()); + for (const auto &v : graph.vertices()) { + vertices.emplace_back(TypedValue(v, ctx_->memory)); + } + return TypedValue(vertices, ctx_->memory); + } + if (prop_name == "edges") { + utils::pmr::vector<TypedValue> edges(ctx_->memory); + edges.reserve(graph.edges().size()); + for (const auto &e : graph.edges()) { + edges.emplace_back(TypedValue(e, ctx_->memory)); + } + return TypedValue(edges, ctx_->memory); + } + return std::nullopt; + }; switch (expression_result.type()) { case TypedValue::Type::Null: return TypedValue(ctx_->memory); @@ -365,6 +384,14 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> { } throw QueryRuntimeException("Invalid property name {} for LocalDateTime", prop_name); } + case TypedValue::Type::Graph: { + const auto &prop_name = property_lookup.property_.name; + const auto &graph = expression_result.ValueGraph(); + if (auto graph_field = maybe_graph(graph, prop_name); graph_field) { + return std::move(*graph_field); + } + throw QueryRuntimeException("Invalid property name {} for Graph", prop_name); + } default: throw QueryRuntimeException("Only nodes, edges, maps and temporal types have properties to be looked-up."); } diff --git a/src/query/path.hpp b/src/query/path.hpp index 832532fbc..43ecb8953 100644 --- a/src/query/path.hpp +++ b/src/query/path.hpp @@ -31,6 +31,12 @@ class Path { /** Allocator type so that STL containers are aware that we need one */ using allocator_type = utils::Allocator<char>; + /** + * Create the path with no elements + * Allocations are done using the given MemoryResource. + */ + explicit Path(utils::MemoryResource *memory) : vertices_(memory), edges_(memory) {} + /** * Create the path starting with the given vertex. * Allocations are done using the given MemoryResource. diff --git a/src/query/plan/operator.cpp b/src/query/plan/operator.cpp index cbc27ccc4..f374a7c1c 100644 --- a/src/query/plan/operator.cpp +++ b/src/query/plan/operator.cpp @@ -33,6 +33,7 @@ #include "query/exceptions.hpp" #include "query/frontend/ast/ast.hpp" #include "query/frontend/semantic/symbol_table.hpp" +#include "query/graph.hpp" #include "query/interpret/eval.hpp" #include "query/path.hpp" #include "query/plan/scoped_profile.hpp" @@ -3143,6 +3144,8 @@ TypedValue DefaultAggregationOpValue(const Aggregate::Element &element, utils::M return TypedValue(TypedValue::TVector(memory)); case Aggregation::Op::COLLECT_MAP: return TypedValue(TypedValue::TMap(memory)); + case Aggregation::Op::PROJECT: + return TypedValue(query::Graph(memory)); } } } // namespace @@ -3305,7 +3308,6 @@ class AggregateCursor : public Cursor { "Expected as much AggregationValue.counts_ as there are " "aggregations."); - // we iterate over counts, values and aggregation info at the same time auto count_it = agg_value->counts_.begin(); auto value_it = agg_value->values_.begin(); auto agg_elem_it = self_.aggregations_.begin(); @@ -3344,6 +3346,11 @@ class AggregateCursor : public Cursor { case Aggregation::Op::COLLECT_LIST: value_it->ValueList().push_back(input_value); break; + case Aggregation::Op::PROJECT: { + EnsureOkForProject(input_value); + value_it->ValueGraph().Expand(input_value.ValuePath()); + break; + } case Aggregation::Op::COLLECT_MAP: auto key = agg_elem_it->key->Accept(*evaluator); if (key.type() != TypedValue::Type::String) throw QueryRuntimeException("Map key must be a string."); @@ -3392,6 +3399,11 @@ class AggregateCursor : public Cursor { case Aggregation::Op::COLLECT_LIST: value_it->ValueList().push_back(input_value); break; + case Aggregation::Op::PROJECT: { + EnsureOkForProject(input_value); + value_it->ValueGraph().Expand(input_value.ValuePath()); + break; + } case Aggregation::Op::COLLECT_MAP: auto key = agg_elem_it->key->Accept(*evaluator); if (key.type() != TypedValue::Type::String) throw QueryRuntimeException("Map key must be a string."); @@ -3428,6 +3440,18 @@ class AggregateCursor : public Cursor { throw QueryRuntimeException("Only numeric values allowed in SUM and AVG aggregations."); } } + + /** Checks if the given TypedValue is legal in PROJECT and PROJECT_TRANSITIVE. If not + * an appropriate exception is thrown. */ + // NOLINTNEXTLINE(readability-convert-member-functions-to-static) + void EnsureOkForProject(const TypedValue &value) const { + switch (value.type()) { + case TypedValue::Type::Path: + return; + default: + throw QueryRuntimeException("Only path values allowed in PROJECT aggregation."); + } + } }; UniqueCursorPtr Aggregate::MakeCursor(utils::MemoryResource *mem) const { @@ -4238,6 +4262,18 @@ void CallCustomProcedure(const std::string_view fully_qualified_procedure_name, for (auto *expression : args) { args_list.emplace_back(expression->Accept(*evaluator)); } + std::optional<query::Graph> subgraph; + std::optional<query::SubgraphDbAccessor> db_acc; + + if (!args_list.empty() && args_list.front().type() == TypedValue::Type::Graph) { + auto subgraph_value = args_list.front().ValueGraph(); + subgraph = query::Graph(std::move(subgraph_value), subgraph_value.GetMemoryResource()); + args_list.erase(args_list.begin()); + + db_acc = query::SubgraphDbAccessor(*std::get<query::DbAccessor *>(graph.impl), &*subgraph); + graph.impl = &*db_acc; + } + 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, diff --git a/src/query/procedure/mg_procedure_impl.cpp b/src/query/procedure/mg_procedure_impl.cpp index 03f60bf8b..c01070d52 100644 --- a/src/query/procedure/mg_procedure_impl.cpp +++ b/src/query/procedure/mg_procedure_impl.cpp @@ -40,6 +40,9 @@ #include "utils/temporal.hpp" #include "utils/variant_helpers.hpp" +#include <cppitertools/filter.hpp> +#include <cppitertools/imap.hpp> + // This file contains implementation of top level C API functions, but this is // all actually part of memgraph::query::procedure. So use that namespace for simplicity. // NOLINTNEXTLINE(google-build-using-namespace) @@ -299,6 +302,8 @@ mgp_value_type FromTypedValueType(memgraph::query::TypedValue::Type type) { return MGP_VALUE_TYPE_LOCAL_DATE_TIME; case memgraph::query::TypedValue::Type::Duration: return MGP_VALUE_TYPE_DURATION; + case memgraph::query::TypedValue::Type::Graph: + throw std::logic_error{"mgp_value for TypedValue::Type::Graph doesn't exist."}; } } } // namespace @@ -333,17 +338,17 @@ memgraph::query::TypedValue ToTypedValue(const mgp_value &val, memgraph::utils:: return memgraph::query::TypedValue(std::move(tv_map)); } case MGP_VALUE_TYPE_VERTEX: - return memgraph::query::TypedValue(val.vertex_v->impl, memory); + return memgraph::query::TypedValue(val.vertex_v->getImpl(), memory); case MGP_VALUE_TYPE_EDGE: return memgraph::query::TypedValue(val.edge_v->impl, memory); case MGP_VALUE_TYPE_PATH: { const auto *path = val.path_v; MG_ASSERT(!path->vertices.empty()); MG_ASSERT(path->vertices.size() == path->edges.size() + 1); - memgraph::query::Path tv_path(path->vertices[0].impl, memory); + memgraph::query::Path tv_path(path->vertices[0].getImpl(), memory); for (size_t i = 0; i < path->edges.size(); ++i) { tv_path.Expand(path->edges[i].impl); - tv_path.Expand(path->vertices[i + 1].impl); + tv_path.Expand(path->vertices[i + 1].getImpl()); } return memgraph::query::TypedValue(std::move(tv_path)); } @@ -463,12 +468,31 @@ mgp_value::mgp_value(const memgraph::query::TypedValue &tv, mgp_graph *graph, me } case MGP_VALUE_TYPE_VERTEX: { memgraph::utils::Allocator<mgp_vertex> allocator(m); - vertex_v = allocator.new_object<mgp_vertex>(tv.ValueVertex(), graph); + vertex_v = std::visit( + memgraph::utils::Overloaded{ + [&](memgraph::query::DbAccessor *) { return allocator.new_object<mgp_vertex>(tv.ValueVertex(), graph); }, + [&](memgraph::query::SubgraphDbAccessor *impl) { + return allocator.new_object<mgp_vertex>( + memgraph::query::SubgraphVertexAccessor(tv.ValueVertex(), impl->getGraph()), graph); + }}, + graph->impl); + break; } case MGP_VALUE_TYPE_EDGE: { memgraph::utils::Allocator<mgp_edge> allocator(m); - edge_v = allocator.new_object<mgp_edge>(tv.ValueEdge(), graph); + + edge_v = std::visit( + memgraph::utils::Overloaded{ + [&tv, graph, &allocator](memgraph::query::DbAccessor *) { + return allocator.new_object<mgp_edge>(tv.ValueEdge(), graph); + }, + [&tv, graph, &allocator](memgraph::query::SubgraphDbAccessor *db_impl) { + return allocator.new_object<mgp_edge>( + tv.ValueEdge(), memgraph::query::SubgraphVertexAccessor(tv.ValueEdge().From(), db_impl->getGraph()), + memgraph::query::SubgraphVertexAccessor(tv.ValueEdge().To(), db_impl->getGraph()), graph); + }}, + graph->impl); break; } case MGP_VALUE_TYPE_PATH: { @@ -479,11 +503,24 @@ mgp_value::mgp_value(const memgraph::query::TypedValue &tv, mgp_graph *graph, me mgp_path tmp_path(m); tmp_path.vertices.reserve(tv.ValuePath().vertices().size()); for (const auto &v : tv.ValuePath().vertices()) { - tmp_path.vertices.emplace_back(v, graph); + std::visit( + memgraph::utils::Overloaded{ + [&v, graph, &tmp_path](memgraph::query::DbAccessor *) { tmp_path.vertices.emplace_back(v, graph); }, + [&v, graph, &tmp_path](memgraph::query::SubgraphDbAccessor *impl) { + tmp_path.vertices.emplace_back(memgraph::query::SubgraphVertexAccessor(v, impl->getGraph()), graph); + }}, + graph->impl); } tmp_path.edges.reserve(tv.ValuePath().edges().size()); for (const auto &e : tv.ValuePath().edges()) { - tmp_path.edges.emplace_back(e, graph); + std::visit(memgraph::utils::Overloaded{ + [&e, graph, &tmp_path](memgraph::query::DbAccessor *) { tmp_path.edges.emplace_back(e, graph); }, + [&e, graph, &tmp_path](memgraph::query::SubgraphDbAccessor *db_impl) { + tmp_path.edges.emplace_back( + e, memgraph::query::SubgraphVertexAccessor(e.From(), db_impl->getGraph()), + memgraph::query::SubgraphVertexAccessor(e.To(), db_impl->getGraph()), graph); + }}, + graph->impl); } memgraph::utils::Allocator<mgp_path> allocator(m); path_v = allocator.new_object<mgp_path>(std::move(tmp_path)); @@ -808,7 +845,15 @@ mgp_value::mgp_value(mgp_value &&other, memgraph::utils::MemoryResource *m) : ty mgp_value::~mgp_value() noexcept { DeleteValueMember(this); } mgp_edge *mgp_edge::Copy(const mgp_edge &edge, mgp_memory &memory) { - return NewRawMgpObject<mgp_edge>(&memory, edge.impl, edge.from.graph); + return std::visit( + memgraph::utils::Overloaded{ + [&](memgraph::query::DbAccessor *) { return NewRawMgpObject<mgp_edge>(&memory, edge.impl, edge.from.graph); }, + [&](memgraph::query::SubgraphDbAccessor *db_impl) { + return NewRawMgpObject<mgp_edge>( + &memory, edge.impl, memgraph::query::SubgraphVertexAccessor(edge.impl.From(), db_impl->getGraph()), + memgraph::query::SubgraphVertexAccessor(edge.impl.To(), db_impl->getGraph()), edge.to.graph); + }}, + edge.to.graph->impl); } void mgp_value_destroy(mgp_value *val) { DeleteRawMgpObject(val); } @@ -1140,7 +1185,7 @@ mgp_error mgp_path_equal(mgp_path *p1, mgp_path *p2, int *result) { } const auto *start1 = Call<mgp_vertex *>(mgp_path_vertex_at, p1, 0); const auto *start2 = Call<mgp_vertex *>(mgp_path_vertex_at, p2, 0); - static_assert(noexcept(start1->impl == start2->impl)); + static_assert(noexcept(start1 == start2)); if (*start1 != *start2) { return 0; } @@ -1507,9 +1552,12 @@ mgp_error mgp_properties_iterator_next(mgp_properties_iterator *it, mgp_property return nullptr; } memgraph::utils::OnScopeExit clean_up([it] { it->current = std::nullopt; }); - it->current.emplace(memgraph::utils::pmr::string(it->graph->impl->PropertyToName(it->current_it->first), - it->GetMemoryResource()), - mgp_value(it->current_it->second, it->GetMemoryResource())); + auto propToName = std::visit( + [it](auto *impl) { + return memgraph::utils::pmr::string(impl->PropertyToName(it->current_it->first), it->GetMemoryResource()); + }, + it->graph->impl); + it->current.emplace(propToName, mgp_value(it->current_it->second, it->GetMemoryResource())); it->property.name = it->current->first.c_str(); it->property.value = &it->current->second; clean_up.Disable(); @@ -1519,7 +1567,9 @@ mgp_error mgp_properties_iterator_next(mgp_properties_iterator *it, mgp_property } mgp_error mgp_vertex_get_id(mgp_vertex *v, mgp_vertex_id *result) { - return WrapExceptions([v] { return mgp_vertex_id{.as_int = v->impl.Gid().AsInt()}; }, result); + return WrapExceptions( + [v] { return mgp_vertex_id{.as_int = std::visit([](auto &impl) { return impl.Gid().AsInt(); }, v->impl)}; }, + result); } mgp_error mgp_vertex_underlying_graph_is_mutable(mgp_vertex *v, int *result) { @@ -1592,7 +1642,8 @@ mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_nam #ifdef MG_ENTERPRISE if (memgraph::utils::license::global_license_checker.IsValidLicenseFast() && ctx && ctx->auth_checker && - !ctx->auth_checker->Has(v->impl, v->graph->view, memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE)) { + !ctx->auth_checker->Has(v->getImpl(), v->graph->view, + memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE)) { throw AuthorizationException{"Insufficient permissions for setting a property on vertex!"}; } #endif @@ -1600,8 +1651,12 @@ mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_nam throw ImmutableObjectException{"Cannot set a property on an immutable vertex!"}; } - const auto prop_key = v->graph->impl->NameToProperty(property_name); - const auto result = v->impl.SetProperty(prop_key, ToPropertyValue(*property_value)); + const auto prop_key = + std::visit([property_name](auto *impl) { return impl->NameToProperty(property_name); }, v->graph->impl); + + const auto result = std::visit( + [prop_key, property_value](auto &impl) { return impl.SetProperty(prop_key, ToPropertyValue(*property_value)); }, + v->impl); if (result.HasError()) { switch (result.GetError()) { case memgraph::storage::Error::DELETED_OBJECT: @@ -1625,23 +1680,24 @@ mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_nam } const auto old_value = memgraph::query::TypedValue(*result); if (property_value->type == mgp_value_type::MGP_VALUE_TYPE_NULL) { - trigger_ctx_collector->RegisterRemovedObjectProperty(v->impl, prop_key, old_value); + trigger_ctx_collector->RegisterRemovedObjectProperty(v->getImpl(), prop_key, old_value); return; } const auto new_value = ToTypedValue(*property_value, property_value->memory); - trigger_ctx_collector->RegisterSetObjectProperty(v->impl, prop_key, old_value, new_value); + trigger_ctx_collector->RegisterSetObjectProperty(v->getImpl(), prop_key, old_value, new_value); }); } mgp_error mgp_vertex_add_label(struct mgp_vertex *v, mgp_label label) { return WrapExceptions([=] { auto *ctx = v->graph->ctx; + const auto label_id = std::visit([label](auto *impl) { return impl->NameToLabel(label.name); }, v->graph->impl); #ifdef MG_ENTERPRISE if (memgraph::utils::license::global_license_checker.IsValidLicenseFast() && ctx && ctx->auth_checker && - !(ctx->auth_checker->Has(v->impl, v->graph->view, memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE) && - ctx->auth_checker->Has({v->graph->impl->NameToLabel(label.name)}, - memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE))) { + !(ctx->auth_checker->Has(v->getImpl(), v->graph->view, + memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE) && + ctx->auth_checker->Has({label_id}, memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE))) { throw AuthorizationException{"Insufficient permissions for adding a label to vertex!"}; } #endif @@ -1649,8 +1705,8 @@ mgp_error mgp_vertex_add_label(struct mgp_vertex *v, mgp_label label) { if (!MgpVertexIsMutable(*v)) { throw ImmutableObjectException{"Cannot add a label to an immutable vertex!"}; } - const auto label_id = v->graph->impl->NameToLabel(label.name); - const auto result = v->impl.AddLabel(label_id); + + const auto result = std::visit([label_id](auto &impl) { return impl.AddLabel(label_id); }, v->impl); if (result.HasError()) { switch (result.GetError()) { @@ -1669,7 +1725,7 @@ mgp_error mgp_vertex_add_label(struct mgp_vertex *v, mgp_label label) { ctx->execution_stats[memgraph::query::ExecutionStats::Key::CREATED_LABELS] += 1; if (ctx->trigger_context_collector) { - ctx->trigger_context_collector->RegisterSetVertexLabel(v->impl, label_id); + ctx->trigger_context_collector->RegisterSetVertexLabel(v->getImpl(), label_id); } }); } @@ -1677,20 +1733,20 @@ mgp_error mgp_vertex_add_label(struct mgp_vertex *v, mgp_label label) { mgp_error mgp_vertex_remove_label(struct mgp_vertex *v, mgp_label label) { return WrapExceptions([=] { auto *ctx = v->graph->ctx; + const auto label_id = std::visit([&label](auto *impl) { return impl->NameToLabel(label.name); }, v->graph->impl); #ifdef MG_ENTERPRISE if (memgraph::utils::license::global_license_checker.IsValidLicenseFast() && ctx && ctx->auth_checker && - !(ctx->auth_checker->Has(v->impl, v->graph->view, memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE) && - ctx->auth_checker->Has({v->graph->impl->NameToLabel(label.name)}, - memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE))) { + !(ctx->auth_checker->Has(v->getImpl(), v->graph->view, + memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE) && + ctx->auth_checker->Has({label_id}, memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE))) { throw AuthorizationException{"Insufficient permissions for removing a label from vertex!"}; } #endif if (!MgpVertexIsMutable(*v)) { throw ImmutableObjectException{"Cannot remove a label from an immutable vertex!"}; } - const auto label_id = v->graph->impl->NameToLabel(label.name); - const auto result = v->impl.RemoveLabel(label_id); + const auto result = std::visit([label_id](auto &impl) { return impl.RemoveLabel(label_id); }, v->impl); if (result.HasError()) { switch (result.GetError()) { @@ -1709,7 +1765,7 @@ mgp_error mgp_vertex_remove_label(struct mgp_vertex *v, mgp_label label) { ctx->execution_stats[memgraph::query::ExecutionStats::Key::DELETED_LABELS] += 1; if (ctx->trigger_context_collector) { - ctx->trigger_context_collector->RegisterRemovedVertexLabel(v->impl, label_id); + ctx->trigger_context_collector->RegisterRemovedVertexLabel(v->getImpl(), label_id); } }); } @@ -1730,7 +1786,7 @@ mgp_error mgp_vertex_equal(mgp_vertex *v1, mgp_vertex *v2, int *result) { mgp_error mgp_vertex_labels_count(mgp_vertex *v, size_t *result) { return WrapExceptions( [v]() -> size_t { - auto maybe_labels = v->impl.Labels(v->graph->view); + auto maybe_labels = std::visit([v](const auto &impl) { return impl.Labels(v->graph->view); }, v->impl); if (maybe_labels.HasError()) { switch (maybe_labels.GetError()) { case memgraph::storage::Error::DELETED_OBJECT: @@ -1752,7 +1808,7 @@ mgp_error mgp_vertex_label_at(mgp_vertex *v, size_t i, mgp_label *result) { return WrapExceptions( [v, i]() -> const char * { // TODO: Maybe it's worth caching this in mgp_vertex. - auto maybe_labels = v->impl.Labels(v->graph->view); + auto maybe_labels = std::visit([v](const auto &impl) { return impl.Labels(v->graph->view); }, v->impl); if (maybe_labels.HasError()) { switch (maybe_labels.GetError()) { case memgraph::storage::Error::DELETED_OBJECT: @@ -1769,10 +1825,12 @@ mgp_error mgp_vertex_label_at(mgp_vertex *v, size_t i, mgp_label *result) { throw std::out_of_range("Label cannot be retrieved, because index exceeds the number of labels!"); } const auto &label = (*maybe_labels)[i]; - static_assert(std::is_lvalue_reference_v<decltype(v->graph->impl->LabelToName(label))>, + static_assert(std::is_lvalue_reference_v< + decltype(std::get<memgraph::query::DbAccessor *>(v->graph->impl)->LabelToName(label))>, "Expected LabelToName to return a pointer or reference, so we " "don't have to take a copy and manage memory."); - const auto &name = v->graph->impl->LabelToName(label); + + const auto &name = std::visit([label](const auto *impl) { return impl->LabelToName(label); }, v->graph->impl); return name.c_str(); }, &result->name); @@ -1782,9 +1840,10 @@ mgp_error mgp_vertex_has_label_named(mgp_vertex *v, const char *name, int *resul return WrapExceptions( [v, name] { memgraph::storage::LabelId label; - label = v->graph->impl->NameToLabel(name); + label = std::visit([name](auto *impl) { return impl->NameToLabel(name); }, v->graph->impl); - auto maybe_has_label = v->impl.HasLabel(v->graph->view, label); + auto maybe_has_label = + std::visit([v, label](auto &impl) { return impl.HasLabel(v->graph->view, label); }, v->impl); if (maybe_has_label.HasError()) { switch (maybe_has_label.GetError()) { case memgraph::storage::Error::DELETED_OBJECT: @@ -1812,8 +1871,9 @@ mgp_error mgp_vertex_has_label(mgp_vertex *v, mgp_label label, int *result) { mgp_error mgp_vertex_get_property(mgp_vertex *v, const char *name, mgp_memory *memory, mgp_value **result) { return WrapExceptions( [v, name, memory]() -> mgp_value * { - const auto &key = v->graph->impl->NameToProperty(name); - auto maybe_prop = v->impl.GetProperty(v->graph->view, key); + const auto &key = std::visit([name](auto *impl) { return impl->NameToProperty(name); }, v->graph->impl); + + auto maybe_prop = std::visit([v, key](auto &impl) { return impl.GetProperty(v->graph->view, key); }, v->impl); if (maybe_prop.HasError()) { switch (maybe_prop.GetError()) { case memgraph::storage::Error::DELETED_OBJECT: @@ -1839,7 +1899,7 @@ mgp_error mgp_vertex_iter_properties(mgp_vertex *v, mgp_memory *memory, mgp_prop // will probably require a different API in storage. return WrapExceptions( [v, memory] { - auto maybe_props = v->impl.Properties(v->graph->view); + auto maybe_props = std::visit([v](auto &impl) { return impl.Properties(v->graph->view); }, v->impl); if (maybe_props.HasError()) { switch (maybe_props.GetError()) { case memgraph::storage::Error::DELETED_OBJECT: @@ -1874,7 +1934,8 @@ void NextPermittedEdge(mgp_edges_iterator &it, const bool for_in) { const auto view = it.source_vertex.graph->view; while (*impl_it != end) { if (auth_checker->Has(**impl_it, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)) { - const auto &check_vertex = it.source_vertex.impl == (*impl_it)->From() ? (*impl_it)->To() : (*impl_it)->From(); + const auto &check_vertex = + it.source_vertex.getImpl() == (*impl_it)->From() ? (*impl_it)->To() : (*impl_it)->From(); if (auth_checker->Has(check_vertex, view, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)) { break; } @@ -1893,7 +1954,7 @@ mgp_error mgp_vertex_iter_in_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges_ auto it = NewMgpObject<mgp_edges_iterator>(memory, *v); MG_ASSERT(it != nullptr); - auto maybe_edges = v->impl.InEdges(v->graph->view); + auto maybe_edges = std::visit([v](auto &impl) { return impl.InEdges(v->graph->view); }, v->impl); if (maybe_edges.HasError()) { switch (maybe_edges.GetError()) { case memgraph::storage::Error::DELETED_OBJECT: @@ -1917,7 +1978,19 @@ mgp_error mgp_vertex_iter_in_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges_ #endif if (*it->in_it != it->in->end()) { - it->current_e.emplace(**it->in_it, v->graph, it->GetMemoryResource()); + std::visit(memgraph::utils::Overloaded{ + [&](memgraph::query::DbAccessor *) { + it->current_e.emplace(**it->in_it, (**it->in_it).From(), (**it->in_it).To(), v->graph, + it->GetMemoryResource()); + }, + [&](memgraph::query::SubgraphDbAccessor *impl) { + it->current_e.emplace( + **it->in_it, + memgraph::query::SubgraphVertexAccessor((**it->in_it).From(), impl->getGraph()), + memgraph::query::SubgraphVertexAccessor((**it->in_it).To(), impl->getGraph()), v->graph, + it->GetMemoryResource()); + }}, + v->graph->impl); } return it.release(); @@ -1930,8 +2003,8 @@ mgp_error mgp_vertex_iter_out_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges [v, memory] { auto it = NewMgpObject<mgp_edges_iterator>(memory, *v); MG_ASSERT(it != nullptr); + auto maybe_edges = std::visit([v](auto &impl) { return impl.OutEdges(v->graph->view); }, v->impl); - auto maybe_edges = v->impl.OutEdges(v->graph->view); if (maybe_edges.HasError()) { switch (maybe_edges.GetError()) { case memgraph::storage::Error::DELETED_OBJECT: @@ -1946,6 +2019,7 @@ mgp_error mgp_vertex_iter_out_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges LOG_FATAL("Unexpected error when getting the outbound edges of a vertex."); } } + it->out.emplace(std::move(*maybe_edges)); it->out_it.emplace(it->out->begin()); @@ -1956,7 +2030,19 @@ mgp_error mgp_vertex_iter_out_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges #endif if (*it->out_it != it->out->end()) { - it->current_e.emplace(**it->out_it, v->graph, it->GetMemoryResource()); + std::visit(memgraph::utils::Overloaded{ + [&](memgraph::query::DbAccessor *) { + it->current_e.emplace(**it->out_it, (**it->out_it).From(), (**it->out_it).To(), v->graph, + it->GetMemoryResource()); + }, + [&](memgraph::query::SubgraphDbAccessor *impl) { + it->current_e.emplace( + **it->out_it, + memgraph::query::SubgraphVertexAccessor((**it->out_it).From(), impl->getGraph()), + memgraph::query::SubgraphVertexAccessor((**it->out_it).To(), impl->getGraph()), v->graph, + it->GetMemoryResource()); + }}, + v->graph->impl); } return it.release(); @@ -2005,8 +2091,19 @@ mgp_error mgp_edges_iterator_next(mgp_edges_iterator *it, mgp_edge **result) { it->current_e = std::nullopt; return nullptr; } + std::visit(memgraph::utils::Overloaded{ + [&](memgraph::query::DbAccessor *) { + it->current_e.emplace(**impl_it, (**impl_it).From(), (**impl_it).To(), + it->source_vertex.graph, it->GetMemoryResource()); + }, + [&](memgraph::query::SubgraphDbAccessor *impl) { + it->current_e.emplace( + **impl_it, memgraph::query::SubgraphVertexAccessor((**impl_it).From(), impl->getGraph()), + memgraph::query::SubgraphVertexAccessor((**impl_it).To(), impl->getGraph()), + it->source_vertex.graph, it->GetMemoryResource()); + }}, + it->source_vertex.graph->impl); - it->current_e.emplace(**impl_it, it->source_vertex.graph, it->GetMemoryResource()); return &*it->current_e; }; if (it->in_it) { @@ -2044,10 +2141,8 @@ mgp_error mgp_edge_equal(mgp_edge *e1, mgp_edge *e2, int *result) { mgp_error mgp_edge_get_type(mgp_edge *e, mgp_edge_type *result) { return WrapExceptions( [e] { - const auto &name = e->from.graph->impl->EdgeTypeToName(e->impl.EdgeType()); - static_assert(std::is_lvalue_reference_v<decltype(e->from.graph->impl->EdgeTypeToName(e->impl.EdgeType()))>, - "Expected EdgeTypeToName to return a pointer or reference, so we " - "don't have to take a copy and manage memory."); + const auto &name = + std::visit([e](const auto *impl) { return impl->EdgeTypeToName(e->impl.EdgeType()); }, e->from.graph->impl); return name.c_str(); }, &result->name); @@ -2066,7 +2161,7 @@ mgp_error mgp_edge_get_to(mgp_edge *e, mgp_vertex **result) { mgp_error mgp_edge_get_property(mgp_edge *e, const char *name, mgp_memory *memory, mgp_value **result) { return WrapExceptions( [e, name, memory] { - const auto &key = e->from.graph->impl->NameToProperty(name); + const auto &key = std::visit([name](auto *impl) { return impl->NameToProperty(name); }, e->from.graph->impl); auto view = e->from.graph->view; auto maybe_prop = e->impl.GetProperty(view, key); if (maybe_prop.HasError()) { @@ -2101,7 +2196,8 @@ mgp_error mgp_edge_set_property(struct mgp_edge *e, const char *property_name, m if (!MgpEdgeIsMutable(*e)) { throw ImmutableObjectException{"Cannot set a property on an immutable edge!"}; } - const auto prop_key = e->from.graph->impl->NameToProperty(property_name); + const auto prop_key = + std::visit([property_name](auto *impl) { return impl->NameToProperty(property_name); }, e->from.graph->impl); const auto result = e->impl.SetProperty(prop_key, ToPropertyValue(*property_value)); if (result.HasError()) { @@ -2167,9 +2263,22 @@ mgp_error mgp_edge_iter_properties(mgp_edge *e, mgp_memory *memory, mgp_properti mgp_error mgp_graph_get_vertex_by_id(mgp_graph *graph, mgp_vertex_id id, mgp_memory *memory, mgp_vertex **result) { return WrapExceptions( [graph, id, memory]() -> mgp_vertex * { - auto maybe_vertex = graph->impl->FindVertex(memgraph::storage::Gid::FromInt(id.as_int), graph->view); + std::optional<memgraph::query::VertexAccessor> maybe_vertex = std::visit( + [graph, id](auto *impl) { + return impl->FindVertex(memgraph::storage::Gid::FromInt(id.as_int), graph->view); + }, + graph->impl); if (maybe_vertex) { - return NewRawMgpObject<mgp_vertex>(memory, *maybe_vertex, graph); + return std::visit(memgraph::utils::Overloaded{ + [memory, graph, maybe_vertex](memgraph::query::DbAccessor *) { + return NewRawMgpObject<mgp_vertex>(memory, *maybe_vertex, graph); + }, + [memory, graph, maybe_vertex](memgraph::query::SubgraphDbAccessor *impl) { + return NewRawMgpObject<mgp_vertex>( + memory, memgraph::query::SubgraphVertexAccessor(*maybe_vertex, impl->getGraph()), + graph); + }}, + graph->impl); } return nullptr; }, @@ -2197,15 +2306,16 @@ mgp_error mgp_graph_create_vertex(struct mgp_graph *graph, mgp_memory *memory, m if (!MgpGraphIsMutable(*graph)) { throw ImmutableObjectException{"Cannot create a vertex in an immutable graph!"}; } - auto vertex = graph->impl->InsertVertex(); + auto *vertex = std::visit( + [=](auto *impl) { return NewRawMgpObject<mgp_vertex>(memory, impl->InsertVertex(), graph); }, graph->impl); auto &ctx = graph->ctx; ctx->execution_stats[memgraph::query::ExecutionStats::Key::CREATED_NODES] += 1; if (ctx->trigger_context_collector) { - ctx->trigger_context_collector->RegisterCreatedObject(vertex); + ctx->trigger_context_collector->RegisterCreatedObject(vertex->getImpl()); } - return NewRawMgpObject<mgp_vertex>(memory, vertex, graph); + return vertex; }, result); } @@ -2216,7 +2326,7 @@ mgp_error mgp_graph_delete_vertex(struct mgp_graph *graph, mgp_vertex *vertex) { #ifdef MG_ENTERPRISE if (memgraph::utils::license::global_license_checker.IsValidLicenseFast() && ctx && ctx->auth_checker && - !ctx->auth_checker->Has(vertex->impl, graph->view, + !ctx->auth_checker->Has(vertex->getImpl(), graph->view, memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) { throw AuthorizationException{"Insufficient permissions for deleting a vertex!"}; } @@ -2225,7 +2335,16 @@ mgp_error mgp_graph_delete_vertex(struct mgp_graph *graph, mgp_vertex *vertex) { if (!MgpGraphIsMutable(*graph)) { throw ImmutableObjectException{"Cannot remove a vertex from an immutable graph!"}; } - const auto result = graph->impl->RemoveVertex(&vertex->impl); + + const auto result = + std::visit(memgraph::utils::Overloaded{ + [&](memgraph::query::DbAccessor *impl) { + return impl->RemoveVertex(&std::get<memgraph::query::VertexAccessor>(vertex->impl)); + }, + [&](memgraph::query::SubgraphDbAccessor *impl) { + return impl->RemoveVertex(&(std::get<memgraph::query::SubgraphVertexAccessor>(vertex->impl))); + }}, + graph->impl); if (result.HasError()) { switch (result.GetError()) { @@ -2258,7 +2377,7 @@ mgp_error mgp_graph_detach_delete_vertex(struct mgp_graph *graph, mgp_vertex *ve auto *ctx = graph->ctx; #ifdef MG_ENTERPRISE if (memgraph::utils::license::global_license_checker.IsValidLicenseFast() && ctx && ctx->auth_checker && - !ctx->auth_checker->Has(vertex->impl, graph->view, + !ctx->auth_checker->Has(vertex->getImpl(), graph->view, memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) { throw AuthorizationException{"Insufficient permissions for deleting a vertex!"}; } @@ -2267,7 +2386,15 @@ mgp_error mgp_graph_detach_delete_vertex(struct mgp_graph *graph, mgp_vertex *ve if (!MgpGraphIsMutable(*graph)) { throw ImmutableObjectException{"Cannot remove a vertex from an immutable graph!"}; } - const auto result = graph->impl->DetachRemoveVertex(&vertex->impl); + const auto result = std::visit( + memgraph::utils::Overloaded{ + [vertex](memgraph::query::DbAccessor *impl) { + return impl->DetachRemoveVertex(&std::get<memgraph::query::VertexAccessor>(vertex->impl)); + }, + [vertex](memgraph::query::SubgraphDbAccessor *impl) { + return impl->DetachRemoveVertex(&std::get<memgraph::query::SubgraphVertexAccessor>(vertex->impl)); + }}, + graph->impl); if (result.HasError()) { switch (result.GetError()) { @@ -2311,17 +2438,30 @@ mgp_error mgp_graph_create_edge(mgp_graph *graph, mgp_vertex *from, mgp_vertex * [=]() -> mgp_edge * { auto *ctx = graph->ctx; #ifdef MG_ENTERPRISE + const auto edge_id = + std::visit([type](auto *impl) { return impl->NameToEdgeType(type.name); }, from->graph->impl); if (memgraph::utils::license::global_license_checker.IsValidLicenseFast() && ctx && ctx->auth_checker && - !ctx->auth_checker->Has(from->graph->impl->NameToEdgeType(type.name), - memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) { + !ctx->auth_checker->Has(edge_id, memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) { throw AuthorizationException{"Insufficient permissions for creating edges!"}; } #endif if (!MgpGraphIsMutable(*graph)) { throw ImmutableObjectException{"Cannot create an edge in an immutable graph!"}; } + auto edge = + std::visit(memgraph::utils::Overloaded{ + [from, to, type](memgraph::query::DbAccessor *impl) { + return impl->InsertEdge(&std::get<memgraph::query::VertexAccessor>(from->impl), + &std::get<memgraph::query::VertexAccessor>(to->impl), + impl->NameToEdgeType(type.name)); + }, + [from, to, type](memgraph::query::SubgraphDbAccessor *impl) { + return impl->InsertEdge(&std::get<memgraph::query::SubgraphVertexAccessor>(from->impl), + &std::get<memgraph::query::SubgraphVertexAccessor>(to->impl), + impl->NameToEdgeType(type.name)); + }}, + graph->impl); - auto edge = graph->impl->InsertEdge(&from->impl, &to->impl, from->graph->impl->NameToEdgeType(type.name)); if (edge.HasError()) { switch (edge.GetError()) { case memgraph::storage::Error::DELETED_OBJECT: @@ -2341,7 +2481,18 @@ mgp_error mgp_graph_create_edge(mgp_graph *graph, mgp_vertex *from, mgp_vertex * if (ctx->trigger_context_collector) { ctx->trigger_context_collector->RegisterCreatedObject(*edge); } - return NewRawMgpObject<mgp_edge>(memory, edge.GetValue(), from->graph); + return std::visit( + memgraph::utils::Overloaded{ + [memory, edge, from](memgraph::query::DbAccessor *) { + return NewRawMgpObject<mgp_edge>(memory->impl, edge.GetValue(), from->graph); + }, + [memory, edge, from](memgraph::query::SubgraphDbAccessor *db_impl) { + const auto &v_from = + memgraph::query::SubgraphVertexAccessor(edge.GetValue().From(), db_impl->getGraph()); + const auto &v_to = memgraph::query::SubgraphVertexAccessor(edge.GetValue().To(), db_impl->getGraph()); + return NewRawMgpObject<mgp_edge>(memory->impl, edge.GetValue(), v_from, v_to, from->graph); + }}, + graph->impl); }, result); } @@ -2358,8 +2509,8 @@ mgp_error mgp_graph_delete_edge(struct mgp_graph *graph, mgp_edge *edge) { if (!MgpGraphIsMutable(*graph)) { throw ImmutableObjectException{"Cannot remove an edge from an immutable graph!"}; } - const auto result = graph->impl->RemoveEdge(&edge->impl); + const auto result = std::visit([edge](auto *impl) { return impl->RemoveEdge(&edge->impl); }, graph->impl); if (result.HasError()) { switch (result.GetError()) { case memgraph::storage::Error::NONEXISTENT_OBJECT: @@ -2407,7 +2558,10 @@ void NextPermitted(mgp_vertices_iterator &it) { /// @throw anything VerticesIterable may throw mgp_vertices_iterator::mgp_vertices_iterator(mgp_graph *graph, memgraph::utils::MemoryResource *memory) - : memory(memory), graph(graph), vertices(graph->impl->Vertices(graph->view)), current_it(vertices.begin()) { + : memory(memory), + graph(graph), + vertices(std::visit([graph](auto *impl) { return impl->Vertices(graph->view); }, graph->impl)), + current_it(vertices.begin()) { #ifdef MG_ENTERPRISE if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) { NextPermitted(*this); @@ -2415,7 +2569,13 @@ mgp_vertices_iterator::mgp_vertices_iterator(mgp_graph *graph, memgraph::utils:: #endif if (current_it != vertices.end()) { - current_v.emplace(*current_it, graph, memory); + std::visit( + memgraph::utils::Overloaded{ + [this, graph, memory](memgraph::query::DbAccessor *) { current_v.emplace(*current_it, graph, memory); }, + [this, graph, memory](memgraph::query::SubgraphDbAccessor *impl) { + current_v.emplace(memgraph::query::SubgraphVertexAccessor(*current_it, impl->getGraph()), graph, memory); + }}, + graph->impl); } } @@ -2462,7 +2622,17 @@ mgp_error mgp_vertices_iterator_next(mgp_vertices_iterator *it, mgp_vertex **res } memgraph::utils::OnScopeExit clean_up([it] { it->current_v = std::nullopt; }); - it->current_v.emplace(*it->current_it, it->graph, it->GetMemoryResource()); + std::visit(memgraph::utils::Overloaded{[it](memgraph::query::DbAccessor *) { + it->current_v.emplace(*it->current_it, it->graph, + it->GetMemoryResource()); + }, + [it](memgraph::query::SubgraphDbAccessor *impl) { + it->current_v.emplace(memgraph::query::SubgraphVertexAccessor( + *it->current_it, impl->getGraph()), + it->graph, it->GetMemoryResource()); + }}, + it->graph->impl); + clean_up.Disable(); return &*it->current_v; }, @@ -2745,6 +2915,7 @@ std::ostream &PrintValue(const TypedValue &value, std::ostream *stream) { case TypedValue::Type::Vertex: case TypedValue::Type::Edge: case TypedValue::Type::Path: + case TypedValue::Type::Graph: LOG_FATAL("value must not be a graph element"); } } diff --git a/src/query/procedure/mg_procedure_impl.hpp b/src/query/procedure/mg_procedure_impl.hpp index 2b2f16b5d..4d2acb77d 100644 --- a/src/query/procedure/mg_procedure_impl.hpp +++ b/src/query/procedure/mg_procedure_impl.hpp @@ -32,6 +32,7 @@ #include "utils/pmr/string.hpp" #include "utils/pmr/vector.hpp" #include "utils/temporal.hpp" +#include "utils/variant_helpers.hpp" /// Wraps memory resource used in custom procedures. /// /// This should have been `using mgp_memory = memgraph::utils::MemoryResource`, but that's @@ -442,6 +443,10 @@ struct mgp_vertex { mgp_vertex(memgraph::query::VertexAccessor v, mgp_graph *graph, memgraph::utils::MemoryResource *memory) noexcept : memory(memory), impl(v), graph(graph) {} + mgp_vertex(memgraph::query::SubgraphVertexAccessor v, mgp_graph *graph, + memgraph::utils::MemoryResource *memory) noexcept + : memory(memory), impl(v), graph(graph) {} + mgp_vertex(const mgp_vertex &other, memgraph::utils::MemoryResource *memory) noexcept : memory(memory), impl(other.impl), graph(other.graph) {} @@ -450,13 +455,21 @@ struct mgp_vertex { mgp_vertex(mgp_vertex &&other) noexcept : memory(other.memory), impl(other.impl), graph(other.graph) {} + memgraph::query::VertexAccessor getImpl() const { + return std::visit( + memgraph::utils::Overloaded{[](memgraph::query::VertexAccessor impl) { return impl; }, + [](memgraph::query::SubgraphVertexAccessor impl) { return impl.impl_; }}, + this->impl); + } + /// Copy construction without memgraph::utils::MemoryResource is not allowed. mgp_vertex(const mgp_vertex &) = delete; mgp_vertex &operator=(const mgp_vertex &) = delete; mgp_vertex &operator=(mgp_vertex &&) = delete; - bool operator==(const mgp_vertex &other) const noexcept { return this->impl == other.impl; } + bool operator==(const mgp_vertex &other) const noexcept { return other.getImpl() == this->getImpl(); } + bool operator!=(const mgp_vertex &other) const noexcept { return !(*this == other); }; ~mgp_vertex() = default; @@ -464,7 +477,7 @@ struct mgp_vertex { memgraph::utils::MemoryResource *GetMemoryResource() const noexcept { return memory; } memgraph::utils::MemoryResource *memory; - memgraph::query::VertexAccessor impl; + std::variant<memgraph::query::VertexAccessor, memgraph::query::SubgraphVertexAccessor> impl; mgp_graph *graph; }; @@ -484,6 +497,16 @@ struct mgp_edge { memgraph::utils::MemoryResource *memory) noexcept : memory(memory), impl(impl), from(impl.From(), graph, memory), to(impl.To(), graph, memory) {} + mgp_edge(const memgraph::query::EdgeAccessor &impl, const memgraph::query::VertexAccessor &from_v, + const memgraph::query::VertexAccessor &to_v, mgp_graph *graph, + memgraph::utils::MemoryResource *memory) noexcept + : memory(memory), impl(impl), from(from_v, graph, memory), to(to_v, graph, memory) {} + + mgp_edge(const memgraph::query::EdgeAccessor &impl, const memgraph::query::SubgraphVertexAccessor &from_v, + const memgraph::query::SubgraphVertexAccessor &to_v, mgp_graph *graph, + memgraph::utils::MemoryResource *memory) noexcept + : memory(memory), impl(impl), from(from_v, graph, memory), to(to_v, graph, memory) {} + mgp_edge(const mgp_edge &other, memgraph::utils::MemoryResource *memory) noexcept : memory(memory), impl(other.impl), from(other.from, memory), to(other.to, memory) {} @@ -541,6 +564,32 @@ struct mgp_path { memgraph::utils::pmr::vector<mgp_edge> edges; }; +struct mgp_graph { + std::variant<memgraph::query::DbAccessor *, memgraph::query::SubgraphDbAccessor *> impl; + memgraph::storage::View view; + // TODO: Merge `mgp_graph` and `mgp_memory` into a single `mgp_context`. The + // `ctx` field is out of place here. + memgraph::query::ExecutionContext *ctx; + + static mgp_graph WritableGraph(memgraph::query::DbAccessor &acc, memgraph::storage::View view, + memgraph::query::ExecutionContext &ctx) { + return mgp_graph{&acc, view, &ctx}; + } + + static mgp_graph NonWritableGraph(memgraph::query::DbAccessor &acc, memgraph::storage::View view) { + return mgp_graph{&acc, view, nullptr}; + } + + static mgp_graph WritableGraph(memgraph::query::SubgraphDbAccessor &acc, memgraph::storage::View view, + memgraph::query::ExecutionContext &ctx) { + return mgp_graph{&acc, view, &ctx}; + } + + static mgp_graph NonWritableGraph(memgraph::query::SubgraphDbAccessor &acc, memgraph::storage::View view) { + return mgp_graph{&acc, view, nullptr}; + } +}; + struct mgp_result_record { /// Result record signature as defined for mgp_proc. const memgraph::utils::pmr::map<memgraph::utils::pmr::string, @@ -570,23 +619,6 @@ struct mgp_func_result { std::optional<memgraph::utils::pmr::string> error_msg; }; -struct mgp_graph { - memgraph::query::DbAccessor *impl; - memgraph::storage::View view; - // TODO: Merge `mgp_graph` and `mgp_memory` into a single `mgp_context`. The - // `ctx` field is out of place here. - memgraph::query::ExecutionContext *ctx; - - static mgp_graph WritableGraph(memgraph::query::DbAccessor &acc, memgraph::storage::View view, - memgraph::query::ExecutionContext &ctx) { - return mgp_graph{&acc, view, &ctx}; - } - - static mgp_graph NonWritableGraph(memgraph::query::DbAccessor &acc, memgraph::storage::View view) { - return mgp_graph{&acc, view, nullptr}; - } -}; - // Prevents user to use ExecutionContext in writable callables struct mgp_func_context { memgraph::query::DbAccessor *impl; @@ -615,8 +647,13 @@ struct mgp_properties_iterator { mgp_properties_iterator(mgp_graph *graph, decltype(pvs) pvs, memgraph::utils::MemoryResource *memory) : memory(memory), graph(graph), pvs(std::move(pvs)), current_it(this->pvs.begin()) { if (current_it != this->pvs.end()) { - current.emplace(memgraph::utils::pmr::string(graph->impl->PropertyToName(current_it->first), memory), - mgp_value(current_it->second, memory)); + auto value = std::visit( + [this, memory](const auto *impl) { + return memgraph::utils::pmr::string(impl->PropertyToName(current_it->first), memory); + }, + graph->impl); + + current.emplace(value, mgp_value(current_it->second, memory)); property.name = current->first.c_str(); property.value = ¤t->second; } @@ -635,7 +672,6 @@ struct mgp_properties_iterator { struct mgp_edges_iterator { using allocator_type = memgraph::utils::Allocator<mgp_edges_iterator>; - // Hopefully mgp_vertex copy constructor remains noexcept, so that we can // have everything noexcept here. static_assert(std::is_nothrow_constructible_v<mgp_vertex, const mgp_vertex &, memgraph::utils::MemoryResource *>); @@ -662,9 +698,14 @@ struct mgp_edges_iterator { memgraph::utils::MemoryResource *memory; mgp_vertex source_vertex; - std::optional<std::remove_reference_t<decltype(*source_vertex.impl.InEdges(source_vertex.graph->view))>> in; + + std::optional<std::remove_reference_t< + decltype(*std::get<memgraph::query::VertexAccessor>(source_vertex.impl).InEdges(source_vertex.graph->view))>> + in; std::optional<decltype(in->begin())> in_it; - std::optional<std::remove_reference_t<decltype(*source_vertex.impl.OutEdges(source_vertex.graph->view))>> out; + std::optional<std::remove_reference_t< + decltype(*std::get<memgraph::query::VertexAccessor>(source_vertex.impl).OutEdges(source_vertex.graph->view))>> + out; std::optional<decltype(out->begin())> out_it; std::optional<mgp_edge> current_e; }; @@ -679,7 +720,7 @@ struct mgp_vertices_iterator { memgraph::utils::MemoryResource *memory; mgp_graph *graph; - decltype(graph->impl->Vertices(graph->view)) vertices; + memgraph::query::VerticesIterable vertices; decltype(vertices.begin()) current_it; std::optional<mgp_vertex> current_v; }; diff --git a/src/query/typed_value.cpp b/src/query/typed_value.cpp index 1b917d297..6719f52f6 100644 --- a/src/query/typed_value.cpp +++ b/src/query/typed_value.cpp @@ -214,6 +214,9 @@ TypedValue::TypedValue(const TypedValue &other, utils::MemoryResource *memory) : case Type::Duration: new (&duration_v) utils::Duration(other.duration_v); return; + case Type::Graph: + new (&graph_v) Graph(other.graph_v, memory_); + return; } LOG_FATAL("Unsupported TypedValue::Type"); } @@ -263,6 +266,8 @@ TypedValue::TypedValue(TypedValue &&other, utils::MemoryResource *memory) : memo case Type::Duration: new (&duration_v) utils::Duration(other.duration_v); break; + case Type::Graph: + new (&graph_v) Graph(std::move(other.graph_v), memory_); } other.DestroyValue(); } @@ -331,6 +336,7 @@ DEFINE_VALUE_AND_TYPE_GETTERS(utils::Date, Date, date_v) DEFINE_VALUE_AND_TYPE_GETTERS(utils::LocalTime, LocalTime, local_time_v) DEFINE_VALUE_AND_TYPE_GETTERS(utils::LocalDateTime, LocalDateTime, local_date_time_v) DEFINE_VALUE_AND_TYPE_GETTERS(utils::Duration, Duration, duration_v) +DEFINE_VALUE_AND_TYPE_GETTERS(Graph, Graph, graph_v) #undef DEFINE_VALUE_AND_TYPE_GETTERS @@ -387,6 +393,8 @@ std::ostream &operator<<(std::ostream &os, const TypedValue::Type &type) { return os << "local_date_time"; case TypedValue::Type::Duration: return os << "duration"; + case TypedValue::Type::Graph: + return os << "graph"; } LOG_FATAL("Unsupported TypedValue::Type"); } @@ -522,6 +530,9 @@ TypedValue &TypedValue::operator=(const TypedValue &other) { case TypedValue::Type::Path: new (&path_v) Path(other.path_v, memory_); return *this; + case TypedValue::Type::Graph: + new (&graph_v) Graph(other.graph_v, memory_); + return *this; case Type::Date: new (&date_v) utils::Date(other.date_v); return *this; @@ -593,6 +604,9 @@ TypedValue &TypedValue::operator=(TypedValue &&other) noexcept(false) { case Type::Duration: new (&duration_v) utils::Duration(other.duration_v); break; + case Type::Graph: + new (&graph_v) Graph(std::move(other.graph_v), memory_); + break; } other.DestroyValue(); } @@ -633,6 +647,9 @@ void TypedValue::DestroyValue() { case Type::LocalDateTime: case Type::Duration: break; + case Type::Graph: + graph_v.~Graph(); + break; } type_ = TypedValue::Type::Null; @@ -792,6 +809,8 @@ TypedValue operator==(const TypedValue &a, const TypedValue &b) { return TypedValue(a.ValueLocalDateTime() == b.ValueLocalDateTime(), a.GetMemoryResource()); case TypedValue::Type::Duration: return TypedValue(a.ValueDuration() == b.ValueDuration(), a.GetMemoryResource()); + case TypedValue::Type::Graph: + throw TypedValueException("Unsupported comparison operator"); default: LOG_FATAL("Unhandled comparison for types"); } @@ -1100,6 +1119,8 @@ size_t TypedValue::Hash::operator()(const TypedValue &value) const { case TypedValue::Type::Duration: return utils::DurationHash{}(value.ValueDuration()); break; + case TypedValue::Type::Graph: + throw TypedValueException("Unsupported hash function for Graph"); } LOG_FATAL("Unhandled TypedValue.type() in hash function"); } diff --git a/src/query/typed_value.hpp b/src/query/typed_value.hpp index e9d8a049e..b0eadf70e 100644 --- a/src/query/typed_value.hpp +++ b/src/query/typed_value.hpp @@ -21,6 +21,7 @@ #include <vector> #include "query/db_accessor.hpp" +#include "query/graph.hpp" #include "query/path.hpp" #include "utils/exceptions.hpp" #include "utils/memory.hpp" @@ -82,7 +83,8 @@ class TypedValue { Date, LocalTime, LocalDateTime, - Duration + Duration, + Graph }; // TypedValue at this exact moment of compilation is an incomplete type, and @@ -401,6 +403,22 @@ class TypedValue { new (&path_v) Path(std::move(path), memory_); } + /** + * Construct with the value of graph. + * utils::MemoryResource is obtained from graph. After the move, graph will be + * left empty. + */ + explicit TypedValue(Graph &&graph) noexcept : TypedValue(std::move(graph), graph.GetMemoryResource()) {} + + /** + * Construct with the value of graph and use the given MemoryResource. + * If `*graph.GetMemoryResource() != *memory`, this call will perform an + * element-wise move and graph is not guaranteed to be empty. + */ + TypedValue(Graph &&graph, utils::MemoryResource *memory) : memory_(memory), type_(Type::Graph) { + new (&graph_v) Graph(std::move(graph), memory_); + } + /** * Construct with the value of other. * Default utils::NewDeleteResource() is used for allocations. After the move, @@ -486,6 +504,7 @@ class TypedValue { DECLARE_VALUE_AND_TYPE_GETTERS(utils::LocalTime, LocalTime) DECLARE_VALUE_AND_TYPE_GETTERS(utils::LocalDateTime, LocalDateTime) DECLARE_VALUE_AND_TYPE_GETTERS(utils::Duration, Duration) + DECLARE_VALUE_AND_TYPE_GETTERS(Graph, Graph) #undef DECLARE_VALUE_AND_TYPE_GETTERS @@ -528,6 +547,7 @@ class TypedValue { utils::LocalTime local_time_v; utils::LocalDateTime local_date_time_v; utils::Duration duration_v; + Graph graph_v; }; /** diff --git a/tests/e2e/write_procedures/CMakeLists.txt b/tests/e2e/write_procedures/CMakeLists.txt index 72b640271..27a9a73e2 100644 --- a/tests/e2e/write_procedures/CMakeLists.txt +++ b/tests/e2e/write_procedures/CMakeLists.txt @@ -5,5 +5,6 @@ endfunction() copy_write_procedures_e2e_python_files(common.py) copy_write_procedures_e2e_python_files(conftest.py) copy_write_procedures_e2e_python_files(simple_write.py) +copy_write_procedures_e2e_python_files(read_subgraph.py) add_subdirectory(procedures) diff --git a/tests/e2e/write_procedures/procedures/read.py b/tests/e2e/write_procedures/procedures/read.py index b24138c38..50c89e325 100644 --- a/tests/e2e/write_procedures/procedures/read.py +++ b/tests/e2e/write_procedures/procedures/read.py @@ -22,6 +22,65 @@ def graph_is_mutable(ctx: mgp.ProcCtx) -> mgp.Record(mutable=bool): return mgp.Record(mutable=ctx.graph.is_mutable()) +@mgp.read_proc +def subgraph_empty(ctx: mgp.ProcCtx, arg1: mgp.Any, arg2: mgp.Any, arg3: mgp.Any = 2) -> mgp.Record(result=int): + return mgp.Record(result=1) + + +@mgp.read_proc +def subgraph_get_vertices(ctx: mgp.ProcCtx) -> mgp.Record(node=mgp.Vertex): + return [mgp.Record(node=vertex) for vertex in ctx.graph.vertices] + + +@mgp.read_proc +def subgraph_get_out_edges(ctx: mgp.ProcCtx, vertex: mgp.Vertex) -> mgp.Record(edge=mgp.Edge): + return [mgp.Record(edge=edge) for edge in vertex.out_edges] + + +@mgp.read_proc +def subgraph_get_in_edges(ctx: mgp.ProcCtx, vertex: mgp.Vertex) -> mgp.Record(edge=mgp.Edge): + return [mgp.Record(edge=edge) for edge in vertex.in_edges] + + +@mgp.read_proc +def subgraph_get_2_hop_edges(ctx: mgp.ProcCtx, vertex: mgp.Vertex) -> mgp.Record(edge=mgp.Edge): + out_edges = vertex.out_edges + records = [] + for edge in out_edges: + vertex = edge.to_vertex + properties = vertex.properties + print(properties) + records.extend([mgp.Record(edge=edge) for edge in edge.to_vertex.out_edges]) + return records + + +@mgp.read_proc +def subgraph_get_out_edges_vertex_id(ctx: mgp.ProcCtx, vertex: mgp.Vertex) -> mgp.Record(edge=mgp.Edge): + vertex = ctx.graph.get_vertex_by_id(vertex.id) + return [mgp.Record(edge=edge) for edge in vertex.out_edges] + + +@mgp.read_proc +def subgraph_get_path_vertices(ctx: mgp.ProcCtx, path: mgp.Path) -> mgp.Record(node=mgp.Vertex): + return [mgp.Record(node=node) for node in path.vertices] + + +@mgp.read_proc +def subgraph_get_path_edges(ctx: mgp.ProcCtx, path: mgp.Path) -> mgp.Record(edge=mgp.Edge): + return [mgp.Record(edge=edge) for edge in path.edges] + + +@mgp.read_proc +def subgraph_get_path_vertices_in_subgraph(ctx: mgp.ProcCtx, path: mgp.Path) -> mgp.Record(node=mgp.Vertex): + path_vertices = path.vertices + graph_vertices = ctx.graph.vertices + records = [] + for path_vertex in path_vertices: + if path_vertex in graph_vertices: + records.append(mgp.Record(node=path_vertex)) + return records + + @mgp.read_proc def log_message(ctx: mgp.ProcCtx, message: str) -> mgp.Record(success=bool): logger = mgp.Logger() diff --git a/tests/e2e/write_procedures/procedures/write.py b/tests/e2e/write_procedures/procedures/write.py index e180be40e..90308a615 100644 --- a/tests/e2e/write_procedures/procedures/write.py +++ b/tests/e2e/write_procedures/procedures/write.py @@ -35,13 +35,12 @@ def detach_delete_vertex(ctx: mgp.ProcCtx, v: mgp.Any) -> mgp.Record(): @mgp.write_proc -def create_edge(ctx: mgp.ProcCtx, from_vertex: mgp.Vertex, - to_vertex: mgp.Vertex, - edge_type: str) -> mgp.Record(e=mgp.Any): +def create_edge( + ctx: mgp.ProcCtx, from_vertex: mgp.Vertex, to_vertex: mgp.Vertex, edge_type: str +) -> mgp.Record(e=mgp.Any): e = None try: - e = ctx.graph.create_edge( - from_vertex, to_vertex, mgp.EdgeType(edge_type)) + e = ctx.graph.create_edge(from_vertex, to_vertex, mgp.EdgeType(edge_type)) except RuntimeError as ex: return mgp.Record(e=str(ex)) return mgp.Record(e=e) @@ -54,32 +53,60 @@ def delete_edge(ctx: mgp.ProcCtx, edge: mgp.Edge) -> mgp.Record(): @mgp.write_proc -def set_property(ctx: mgp.ProcCtx, object: mgp.Any, - name: str, value: mgp.Nullable[mgp.Any]) -> mgp.Record(): +def set_property(ctx: mgp.ProcCtx, object: mgp.Any, name: str, value: mgp.Nullable[mgp.Any]) -> mgp.Record(): object.properties.set(name, value) return mgp.Record() @mgp.write_proc -def add_label(ctx: mgp.ProcCtx, object: mgp.Any, - name: str) -> mgp.Record(o=mgp.Any): +def add_label(ctx: mgp.ProcCtx, object: mgp.Any, name: str) -> mgp.Record(o=mgp.Any): object.add_label(name) return mgp.Record(o=object) @mgp.write_proc -def remove_label(ctx: mgp.ProcCtx, object: mgp.Any, - name: str) -> mgp.Record(o=mgp.Any): +def remove_label(ctx: mgp.ProcCtx, object: mgp.Any, name: str) -> mgp.Record(o=mgp.Any): object.remove_label(name) return mgp.Record(o=object) @mgp.write_proc -def underlying_graph_is_mutable(ctx: mgp.ProcCtx, - object: mgp.Any) -> mgp.Record(mutable=bool): +def underlying_graph_is_mutable(ctx: mgp.ProcCtx, object: mgp.Any) -> mgp.Record(mutable=bool): return mgp.Record(mutable=object.underlying_graph_is_mutable()) @mgp.write_proc def graph_is_mutable(ctx: mgp.ProcCtx) -> mgp.Record(mutable=bool): return mgp.Record(mutable=ctx.graph.is_mutable()) + + +@mgp.write_proc +def subgraph_insert_vertex_get_vertices(ctx: mgp.ProcCtx) -> mgp.Record(node=mgp.Vertex): + ctx.graph.create_vertex() + return [mgp.Record(node=node) for node in ctx.graph.vertices] + + +@mgp.write_proc +def subgraph_insert_edge_get_vertex_out_edges( + ctx: mgp.ProcCtx, vertex1: mgp.Vertex, vertex2: mgp.Vertex +) -> mgp.Record(edge=mgp.Edge): + ctx.graph.create_edge(vertex1, vertex2, edge_type=mgp.EdgeType("EDGE_TYPE")) + return [mgp.Record(edge=edge) for edge in vertex1.out_edges] + + +@mgp.write_proc +def subgraph_remove_edge_get_vertex_out_edges(ctx: mgp.ProcCtx, edge: mgp.Edge) -> mgp.Record(edge=mgp.Edge): + from_vertex = edge.from_vertex + ctx.graph.delete_edge(edge) + return [mgp.Record(edge=edge) for edge in from_vertex.out_edges] + + +@mgp.write_proc +def subgraph_remove_vertex_and_out_edges_get_vertices( + ctx: mgp.ProcCtx, vertex: mgp.Vertex +) -> mgp.Record(node=mgp.Vertex): + out_edges = vertex.out_edges + for edge in out_edges: + ctx.graph.delete_edge(edge) + ctx.graph.delete_vertex(vertex) + return [mgp.Record(node=vertex) for vertex in ctx.graph.vertices] diff --git a/tests/e2e/write_procedures/read_subgraph.py b/tests/e2e/write_procedures/read_subgraph.py new file mode 100644 index 000000000..40f315050 --- /dev/null +++ b/tests/e2e/write_procedures/read_subgraph.py @@ -0,0 +1,319 @@ +# 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. + +import typing +import mgclient +import sys +import pytest +from common import execute_and_fetch_all, has_n_result_row + + +def create_subgraph(cursor): + execute_and_fetch_all(cursor, "CREATE (n:Person {id: 1});") + execute_and_fetch_all(cursor, "CREATE (n:Person {id: 2});") + execute_and_fetch_all(cursor, "CREATE (n:Person {id: 3});") + execute_and_fetch_all(cursor, "CREATE (n:Person {id: 4});") + execute_and_fetch_all(cursor, "CREATE (n:Team {id: 5});") + execute_and_fetch_all(cursor, "CREATE (n:Team {id: 6});") + execute_and_fetch_all(cursor, "MATCH (p:Person {id: 1}) MATCH (t:Team {id:5}) CREATE (p)-[:SUPPORTS]->(t);") + execute_and_fetch_all(cursor, "MATCH (p:Person {id: 1}) MATCH (t:Team {id:6}) CREATE (p)-[:SUPPORTS]->(t);") + execute_and_fetch_all(cursor, "MATCH (p:Person {id: 2}) MATCH (t:Team {id:6}) CREATE (p)-[:SUPPORTS]->(t);") + execute_and_fetch_all(cursor, "MATCH (p1:Person {id: 1}) MATCH (p2:Person {id:2}) CREATE (p1)-[:KNOWS]->(p2);") + execute_and_fetch_all(cursor, "MATCH (t1:Team {id: 5}) MATCH (t2:Team {id:6}) CREATE (t1)-[:IS_RIVAL_TO]->(t2);") + execute_and_fetch_all(cursor, "MATCH (p1:Person {id: 3}) MATCH (p2:Person {id:4}) CREATE (p1)-[:KNOWS]->(p2);") + + +def create_smaller_subgraph(cursor): + execute_and_fetch_all(cursor, "CREATE (n:Person {id: 1});") + execute_and_fetch_all(cursor, "CREATE (n:Person {id: 2});") + execute_and_fetch_all(cursor, "CREATE (n:Team {id: 5});") + execute_and_fetch_all(cursor, "CREATE (n:Team {id: 6});") + execute_and_fetch_all(cursor, "MATCH (p:Person {id: 1}) MATCH (t:Team {id:5}) CREATE (p)-[:SUPPORTS]->(t);") + execute_and_fetch_all(cursor, "MATCH (p:Person {id: 1}) MATCH (t:Team {id:6}) CREATE (p)-[:SUPPORTS]->(t);") + execute_and_fetch_all(cursor, "MATCH (p:Person {id: 2}) MATCH (t:Team {id:6}) CREATE (p)-[:SUPPORTS]->(t);") + + +def test_is_callable(connection): + cursor = connection.cursor() + execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;") + create_subgraph(cursor) + + assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6) + assert has_n_result_row( + cursor, + f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) AS graph CALL read.subgraph_empty(graph, 2, 3) YIELD result RETURN result;", + 1, + ) + + execute_and_fetch_all( + cursor, + f"MATCH (n) DETACH DELETE n;", + ) + + +def test_incorrect_graph_argument_placement(connection): + cursor = connection.cursor() + execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;") + create_subgraph(cursor) + + assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6) + queries = [ + f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) AS graph CALL read.subgraph_empty(2, graph, 3) YIELD result RETURN result;", + f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) AS graph CALL read.subgraph_empty(2, 3, graph) YIELD result RETURN result;", + ] + for query in queries: + with pytest.raises(mgclient.DatabaseError): + execute_and_fetch_all(cursor, query) + + execute_and_fetch_all( + cursor, + f"MATCH (n) DETACH DELETE n;", + ) + + +def test_get_vertices(connection): + cursor = connection.cursor() + execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;") + create_subgraph(cursor) + + assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6) + assert has_n_result_row( + cursor, + f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) AS graph CALL read.subgraph_get_vertices(graph) YIELD node RETURN node;", + 4, + ) + + execute_and_fetch_all( + cursor, + f"MATCH (n) DETACH DELETE n;", + ) + + +def test_get_out_edges(connection): + cursor = connection.cursor() + execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;") + create_subgraph(cursor) + + assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6) + assert has_n_result_row( + cursor, + f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) AS graph MATCH (n1:Person {{id:1}}) CALL read.subgraph_get_out_edges(graph, n1) YIELD edge RETURN edge;", + 2, + ) + + execute_and_fetch_all( + cursor, + f"MATCH (n) DETACH DELETE n;", + ) + + +def test_get_in_edges(connection): + cursor = connection.cursor() + execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;") + create_subgraph(cursor) + + assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6) + assert has_n_result_row( + cursor, + f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) AS graph MATCH (t1:Team {{id:6}}) CALL read.subgraph_get_in_edges(graph, t1) YIELD edge RETURN edge;", + 2, + ) + + execute_and_fetch_all( + cursor, + f"MATCH (n) DETACH DELETE n;", + ) + + +def test_get_2_hop_edges(connection): + cursor = connection.cursor() + execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;") + create_subgraph(cursor) + + assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6) + assert has_n_result_row( + cursor, + f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) AS graph MATCH (n1:Person {{id:1}}) CALL read.subgraph_get_2_hop_edges(graph, n1) YIELD edge RETURN edge;", + 0, + ) + execute_and_fetch_all( + cursor, + f"MATCH (n) DETACH DELETE n;", + ) + + +def test_get_out_edges_vertex_id(connection): + cursor = connection.cursor() + execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;") + create_subgraph(cursor=cursor) + + assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6) + assert has_n_result_row( + cursor, + f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) AS graph MATCH (n1:Person {{id:1}}) CALL read.subgraph_get_out_edges_vertex_id(graph, n1) YIELD edge RETURN edge;", + 2, + ) + + execute_and_fetch_all( + cursor, + f"MATCH (n) DETACH DELETE n;", + ) + + +def test_subgraph_get_path_vertices(connection): + cursor = connection.cursor() + execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;") + create_subgraph(cursor) + + assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6) + assert has_n_result_row( + cursor, + f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) as graph MATCH path=(a:Person {{id: 1}})-[:SUPPORTS]->(b:Team {{id:5}}) CALL read.subgraph_get_path_vertices(graph, path) YIELD node RETURN node;", + 2, + ) + execute_and_fetch_all( + cursor, + f"MATCH (n) DETACH DELETE n;", + ) + + +def test_subgraph_get_path_edges(connection): + cursor = connection.cursor() + execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;") + create_subgraph(cursor) + + assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6) + assert has_n_result_row( + cursor, + f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) as graph MATCH path=(:Person {{id: 1}})-[:SUPPORTS]->(:Team {{id:5}}) CALL read.subgraph_get_path_edges(graph, path) YIELD edge RETURN edge;", + 1, + ) + execute_and_fetch_all( + cursor, + f"MATCH (n) DETACH DELETE n;", + ) + + +def test_subgraph_get_path_vertices_in_subgraph(connection): + cursor = connection.cursor() + execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;") + create_subgraph(cursor) + assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6) + assert has_n_result_row( + cursor, + f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) as graph MATCH path=(:Person {{id: 1}})-[:SUPPORTS]->(:Team {{id:5}}) CALL read.subgraph_get_path_vertices_in_subgraph(graph, path) YIELD node RETURN node;", + 2, + ) + execute_and_fetch_all( + cursor, + f"MATCH (n) DETACH DELETE n;", + ) + + +def test_subgraph_insert_vertex_get_vertices(connection): + cursor = connection.cursor() + execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;") + create_subgraph(cursor) + assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6) + assert has_n_result_row( + cursor, + f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) as graph CALL write.subgraph_insert_vertex_get_vertices(graph) YIELD node RETURN node;", + 5, + ) + execute_and_fetch_all( + cursor, + f"MATCH (n) DETACH DELETE n;", + ) + + +def test_subgraph_insert_edge_get_vertex_out_edges(connection): + cursor = connection.cursor() + execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;") + create_subgraph(cursor) + assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6) + assert has_n_result_row( + cursor, + f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) as graph MATCH (p1:Person {{id:2}}) MATCH (t1:Team {{id:6}}) CALL write.subgraph_insert_edge_get_vertex_out_edges(graph, p1, t1) YIELD edge RETURN edge;", + 2, + ) + execute_and_fetch_all( + cursor, + f"MATCH (n) DETACH DELETE n;", + ) + + +def test_subgraph_create_edge_both_vertices_not_in_projected_graph_error(connection): + cursor = connection.cursor() + execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;") + create_subgraph(cursor) + assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6) + with pytest.raises(mgclient.DatabaseError): + execute_and_fetch_all( + cursor, + f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) as graph MATCH (p1:Person {{id:2}}) MATCH (p2:Person {{id:4}}) CALL write.subgraph_insert_edge_get_vertex_out_edges(graph, p1, p2) YIELD edge RETURN edge;", + ) + + execute_and_fetch_all( + cursor, + f"MATCH (n) DETACH DELETE n;", + ) + + +def test_subgraph_remove_edge_get_vertex_out_edges(connection): + cursor = connection.cursor() + execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;") + create_subgraph(cursor) + assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6) + assert has_n_result_row( + cursor, + f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) as graph MATCH (p1:Person {{id:1}})-[e:SUPPORTS]->(t1:Team {{id:5}}) CALL write.subgraph_remove_edge_get_vertex_out_edges(graph, e) YIELD edge RETURN edge;", + 1, + ) + execute_and_fetch_all( + cursor, + f"MATCH (n) DETACH DELETE n;", + ) + + +def test_subgraph_remove_edge_not_in_subgraph_error(connection): + cursor = connection.cursor() + execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;") + create_subgraph(cursor) + assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6) + with pytest.raises(mgclient.DatabaseError): + execute_and_fetch_all( + cursor, + f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) as graph MATCH (p1:Person {{id:1}})-[e:KNOWS]->(p2:Person {{id:2}}) CALL write.subgraph_remove_edge_get_vertex_out_edges(graph, e) YIELD edge RETURN edge;", + ) + execute_and_fetch_all( + cursor, + f"MATCH (n) DETACH DELETE n;", + ) + + +def test_subgraph_remove_vertex_and_out_edges_get_vertices(connection): + cursor = connection.cursor() + execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;") + create_smaller_subgraph(cursor) + assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 4) + assert has_n_result_row( + cursor, + f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) as graph MATCH (p1:Person {{id:1}}) CALL write.subgraph_remove_vertex_and_out_edges_get_vertices(graph, p1) YIELD node RETURN node;", + 3, + ) + execute_and_fetch_all( + cursor, + f"MATCH (n) DETACH DELETE n;", + ) + + +if __name__ == "__main__": + sys.exit(pytest.main([__file__, "-rA"])) diff --git a/tests/e2e/write_procedures/workloads.yaml b/tests/e2e/write_procedures/workloads.yaml index a6a57231f..667947920 100644 --- a/tests/e2e/write_procedures/workloads.yaml +++ b/tests/e2e/write_procedures/workloads.yaml @@ -12,3 +12,8 @@ workloads: proc: "tests/e2e/write_procedures/procedures/" args: ["write_procedures/simple_write.py"] <<: *template_cluster + - name: "Graph projection procedures" + binary: "tests/e2e/pytest_runner.sh" + proc: "tests/e2e/write_procedures/procedures/" + args: ["write_procedures/read_subgraph.py"] + <<: *template_cluster diff --git a/tests/gql_behave/tests/memgraph_V1/features/aggregations.feature b/tests/gql_behave/tests/memgraph_V1/features/aggregations.feature index 796f598eb..8fe6a47ad 100644 --- a/tests/gql_behave/tests/memgraph_V1/features/aggregations.feature +++ b/tests/gql_behave/tests/memgraph_V1/features/aggregations.feature @@ -344,3 +344,61 @@ Feature: Aggregations | count(n) < n.property | count(n.property) | count(n) | avg(n.property) | min(n.property) | max(n.property) | sum(n.property) | | false | 1 | 1 | 1.0 | 1 | 1 | 1 | | null | 0 | 1 | null | null | null | 0 | + + Scenario: Graph projection test 01: + Given an empty graph + And having executed + """ + CREATE (a{x: 1}), (b{x: 2}), (c{x: 3}), (d{x: 4}), (a)-[:X]->(b), (b)-[:X]->(c), (c)-[:X]->(a), (a)-[:B]->(d) + """ + When executing query: + """ + MATCH p=()-[:X]->() WITH project(p) as graph WITH graph.nodes as nodes UNWIND nodes as n RETURN n.x as x ORDER BY x DESC + """ + Then the result should be: + | x | + | 3 | + | 2 | + | 1 | + + Scenario: Graph projection test 02: + Given an empty graph + And having executed + """ + CREATE (a{x: 1}), (b{x: 2}), (c{x: 3}), (d{x: 4}), (a)-[:X]->(b), (b)-[:X]->(c), (c)-[:X]->(a), (a)-[:B]->(d) + """ + When executing query: + """ + MATCH p=()-[:Z]->() WITH project(p) as graph WITH graph.nodes as nodes UNWIND nodes as n RETURN n.x as x ORDER BY x DESC + """ + Then the result should be: + | x | + + Scenario: Graph projection test 03: + Given an empty graph + And having executed + """ + CREATE (a{x: 1}), (b{x: 2}), (c{x: 3}), (d{x: 4}), (a)-[:X {prop:1}]->(b), (b)-[:X {prop:2}]->(c), (c)-[:X {prop:3}]->(a), (a)-[:B {prop:4}]->(d) + """ + When executing query: + """ + MATCH p=()-[:X]->() WITH project(p) as graph WITH graph.edges as edges UNWIND edges as e RETURN e.prop as y ORDER BY y DESC + """ + Then the result should be: + | y | + | 3 | + | 2 | + | 1 | + + Scenario: Graph projection test 04: + Given an empty graph + And having executed + """ + CREATE (a{x: 1}), (b{x: 2}), (c{x: 3}), (d{x: 4}), (a)-[:X {prop:1}]->(b), (b)-[:X {prop:2}]->(c), (c)-[:X {prop:3}]->(a), (a)-[:B {prop:4}]->(d) + """ + When executing query: + """ + MATCH p=()-[:Z]->() WITH project(p) as graph WITH graph.edges as edges UNWIND edges as e RETURN e.prop as y ORDER BY y DESC + """ + Then the result should be: + | y | diff --git a/tests/unit/formatters.hpp b/tests/unit/formatters.hpp index a2c9df807..0b4d25908 100644 --- a/tests/unit/formatters.hpp +++ b/tests/unit/formatters.hpp @@ -136,6 +136,8 @@ inline std::string ToString(const memgraph::query::TypedValue &value, const TAcc case memgraph::query::TypedValue::Type::Duration: os << ToString(value.ValueDuration()); break; + case memgraph::query::TypedValue::Type::Graph: + throw std::logic_error{"Not implemented"}; } return os.str(); } diff --git a/tests/unit/query_procedures_mgp_graph.cpp b/tests/unit/query_procedures_mgp_graph.cpp index 9b00ec4f8..b2742e8f1 100644 --- a/tests/unit/query_procedures_mgp_graph.cpp +++ b/tests/unit/query_procedures_mgp_graph.cpp @@ -30,6 +30,7 @@ #include "storage_test_utils.hpp" #include "test_utils.hpp" #include "utils/memory.hpp" +#include "utils/variant_helpers.hpp" #define EXPECT_SUCCESS(...) EXPECT_EQ(__VA_ARGS__, mgp_error::MGP_ERROR_NO_ERROR) @@ -90,11 +91,21 @@ size_t CountMaybeIterables(TMaybeIterable &&maybe_iterable) { return std::distance(iterable.begin(), iterable.end()); } +; + void CheckEdgeCountBetween(const MgpVertexPtr &from, const MgpVertexPtr &to, const size_t number_of_edges_between) { - EXPECT_EQ(CountMaybeIterables(from->impl.InEdges(memgraph::storage::View::NEW)), 0); - EXPECT_EQ(CountMaybeIterables(from->impl.OutEdges(memgraph::storage::View::NEW)), number_of_edges_between); - EXPECT_EQ(CountMaybeIterables(to->impl.InEdges(memgraph::storage::View::NEW)), number_of_edges_between); - EXPECT_EQ(CountMaybeIterables(to->impl.OutEdges(memgraph::storage::View::NEW)), 0); + EXPECT_EQ( + CountMaybeIterables(std::visit([](auto impl) { return impl.InEdges(memgraph::storage::View::NEW); }, from->impl)), + 0); + EXPECT_EQ(CountMaybeIterables( + std::visit([](auto impl) { return impl.OutEdges(memgraph::storage::View::NEW); }, from->impl)), + number_of_edges_between); + EXPECT_EQ( + CountMaybeIterables(std::visit([](auto impl) { return impl.InEdges(memgraph::storage::View::NEW); }, to->impl)), + number_of_edges_between); + EXPECT_EQ( + CountMaybeIterables(std::visit([](auto impl) { return impl.OutEdges(memgraph::storage::View::NEW); }, to->impl)), + 0); } } // namespace