parent
9e8fb2516b
commit
f1fe77adfb
@ -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:
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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");
|
||||
|
148
src/query/db_accessor.cpp
Normal file
148
src/query/db_accessor.cpp
Normal file
@ -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
|
@ -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,23 +188,87 @@ 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_;
|
||||
|
||||
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 {
|
||||
storage::VerticesIterable iterable_;
|
||||
std::variant<storage::VerticesIterable, std::unordered_set<VertexAccessor, std::hash<VertexAccessor>,
|
||||
std::equal_to<void>, utils::Allocator<VertexAccessor>> *>
|
||||
iterable_;
|
||||
|
||||
public:
|
||||
class Iterator final {
|
||||
storage::VerticesIterable::Iterator it_;
|
||||
std::variant<storage::VerticesIterable::Iterator,
|
||||
std::unordered_set<VertexAccessor, std::hash<VertexAccessor>, std::equal_to<void>,
|
||||
utils::Allocator<VertexAccessor>>::iterator>
|
||||
it_;
|
||||
|
||||
public:
|
||||
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) {}
|
||||
|
||||
VertexAccessor operator*() const { return VertexAccessor(*it_); }
|
||||
VertexAccessor operator*() const {
|
||||
return std::visit([](auto it_) { return VertexAccessor(*it_); }, it_);
|
||||
}
|
||||
|
||||
Iterator &operator++() {
|
||||
++it_;
|
||||
std::visit([this](auto it_) { this->it_ = ++it_; }, it_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -211,12 +278,33 @@ class DbAccessor final {
|
||||
};
|
||||
|
||||
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 Iterator(iterable_.begin()); }
|
||||
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 Iterator(iterable_.end()); }
|
||||
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
|
||||
|
@ -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)];
|
||||
}
|
||||
|
||||
|
@ -2126,6 +2126,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) {
|
||||
|
73
src/query/graph.cpp
Normal file
73
src/query/graph.cpp
Normal file
@ -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
|
114
src/query/graph.hpp
Normal file
114
src/query/graph.hpp
Normal file
@ -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
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.");
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -31,6 +31,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"
|
||||
@ -2918,6 +2919,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
|
||||
@ -3080,7 +3083,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();
|
||||
@ -3119,6 +3121,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.");
|
||||
@ -3167,6 +3174,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.");
|
||||
@ -3203,6 +3215,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 {
|
||||
@ -4015,6 +4039,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,
|
||||
|
@ -38,6 +38,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)
|
||||
@ -290,6 +293,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
|
||||
@ -324,17 +329,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));
|
||||
}
|
||||
@ -454,12 +459,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: {
|
||||
@ -470,11 +494,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));
|
||||
@ -799,7 +836,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); }
|
||||
@ -1130,7 +1175,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;
|
||||
}
|
||||
@ -1497,9 +1542,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();
|
||||
@ -1509,7 +1557,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) {
|
||||
@ -1581,8 +1631,13 @@ mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_nam
|
||||
if (!MgpVertexIsMutable(*v)) {
|
||||
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:
|
||||
@ -1608,11 +1663,11 @@ 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);
|
||||
});
|
||||
}
|
||||
|
||||
@ -1621,8 +1676,9 @@ 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 label_id = std::visit([label](auto *impl) { return impl->NameToLabel(label.name); }, v->graph->impl);
|
||||
|
||||
const auto result = std::visit([label_id](auto &impl) { return impl.AddLabel(label_id); }, v->impl);
|
||||
|
||||
if (result.HasError()) {
|
||||
switch (result.GetError()) {
|
||||
@ -1643,7 +1699,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);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1653,8 +1709,8 @@ mgp_error mgp_vertex_remove_label(struct mgp_vertex *v, mgp_label label) {
|
||||
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 label_id = std::visit([&label](auto *impl) { return impl->NameToLabel(label.name); }, v->graph->impl);
|
||||
const auto result = std::visit([label_id](auto &impl) { return impl.RemoveLabel(label_id); }, v->impl);
|
||||
|
||||
if (result.HasError()) {
|
||||
switch (result.GetError()) {
|
||||
@ -1675,7 +1731,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);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1696,7 +1752,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:
|
||||
@ -1718,7 +1774,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:
|
||||
@ -1735,10 +1791,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);
|
||||
@ -1748,9 +1806,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:
|
||||
@ -1778,8 +1837,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:
|
||||
@ -1805,7 +1865,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:
|
||||
@ -1833,7 +1893,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:
|
||||
@ -1851,7 +1911,19 @@ mgp_error mgp_vertex_iter_in_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges_
|
||||
it->in.emplace(std::move(*maybe_edges));
|
||||
it->in_it.emplace(it->in->begin());
|
||||
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();
|
||||
@ -1864,8 +1936,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:
|
||||
@ -1880,10 +1952,23 @@ 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());
|
||||
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();
|
||||
@ -1921,7 +2006,19 @@ mgp_error mgp_edges_iterator_next(mgp_edges_iterator *it, mgp_edge **result) {
|
||||
it->current_e = std::nullopt;
|
||||
return nullptr;
|
||||
}
|
||||
it->current_e.emplace(**impl_it, it->source_vertex.graph, it->GetMemoryResource());
|
||||
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);
|
||||
|
||||
return &*it->current_e;
|
||||
};
|
||||
if (it->in_it) {
|
||||
@ -1956,10 +2053,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);
|
||||
@ -1978,7 +2073,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()) {
|
||||
@ -2004,7 +2099,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()) {
|
||||
@ -2071,9 +2167,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 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;
|
||||
},
|
||||
@ -2091,15 +2200,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);
|
||||
}
|
||||
@ -2109,7 +2219,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()) {
|
||||
@ -2144,7 +2263,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()) {
|
||||
@ -2191,8 +2318,20 @@ mgp_error mgp_graph_create_edge(mgp_graph *graph, mgp_vertex *from, mgp_vertex *
|
||||
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:
|
||||
@ -2213,7 +2352,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);
|
||||
}
|
||||
@ -2223,8 +2373,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:
|
||||
@ -2285,7 +2435,17 @@ mgp_error mgp_vertices_iterator_next(mgp_vertices_iterator *it, mgp_vertex **res
|
||||
return nullptr;
|
||||
}
|
||||
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;
|
||||
},
|
||||
@ -2568,6 +2728,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");
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
@ -674,9 +715,19 @@ struct mgp_vertices_iterator {
|
||||
|
||||
/// @throw anything VerticesIterable may throw
|
||||
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()) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -684,7 +735,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;
|
||||
};
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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]
|
||||
|
319
tests/e2e/write_procedures/read_subgraph.py
Normal file
319
tests/e2e/write_procedures/read_subgraph.py
Normal file
@ -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"]))
|
@ -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
|
||||
|
@ -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 |
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user