Compare commits
42 Commits
master
...
T0985-MG-g
Author | SHA1 | Date | |
---|---|---|---|
|
d201e69679 | ||
|
929e9433ea | ||
|
d5041c219e | ||
|
c55183bd97 | ||
|
5de88ea24b | ||
|
f78b5b2f99 | ||
|
3c84ad16cb | ||
|
6706d0701f | ||
|
2285790521 | ||
|
5505257daa | ||
|
93a2de96c1 | ||
|
30638dbc75 | ||
|
026ccc85ee | ||
|
b776313c80 | ||
|
765145dbdf | ||
|
006aadac6e | ||
|
c2b80afd0f | ||
|
7d41c580dc | ||
|
c65ba1618b | ||
|
7bbc8f3c4f | ||
|
259d774539 | ||
|
d373365710 | ||
|
f762a7cdc8 | ||
|
5d7238fad7 | ||
|
08c6a09c77 | ||
|
2775fdaea3 | ||
|
cf09181139 | ||
|
7c053bd87e | ||
|
3281d606ef | ||
|
ff43f9b2d1 | ||
|
30864af98b | ||
|
80845b0233 | ||
|
e4a3ba7a2b | ||
|
8fb770568f | ||
|
78c459de1a | ||
|
60c1412416 | ||
|
d27ea7b869 | ||
|
a205030a15 | ||
|
e70e445899 | ||
|
12979c3048 | ||
|
261072af89 | ||
|
b7c8dc8ae7 |
src
communication/bolt/v1
glue
query
tests
e2e/write_procedures
unit
@ -167,6 +167,7 @@ class Value {
|
||||
Value(const Edge &value) : type_(Type::Edge) { new (&edge_v) Edge(value); }
|
||||
Value(const UnboundedEdge &value) : type_(Type::UnboundedEdge) { new (&unbounded_edge_v) UnboundedEdge(value); }
|
||||
Value(const Path &value) : type_(Type::Path) { new (&path_v) Path(value); }
|
||||
|
||||
Value(const utils::Date &date) : type_(Type::Date) { new (&date_v) utils::Date(date); }
|
||||
Value(const utils::LocalTime &time) : type_(Type::LocalTime) { new (&local_time_v) utils::LocalTime(time); }
|
||||
Value(const utils::LocalDateTime &date_time) : type_(Type::LocalDateTime) {
|
||||
|
@ -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(std::make_pair("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(std::make_pair("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::Graph.
|
||||
/// @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");
|
||||
|
155
src/query/db_accessor.cpp
Normal file
155
src/query/db_accessor.cpp
Normal file
@ -0,0 +1,155 @@
|
||||
// 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) {}
|
||||
|
||||
SubgraphDbAccessor *SubgraphDbAccessor::MakeSubgraphDbAccessor(DbAccessor *db_accessor, Graph *graph) {
|
||||
return new SubgraphDbAccessor(db_accessor, 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) {
|
||||
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_;
|
||||
|
||||
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(
|
||||
VertexAccessor *) { // NOLINT(hicpp-named-parameter, readability-convert-member-functions-to-static)
|
||||
throw std::logic_error{"Such operation not possible on subgraph"};
|
||||
}
|
||||
|
||||
storage::Result<std::optional<VertexAccessor>> SubgraphDbAccessor::RemoveVertex(
|
||||
SubgraphVertexAccessor *subgraphvertex_accessor) {
|
||||
VertexAccessor *vertex_accessor = &subgraphvertex_accessor->impl_;
|
||||
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) {
|
||||
return VerticesIterable(graph_->vertices());
|
||||
} // NOLINT(hicpp-named-parameter)
|
||||
|
||||
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::unordered_set<storage::EdgeAccessor> graph_edges_storage;
|
||||
|
||||
for (auto e : graph_edges) {
|
||||
graph_edges_storage.insert(e.impl_);
|
||||
}
|
||||
|
||||
std::vector<storage::EdgeAccessor> filteredOutEdges;
|
||||
for (auto &edge : edges) {
|
||||
if (std::find(begin(graph_edges_storage), end(graph_edges_storage), edge) != std::end(graph_edges_storage)) {
|
||||
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::unordered_set<storage::EdgeAccessor> graph_edges_storage;
|
||||
|
||||
for (auto e : graph_edges) {
|
||||
graph_edges_storage.insert(e.impl_);
|
||||
}
|
||||
|
||||
std::vector<storage::EdgeAccessor> filteredOutEdges;
|
||||
for (auto &edge : edges) {
|
||||
if (std::find(begin(graph_edges_storage), end(graph_edges_storage), edge) != std::end(graph_edges_storage)) {
|
||||
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,38 +188,115 @@ inline VertexAccessor EdgeAccessor::From() const { return VertexAccessor(impl_.F
|
||||
|
||||
inline bool EdgeAccessor::IsCycle() const { return To() == From(); }
|
||||
|
||||
class DbAccessor final {
|
||||
storage::Storage::Accessor *accessor_;
|
||||
class SubgraphVertexAccessor final {
|
||||
public:
|
||||
query::VertexAccessor impl_;
|
||||
query::Graph *graph_;
|
||||
|
||||
class VerticesIterable final {
|
||||
storage::VerticesIterable iterable_;
|
||||
explicit SubgraphVertexAccessor(query::VertexAccessor impl, query::Graph *graph_) : impl_(impl), graph_(graph_) {}
|
||||
|
||||
bool operator==(const SubgraphVertexAccessor &v) const noexcept {
|
||||
static_assert(noexcept(impl_ == v.impl_));
|
||||
return impl_ == v.impl_;
|
||||
}
|
||||
|
||||
auto InEdges(storage::View view) const -> decltype(impl_.OutEdges(view));
|
||||
|
||||
auto OutEdges(storage::View view) const -> decltype(impl_.OutEdges(view));
|
||||
|
||||
auto Labels(storage::View view) const { return impl_.Labels(view); }
|
||||
|
||||
storage::Result<bool> AddLabel(storage::LabelId label) { return impl_.AddLabel(label); }
|
||||
|
||||
storage::Result<bool> RemoveLabel(storage::LabelId label) { return impl_.RemoveLabel(label); }
|
||||
|
||||
storage::Result<bool> HasLabel(storage::View view, storage::LabelId label) const {
|
||||
return impl_.HasLabel(view, label);
|
||||
}
|
||||
|
||||
auto Properties(storage::View view) const { return impl_.Properties(view); }
|
||||
|
||||
storage::Result<storage::PropertyValue> GetProperty(storage::View view, storage::PropertyId key) const {
|
||||
return impl_.GetProperty(view, key);
|
||||
}
|
||||
|
||||
storage::Gid Gid() const noexcept { return impl_.Gid(); }
|
||||
|
||||
storage::Result<storage::PropertyValue> SetProperty(storage::PropertyId key, const storage::PropertyValue &value) {
|
||||
return impl_.SetProperty(key, value);
|
||||
}
|
||||
VertexAccessor GetVertexAccessor() const;
|
||||
};
|
||||
} // namespace memgraph::query
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<memgraph::query::VertexAccessor> {
|
||||
size_t operator()(const memgraph::query::VertexAccessor &v) const { return std::hash<decltype(v.impl_)>{}(v.impl_); }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<memgraph::query::EdgeAccessor> {
|
||||
size_t operator()(const memgraph::query::EdgeAccessor &e) const { return std::hash<decltype(e.impl_)>{}(e.impl_); }
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
namespace memgraph::query {
|
||||
|
||||
class VerticesIterable final {
|
||||
std::variant<storage::VerticesIterable, std::unordered_set<VertexAccessor, std::hash<VertexAccessor>,
|
||||
std::equal_to<void>, utils::Allocator<VertexAccessor>>>
|
||||
iterable_;
|
||||
|
||||
public:
|
||||
class Iterator final {
|
||||
std::variant<storage::VerticesIterable::Iterator,
|
||||
std::unordered_set<VertexAccessor, std::hash<VertexAccessor>, std::equal_to<void>,
|
||||
utils::Allocator<VertexAccessor>>::iterator>
|
||||
it_;
|
||||
|
||||
public:
|
||||
class Iterator final {
|
||||
storage::VerticesIterable::Iterator it_;
|
||||
explicit Iterator(storage::VerticesIterable::Iterator it) : it_(it) {}
|
||||
explicit Iterator(std::unordered_set<VertexAccessor, std::hash<VertexAccessor>, std::equal_to<void>,
|
||||
utils::Allocator<VertexAccessor>>::iterator it)
|
||||
: it_(it) {}
|
||||
|
||||
public:
|
||||
explicit Iterator(storage::VerticesIterable::Iterator it) : it_(it) {}
|
||||
VertexAccessor operator*() const {
|
||||
return std::visit(memgraph::utils::Overloaded{[](auto it_) { return VertexAccessor(*it_); }}, it_);
|
||||
}
|
||||
|
||||
VertexAccessor operator*() const { return VertexAccessor(*it_); }
|
||||
Iterator &operator++() {
|
||||
std::visit(memgraph::utils::Overloaded{[this](auto it_) { this->it_ = ++it_; }}, it_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iterator &operator++() {
|
||||
++it_;
|
||||
return *this;
|
||||
}
|
||||
bool operator==(const Iterator &other) const { return it_ == other.it_; }
|
||||
|
||||
bool operator==(const Iterator &other) const { return it_ == other.it_; }
|
||||
|
||||
bool operator!=(const Iterator &other) const { return !(other == *this); }
|
||||
};
|
||||
|
||||
explicit VerticesIterable(storage::VerticesIterable iterable) : iterable_(std::move(iterable)) {}
|
||||
|
||||
Iterator begin() { return Iterator(iterable_.begin()); }
|
||||
|
||||
Iterator end() { return Iterator(iterable_.end()); }
|
||||
bool operator!=(const Iterator &other) const { return !(other == *this); }
|
||||
};
|
||||
|
||||
explicit VerticesIterable(storage::VerticesIterable iterable) : iterable_(std::move(iterable)) {}
|
||||
explicit VerticesIterable(std::unordered_set<VertexAccessor, std::hash<VertexAccessor>, std::equal_to<void>,
|
||||
utils::Allocator<VertexAccessor>>
|
||||
vertices)
|
||||
: iterable_(vertices) {}
|
||||
|
||||
Iterator begin() {
|
||||
return std::visit(memgraph::utils::Overloaded{[](auto &iterable_) { return Iterator(iterable_.begin()); }},
|
||||
iterable_);
|
||||
}
|
||||
|
||||
Iterator end() {
|
||||
return std::visit(memgraph::utils::Overloaded{[](auto &iterable_) { return Iterator(iterable_.end()); }},
|
||||
iterable_);
|
||||
}
|
||||
};
|
||||
|
||||
class DbAccessor final {
|
||||
storage::Storage::Accessor *accessor_;
|
||||
|
||||
public:
|
||||
explicit DbAccessor(storage::Storage::Accessor *accessor) : accessor_(accessor) {}
|
||||
|
||||
@ -358,18 +438,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(
|
||||
VertexAccessor *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)];
|
||||
}
|
||||
|
||||
|
@ -2108,6 +2108,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) {
|
||||
|
91
src/query/graph.cpp
Normal file
91
src/query/graph.cpp
Normal file
@ -0,0 +1,91 @@
|
||||
// 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 std::find(begin(vertices_), end(vertices_), vertex) != std::end(vertices_);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
std::vector<EdgeAccessor> Graph::OutEdges(VertexAccessor vertex_accessor) {
|
||||
std::vector<EdgeAccessor> out_edges;
|
||||
for (const auto &edge : edges_) {
|
||||
if (edge.From() == vertex_accessor) {
|
||||
out_edges.emplace_back(edge);
|
||||
}
|
||||
}
|
||||
return out_edges;
|
||||
}
|
||||
|
||||
/** Copy assign other, utils::MemoryResource of `this` is used */
|
||||
Graph &Graph::operator=(const Graph &) = default;
|
||||
|
||||
/** Move assign other, utils::MemoryResource of `this` is used. */
|
||||
Graph &Graph::operator=(Graph &&) noexcept = default;
|
||||
|
||||
Graph::~Graph() = default;
|
||||
|
||||
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
|
111
src/query/graph.hpp
Normal file
111
src/query/graph.hpp
Normal file
@ -0,0 +1,111 @@
|
||||
// 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<char>;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/** 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 &);
|
||||
|
||||
/** Move assign other, utils::MemoryResource of `this` is used. */
|
||||
Graph &operator=(Graph &&) noexcept;
|
||||
|
||||
~Graph();
|
||||
|
||||
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("Trying to get graph");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -365,6 +365,9 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
}
|
||||
throw QueryRuntimeException("Invalid property name {} for LocalDateTime", prop_name);
|
||||
}
|
||||
case TypedValue::Type::Graph: {
|
||||
throw QueryRuntimeException("Invalid operation for Graph");
|
||||
}
|
||||
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"
|
||||
@ -2607,10 +2608,12 @@ TypedValue DefaultAggregationOpValue(const Aggregate::Element &element, utils::M
|
||||
case Aggregation::Op::MAX:
|
||||
case Aggregation::Op::AVG:
|
||||
return TypedValue(memory);
|
||||
case Aggregation::Op::COLLECT_LIST:
|
||||
return TypedValue(TypedValue::TVector(memory));
|
||||
case Aggregation::Op::COLLECT_MAP:
|
||||
return TypedValue(TypedValue::TMap(memory));
|
||||
case Aggregation::Op::COLLECT_LIST:
|
||||
return TypedValue(TypedValue::TVector(memory));
|
||||
case Aggregation::Op::PROJECT:
|
||||
return TypedValue(query::Graph(memory));
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
@ -2775,7 +2778,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();
|
||||
@ -2814,6 +2816,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.");
|
||||
@ -2862,6 +2869,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.");
|
||||
@ -2898,6 +2910,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 {
|
||||
@ -3710,6 +3734,15 @@ void CallCustomProcedure(const std::string_view fully_qualified_procedure_name,
|
||||
for (auto *expression : args) {
|
||||
args_list.emplace_back(expression->Accept(*evaluator));
|
||||
}
|
||||
|
||||
if (!args_list.empty() && args_list.front().type() == TypedValue::Type::Graph) {
|
||||
auto *subgraph =
|
||||
new query::Graph(std::move(args_list.front().ValueGraph()), args_list.front().ValueGraph().GetMemoryResource());
|
||||
args_list.erase(args_list.begin());
|
||||
|
||||
graph.impl = query::SubgraphDbAccessor::MakeSubgraphDbAccessor(std::get<query::DbAccessor *>(graph.impl), subgraph);
|
||||
}
|
||||
|
||||
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{"No graph type"};
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
@ -324,17 +329,19 @@ 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 +461,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 +496,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 +838,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 +1177,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 +1544,15 @@ 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(
|
||||
memgraph::utils::Overloaded{
|
||||
[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 +1562,12 @@ 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(memgraph::utils::Overloaded{[](auto &impl) { return impl.Gid().AsInt(); }}, v->impl)};
|
||||
},
|
||||
result);
|
||||
}
|
||||
|
||||
mgp_error mgp_vertex_underlying_graph_is_mutable(mgp_vertex *v, int *result) {
|
||||
@ -1581,8 +1639,15 @@ 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(
|
||||
memgraph::utils::Overloaded{[property_name](auto *impl) { return impl->NameToProperty(property_name); }},
|
||||
v->graph->impl);
|
||||
|
||||
const auto result = std::visit(memgraph::utils::Overloaded{[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 +1673,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 +1686,18 @@ 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(
|
||||
memgraph::utils::Overloaded{
|
||||
[label](auto *impl) { return impl->NameToLabel(label.name); },
|
||||
},
|
||||
v->graph->impl);
|
||||
|
||||
const auto result = std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[label_id](auto &impl) { return impl.AddLabel(label_id); },
|
||||
|
||||
},
|
||||
v->impl);
|
||||
|
||||
if (result.HasError()) {
|
||||
switch (result.GetError()) {
|
||||
@ -1643,7 +1718,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 +1728,17 @@ 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(
|
||||
memgraph::utils::Overloaded{
|
||||
[label](auto *impl) { return impl->NameToLabel(label.name); },
|
||||
},
|
||||
v->graph->impl);
|
||||
const auto result = std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[label_id](auto &impl) { return impl.RemoveLabel(label_id); },
|
||||
|
||||
},
|
||||
v->impl);
|
||||
|
||||
if (result.HasError()) {
|
||||
switch (result.GetError()) {
|
||||
@ -1675,7 +1759,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 +1780,8 @@ 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(
|
||||
memgraph::utils::Overloaded{[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 +1803,11 @@ 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(
|
||||
memgraph::utils::Overloaded{
|
||||
[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 +1824,16 @@ 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(
|
||||
memgraph::utils::Overloaded{
|
||||
[label](const auto *impl) { return impl->LabelToName(label); },
|
||||
},
|
||||
v->graph->impl);
|
||||
return name.c_str();
|
||||
},
|
||||
&result->name);
|
||||
@ -1748,9 +1843,17 @@ 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(
|
||||
memgraph::utils::Overloaded{
|
||||
[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(
|
||||
memgraph::utils::Overloaded{
|
||||
[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 +1881,17 @@ 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(
|
||||
memgraph::utils::Overloaded{
|
||||
[name](auto *impl) { return impl->NameToProperty(name); },
|
||||
},
|
||||
v->graph->impl);
|
||||
|
||||
auto maybe_prop = std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[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 +1917,11 @@ 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(
|
||||
memgraph::utils::Overloaded{
|
||||
[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 +1949,11 @@ 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(
|
||||
memgraph::utils::Overloaded{
|
||||
[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 +1971,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 +1996,12 @@ 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(
|
||||
memgraph::utils::Overloaded{
|
||||
[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 +2016,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 +2070,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 +2117,14 @@ 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(
|
||||
memgraph::utils::Overloaded{
|
||||
[e](const auto *impl) { return impl->EdgeTypeToName(e->impl.EdgeType()); },
|
||||
},
|
||||
e->from.graph->impl);
|
||||
// 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.");
|
||||
return name.c_str();
|
||||
},
|
||||
&result->name);
|
||||
@ -1978,7 +2143,11 @@ 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(
|
||||
memgraph::utils::Overloaded{
|
||||
[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 +2173,11 @@ 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(
|
||||
memgraph::utils::Overloaded{
|
||||
[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 +2244,24 @@ 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(
|
||||
memgraph::utils::Overloaded{
|
||||
[graph, id](auto *impl) {
|
||||
return impl->FindVertex(memgraph::storage::Gid::FromInt(id.as_int), graph->view);
|
||||
},
|
||||
},
|
||||
graph->impl);
|
||||
if (maybe_vertex) {
|
||||
return NewRawMgpObject<mgp_vertex>(memory, *maybe_vertex, graph);
|
||||
return std::visit(memgraph::utils::Overloaded{
|
||||
[memory, graph, maybe_vertex](memgraph::query::DbAccessor *) {
|
||||
return NewRawMgpObject<mgp_vertex>(memory, *maybe_vertex, graph);
|
||||
},
|
||||
[memory, graph, maybe_vertex](memgraph::query::SubgraphDbAccessor *impl) {
|
||||
return NewRawMgpObject<mgp_vertex>(
|
||||
memory, memgraph::query::SubgraphVertexAccessor(*maybe_vertex, impl->getGraph()),
|
||||
graph);
|
||||
}},
|
||||
graph->impl);
|
||||
}
|
||||
return nullptr;
|
||||
},
|
||||
@ -2091,15 +2279,18 @@ 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(memgraph::utils::Overloaded{[=](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 +2300,24 @@ 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) {
|
||||
if (std::holds_alternative<memgraph::query::SubgraphVertexAccessor>(vertex->impl)) {
|
||||
throw std::logic_error{
|
||||
"Remove vertex for DbAccessor should not get reference to SubgraphVertexAccessor"};
|
||||
}
|
||||
return impl->RemoveVertex(&std::get<memgraph::query::VertexAccessor>(vertex->impl));
|
||||
},
|
||||
[&](memgraph::query::SubgraphDbAccessor *impl) {
|
||||
if (std::holds_alternative<memgraph::query::VertexAccessor>(vertex->impl)) {
|
||||
throw std::logic_error{
|
||||
"Remove vertex for SubgraphDbAccessor should not get reference to VertexAccessor"};
|
||||
}
|
||||
return impl->RemoveVertex(&(std::get<memgraph::query::SubgraphVertexAccessor>(vertex->impl)));
|
||||
}},
|
||||
graph->impl);
|
||||
|
||||
if (result.HasError()) {
|
||||
switch (result.GetError()) {
|
||||
@ -2144,7 +2352,23 @@ 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) {
|
||||
if (std::holds_alternative<memgraph::query::SubgraphVertexAccessor>(vertex->impl)) {
|
||||
throw std::logic_error{"Wrong type"};
|
||||
}
|
||||
return impl->DetachRemoveVertex(&std::get<memgraph::query::VertexAccessor>(vertex->impl));
|
||||
},
|
||||
[vertex](memgraph::query::SubgraphDbAccessor *impl) {
|
||||
// todo antoniofilipovic change this, it is wrong here since it needs to be
|
||||
// SubgraphVertexAccessor
|
||||
if (std::holds_alternative<memgraph::query::SubgraphVertexAccessor>(vertex->impl)) {
|
||||
throw std::logic_error{"Wrong type"};
|
||||
}
|
||||
return impl->DetachRemoveVertex(&std::get<memgraph::query::VertexAccessor>(vertex->impl));
|
||||
}},
|
||||
graph->impl);
|
||||
|
||||
if (result.HasError()) {
|
||||
switch (result.GetError()) {
|
||||
@ -2191,8 +2415,28 @@ 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) {
|
||||
if (std::holds_alternative<memgraph::query::SubgraphVertexAccessor>(from->impl) ||
|
||||
std::holds_alternative<memgraph::query::SubgraphVertexAccessor>(to->impl)) {
|
||||
throw std::logic_error{"Both vertices must be of VertexAccessor type"};
|
||||
}
|
||||
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) {
|
||||
if (std::holds_alternative<memgraph::query::VertexAccessor>(from->impl) ||
|
||||
std::holds_alternative<memgraph::query::VertexAccessor>(to->impl)) {
|
||||
throw std::logic_error{"Both vertices must be of SubgraphVertexAccessor type"};
|
||||
}
|
||||
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 +2457,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 +2478,9 @@ 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(
|
||||
memgraph::utils::Overloaded{[edge](auto *impl) { return impl->RemoveEdge(&edge->impl); }}, graph->impl);
|
||||
if (result.HasError()) {
|
||||
switch (result.GetError()) {
|
||||
case memgraph::storage::Error::NONEXISTENT_OBJECT:
|
||||
@ -2285,7 +2541,18 @@ 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 +2835,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,21 +443,69 @@ 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) {}
|
||||
: memory(memory), impl(other.impl), graph(other.graph) {
|
||||
std::visit(memgraph::utils::Overloaded{
|
||||
[](memgraph::query::VertexAccessor) { std::cout << "VertexAccessor" << std::endl; },
|
||||
[](memgraph::query::SubgraphVertexAccessor) { std::cout << "SubgraphVertexAccessor" << std::endl; }},
|
||||
other.impl);
|
||||
|
||||
std::visit(memgraph::utils::Overloaded{
|
||||
[](memgraph::query::VertexAccessor) { std::cout << "VertexAccessor" << std::endl; },
|
||||
[](memgraph::query::SubgraphVertexAccessor) { std::cout << "SubgraphVertexAccessor" << std::endl; }},
|
||||
this->impl);
|
||||
}
|
||||
|
||||
mgp_vertex(mgp_vertex &&other, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), impl(other.impl), graph(other.graph) {}
|
||||
: memory(memory), impl(other.impl), graph(other.graph) {
|
||||
std::visit(memgraph::utils::Overloaded{
|
||||
[](memgraph::query::VertexAccessor) { std::cout << "VertexAccessor" << std::endl; },
|
||||
[](memgraph::query::SubgraphVertexAccessor) { std::cout << "SubgraphVertexAccessor" << std::endl; }},
|
||||
other.impl);
|
||||
|
||||
std::visit(memgraph::utils::Overloaded{
|
||||
[](memgraph::query::VertexAccessor) { std::cout << "VertexAccessor" << std::endl; },
|
||||
[](memgraph::query::SubgraphVertexAccessor) { std::cout << "SubgraphVertexAccessor" << std::endl; }},
|
||||
this->impl);
|
||||
}
|
||||
|
||||
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 std::visit(memgraph::utils::Overloaded{
|
||||
[&other](memgraph::query::VertexAccessor impl) {
|
||||
if (std::holds_alternative<memgraph::query::SubgraphVertexAccessor>(other.impl)) {
|
||||
return false;
|
||||
}
|
||||
return impl == std::get<memgraph::query::VertexAccessor>(other.impl);
|
||||
},
|
||||
[&other](memgraph::query::SubgraphVertexAccessor impl) -> bool {
|
||||
if (std::holds_alternative<memgraph::query::VertexAccessor>(other.impl)) {
|
||||
return false;
|
||||
}
|
||||
return impl == std::get<memgraph::query::SubgraphVertexAccessor>(other.impl);
|
||||
},
|
||||
},
|
||||
this->impl);
|
||||
}
|
||||
|
||||
bool operator!=(const mgp_vertex &other) const noexcept { return !(*this == other); };
|
||||
|
||||
~mgp_vertex() = default;
|
||||
@ -464,10 +513,38 @@ 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;
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
// memgraph:::query::Graph *subraph;
|
||||
|
||||
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_edge {
|
||||
/// Allocator type so that STL containers are aware that we need one.
|
||||
/// We don't actually need this, but it simplifies the C API, because we store
|
||||
@ -484,6 +561,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) {}
|
||||
|
||||
@ -570,23 +657,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 +685,18 @@ 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(memgraph::utils::Overloaded{
|
||||
[this, memory](const memgraph::query::DbAccessor *impl) {
|
||||
return memgraph::utils::pmr::string(impl->PropertyToName(current_it->first), memory);
|
||||
},
|
||||
[this, memory](const memgraph::query::SubgraphDbAccessor *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 +715,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 +741,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).InEdges(source_vertex.graph->view))>>
|
||||
out;
|
||||
std::optional<decltype(out->begin())> out_it;
|
||||
std::optional<mgp_edge> current_e;
|
||||
};
|
||||
@ -674,9 +758,20 @@ 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(memgraph::utils::Overloaded{[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 +779,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;
|
||||
};
|
||||
|
@ -235,6 +235,7 @@ void ConstructArguments(const std::vector<TypedValue> &args, const TCall &callab
|
||||
throw QueryRuntimeException("'{}' argument named '{}' at position {} must be of type {}.", fully_qualified_name,
|
||||
name, i, type->GetPresentableName());
|
||||
}
|
||||
// why do we move graph here?
|
||||
args_list.elems.emplace_back(std::move(arg), &graph);
|
||||
}
|
||||
// Fill missing optional arguments with their default values.
|
||||
|
@ -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();
|
||||
}
|
||||
@ -297,6 +302,8 @@ TypedValue::operator storage::PropertyValue() const {
|
||||
storage::TemporalData{storage::TemporalType::LocalDateTime, local_date_time_v.MicrosecondsSinceEpoch()});
|
||||
case Type::Duration:
|
||||
return storage::PropertyValue(storage::TemporalData{storage::TemporalType::Duration, duration_v.microseconds});
|
||||
case Type::Graph:
|
||||
throw TypedValueException("Unsupported copy from StorageValue to TypedValue");
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -331,6 +338,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 +395,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 +532,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 +606,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 +649,9 @@ void TypedValue::DestroyValue() {
|
||||
case Type::LocalDateTime:
|
||||
case Type::Duration:
|
||||
break;
|
||||
case Type::Graph:
|
||||
graph_v.~Graph();
|
||||
break;
|
||||
}
|
||||
|
||||
type_ = TypedValue::Type::Null;
|
||||
@ -792,6 +811,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 +1121,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)
|
||||
|
@ -13,11 +13,64 @@ import mgp
|
||||
|
||||
|
||||
@mgp.read_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.read_proc
|
||||
def graph_is_mutable(ctx: mgp.ProcCtx) -> mgp.Record(mutable=bool):
|
||||
return mgp.Record(mutable=ctx.graph.is_mutable())
|
||||
|
||||
|
||||
@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
|
||||
|
@ -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,59 @@ 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)
|
||||
return [mgp.Record(node=vertex) for vertex in ctx.graph.vertices]
|
||||
|
248
tests/e2e/write_procedures/read_subgraph.py
Normal file
248
tests/e2e/write_procedures/read_subgraph.py
Normal file
@ -0,0 +1,248 @@
|
||||
# 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_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_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_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"]))
|
@ -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,33 @@ 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(
|
||||
memgraph::utils::Overloaded{
|
||||
[](auto impl) { return impl.InEdges(memgraph::storage::View::NEW); },
|
||||
},
|
||||
from->impl)),
|
||||
0);
|
||||
EXPECT_EQ(CountMaybeIterables(std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[](auto impl) { return impl.OutEdges(memgraph::storage::View::NEW); },
|
||||
},
|
||||
from->impl)),
|
||||
number_of_edges_between);
|
||||
EXPECT_EQ(CountMaybeIterables(std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[](auto impl) { return impl.InEdges(memgraph::storage::View::NEW); },
|
||||
},
|
||||
to->impl)),
|
||||
number_of_edges_between);
|
||||
EXPECT_EQ(CountMaybeIterables(std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[](auto impl) { return impl.OutEdges(memgraph::storage::View::NEW); },
|
||||
},
|
||||
to->impl)),
|
||||
0);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user