Graph project feature implementation (#508) (#535)

This commit is contained in:
Kostas Kyrimis 2022-09-07 16:00:49 +03:00 committed by GitHub
parent 9e8fb2516b
commit f1fe77adfb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1440 additions and 140 deletions

View File

@ -127,6 +127,10 @@ storage::Result<Value> ToBoltValue(const query::TypedValue &value, const storage
return Value(value.ValueLocalDateTime());
case query::TypedValue::Type::Duration:
return Value(value.ValueDuration());
case query::TypedValue::Type::Graph:
auto maybe_graph = ToBoltGraph(value.ValueGraph(), db, view);
if (maybe_graph.HasError()) return maybe_graph.GetError();
return Value(std::move(*maybe_graph));
}
}
@ -183,6 +187,30 @@ storage::Result<communication::bolt::Path> ToBoltPath(const query::Path &path, c
return communication::bolt::Path(vertices, edges);
}
storage::Result<std::map<std::string, Value>> ToBoltGraph(const query::Graph &graph, const storage::Storage &db,
storage::View view) {
std::map<std::string, Value> map;
std::vector<Value> vertices;
vertices.reserve(graph.vertices().size());
for (const auto &v : graph.vertices()) {
auto maybe_vertex = ToBoltVertex(v, db, view);
if (maybe_vertex.HasError()) return maybe_vertex.GetError();
vertices.emplace_back(Value(std::move(*maybe_vertex)));
}
map.emplace("nodes", Value(vertices));
std::vector<Value> edges;
edges.reserve(graph.edges().size());
for (const auto &e : graph.edges()) {
auto maybe_edge = ToBoltEdge(e, db, view);
if (maybe_edge.HasError()) return maybe_edge.GetError();
edges.emplace_back(Value(std::move(*maybe_edge)));
}
map.emplace("edges", Value(edges));
return std::move(map);
}
storage::PropertyValue ToPropertyValue(const Value &value) {
switch (value.type()) {
case Value::Type::Null:

View File

@ -51,6 +51,15 @@ storage::Result<communication::bolt::Edge> ToBoltEdge(const storage::EdgeAccesso
storage::Result<communication::bolt::Path> ToBoltPath(const query::Path &path, const storage::Storage &db,
storage::View view);
/// @param query::Graph for converting to communication::bolt::Map.
/// @param storage::Storage for ToBoltVertex and ToBoltEdge.
/// @param storage::View for ToBoltVertex and ToBoltEdge.
///
/// @throw std::bad_alloc
storage::Result<std::map<std::string, communication::bolt::Value>> ToBoltGraph(const query::Graph &graph,
const storage::Storage &db,
storage::View view);
/// @param query::TypedValue for converting to communication::bolt::Value.
/// @param storage::Storage for ToBoltVertex and ToBoltEdge.
/// @param storage::View for ToBoltVertex and ToBoltEdge.

View File

@ -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)

View File

@ -61,6 +61,7 @@ bool TypedValueCompare(const TypedValue &a, const TypedValue &b) {
case TypedValue::Type::Vertex:
case TypedValue::Type::Edge:
case TypedValue::Type::Path:
case TypedValue::Type::Graph:
throw QueryRuntimeException("Comparison is not defined for values of type {}.", a.type());
case TypedValue::Type::Null:
LOG_FATAL("Invalid type");

148
src/query/db_accessor.cpp Normal file
View File

@ -0,0 +1,148 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "query/db_accessor.hpp"
#include "query/graph.hpp"
#include <cppitertools/filter.hpp>
#include <cppitertools/imap.hpp>
#include "utils/pmr/unordered_set.hpp"
namespace memgraph::query {
SubgraphDbAccessor::SubgraphDbAccessor(query::DbAccessor db_accessor, Graph *graph)
: db_accessor_(db_accessor), graph_(graph) {}
storage::PropertyId SubgraphDbAccessor::NameToProperty(const std::string_view name) {
return db_accessor_.NameToProperty(name);
}
storage::LabelId SubgraphDbAccessor::NameToLabel(const std::string_view name) { return db_accessor_.NameToLabel(name); }
storage::EdgeTypeId SubgraphDbAccessor::NameToEdgeType(const std::string_view name) {
return db_accessor_.NameToEdgeType(name);
}
const std::string &SubgraphDbAccessor::PropertyToName(storage::PropertyId prop) const {
return db_accessor_.PropertyToName(prop);
}
const std::string &SubgraphDbAccessor::LabelToName(storage::LabelId label) const {
return db_accessor_.LabelToName(label);
}
const std::string &SubgraphDbAccessor::EdgeTypeToName(storage::EdgeTypeId type) const {
return db_accessor_.EdgeTypeToName(type);
}
storage::Result<std::optional<EdgeAccessor>> SubgraphDbAccessor::RemoveEdge(EdgeAccessor *edge) {
if (!this->graph_->ContainsEdge(*edge)) {
throw std::logic_error{"Projected graph must contain edge!"};
}
auto result = db_accessor_.RemoveEdge(edge);
if (result.HasError() || !*result) {
return result;
}
return this->graph_->RemoveEdge(*edge);
}
storage::Result<EdgeAccessor> SubgraphDbAccessor::InsertEdge(SubgraphVertexAccessor *from, SubgraphVertexAccessor *to,
const storage::EdgeTypeId &edge_type) {
VertexAccessor *from_impl = &from->impl_;
VertexAccessor *to_impl = &to->impl_;
if (!this->graph_->ContainsVertex(*from_impl) || !this->graph_->ContainsVertex(*to_impl)) {
throw std::logic_error{"Projected graph must contain both vertices to insert edge!"};
}
auto result = db_accessor_.InsertEdge(from_impl, to_impl, edge_type);
if (result.HasError()) {
return result;
}
this->graph_->InsertEdge(*result);
return result;
}
storage::Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>>
SubgraphDbAccessor::DetachRemoveVertex( // NOLINT(readability-convert-member-functions-to-static)
SubgraphVertexAccessor *) { // NOLINT(hicpp-named-parameter)
throw std::logic_error{
"Vertex holds only partial information about edges. Cannot detach delete safely while using projected graph."};
}
storage::Result<std::optional<VertexAccessor>> SubgraphDbAccessor::RemoveVertex(
SubgraphVertexAccessor *subgraphvertex_accessor) {
VertexAccessor *vertex_accessor = &subgraphvertex_accessor->impl_;
if (!this->graph_->ContainsVertex(*vertex_accessor)) {
throw std::logic_error{"Projected graph must contain vertex!"};
}
auto result = db_accessor_.RemoveVertex(vertex_accessor);
if (result.HasError() || !*result) {
return result;
}
return this->graph_->RemoveVertex(*vertex_accessor);
}
SubgraphVertexAccessor SubgraphDbAccessor::InsertVertex() {
VertexAccessor vertex = db_accessor_.InsertVertex();
this->graph_->InsertVertex(vertex);
return SubgraphVertexAccessor(vertex, this->getGraph());
}
VerticesIterable SubgraphDbAccessor::Vertices(storage::View) { // NOLINT(hicpp-named-parameter)
return VerticesIterable(&graph_->vertices());
}
std::optional<VertexAccessor> SubgraphDbAccessor::FindVertex(storage::Gid gid, storage::View view) {
std::optional<VertexAccessor> maybe_vertex = db_accessor_.FindVertex(gid, view);
if (maybe_vertex && this->graph_->ContainsVertex(*maybe_vertex)) {
return *maybe_vertex;
}
return std::nullopt;
}
query::Graph *SubgraphDbAccessor::getGraph() { return graph_; }
VertexAccessor SubgraphVertexAccessor::GetVertexAccessor() const { return impl_; }
auto SubgraphVertexAccessor::OutEdges(storage::View view) const -> decltype(impl_.OutEdges(view)) {
auto maybe_edges = impl_.impl_.OutEdges(view, {});
if (maybe_edges.HasError()) return maybe_edges.GetError();
auto edges = std::move(*maybe_edges);
auto graph_edges = graph_->edges();
std::vector<storage::EdgeAccessor> filteredOutEdges;
for (auto &edge : edges) {
auto edge_q = EdgeAccessor(edge);
if (graph_edges.contains(edge_q)) {
filteredOutEdges.push_back(edge);
}
}
return iter::imap(VertexAccessor::MakeEdgeAccessor, std::move(filteredOutEdges));
}
auto SubgraphVertexAccessor::InEdges(storage::View view) const -> decltype(impl_.InEdges(view)) {
auto maybe_edges = impl_.impl_.InEdges(view, {});
if (maybe_edges.HasError()) return maybe_edges.GetError();
auto edges = std::move(*maybe_edges);
auto graph_edges = graph_->edges();
std::vector<storage::EdgeAccessor> filteredOutEdges;
for (auto &edge : edges) {
auto edge_q = EdgeAccessor(edge);
if (graph_edges.contains(edge_q)) {
filteredOutEdges.push_back(edge);
}
}
return iter::imap(VertexAccessor::MakeEdgeAccessor, std::move(filteredOutEdges));
}
} // namespace memgraph::query

View File

@ -20,6 +20,8 @@
#include "storage/v2/id_types.hpp"
#include "storage/v2/property_value.hpp"
#include "storage/v2/result.hpp"
#include "utils/pmr/unordered_set.hpp"
#include "utils/variant_helpers.hpp"
///////////////////////////////////////////////////////////
// Our communication layer and query engine don't mix
@ -45,6 +47,7 @@
namespace memgraph::query {
class Graph;
class VertexAccessor;
class EdgeAccessor final {
@ -185,23 +188,87 @@ inline VertexAccessor EdgeAccessor::From() const { return VertexAccessor(impl_.F
inline bool EdgeAccessor::IsCycle() const { return To() == From(); }
class DbAccessor final {
storage::Storage::Accessor *accessor_;
class SubgraphVertexAccessor final {
public:
query::VertexAccessor impl_;
query::Graph *graph_;
explicit SubgraphVertexAccessor(query::VertexAccessor impl, query::Graph *graph_) : impl_(impl), graph_(graph_) {}
bool operator==(const SubgraphVertexAccessor &v) const noexcept {
static_assert(noexcept(impl_ == v.impl_));
return impl_ == v.impl_;
}
auto InEdges(storage::View view) const -> decltype(impl_.OutEdges(view));
auto OutEdges(storage::View view) const -> decltype(impl_.OutEdges(view));
auto Labels(storage::View view) const { return impl_.Labels(view); }
storage::Result<bool> AddLabel(storage::LabelId label) { return impl_.AddLabel(label); }
storage::Result<bool> RemoveLabel(storage::LabelId label) { return impl_.RemoveLabel(label); }
storage::Result<bool> HasLabel(storage::View view, storage::LabelId label) const {
return impl_.HasLabel(view, label);
}
auto Properties(storage::View view) const { return impl_.Properties(view); }
storage::Result<storage::PropertyValue> GetProperty(storage::View view, storage::PropertyId key) const {
return impl_.GetProperty(view, key);
}
storage::Gid Gid() const noexcept { return impl_.Gid(); }
storage::Result<storage::PropertyValue> SetProperty(storage::PropertyId key, const storage::PropertyValue &value) {
return impl_.SetProperty(key, value);
}
VertexAccessor GetVertexAccessor() const;
};
} // namespace memgraph::query
namespace std {
template <>
struct hash<memgraph::query::VertexAccessor> {
size_t operator()(const memgraph::query::VertexAccessor &v) const { return std::hash<decltype(v.impl_)>{}(v.impl_); }
};
template <>
struct hash<memgraph::query::EdgeAccessor> {
size_t operator()(const memgraph::query::EdgeAccessor &e) const { return std::hash<decltype(e.impl_)>{}(e.impl_); }
};
} // namespace std
namespace memgraph::query {
class VerticesIterable final {
storage::VerticesIterable iterable_;
std::variant<storage::VerticesIterable, std::unordered_set<VertexAccessor, std::hash<VertexAccessor>,
std::equal_to<void>, utils::Allocator<VertexAccessor>> *>
iterable_;
public:
class Iterator final {
storage::VerticesIterable::Iterator it_;
std::variant<storage::VerticesIterable::Iterator,
std::unordered_set<VertexAccessor, std::hash<VertexAccessor>, std::equal_to<void>,
utils::Allocator<VertexAccessor>>::iterator>
it_;
public:
explicit Iterator(storage::VerticesIterable::Iterator it) : it_(it) {}
explicit Iterator(std::unordered_set<VertexAccessor, std::hash<VertexAccessor>, std::equal_to<void>,
utils::Allocator<VertexAccessor>>::iterator it)
: it_(it) {}
VertexAccessor operator*() const { return VertexAccessor(*it_); }
VertexAccessor operator*() const {
return std::visit([](auto it_) { return VertexAccessor(*it_); }, it_);
}
Iterator &operator++() {
++it_;
std::visit([this](auto it_) { this->it_ = ++it_; }, it_);
return *this;
}
@ -211,12 +278,33 @@ class DbAccessor final {
};
explicit VerticesIterable(storage::VerticesIterable iterable) : iterable_(std::move(iterable)) {}
explicit VerticesIterable(std::unordered_set<VertexAccessor, std::hash<VertexAccessor>, std::equal_to<void>,
utils::Allocator<VertexAccessor>> *vertices)
: iterable_(vertices) {}
Iterator begin() { return Iterator(iterable_.begin()); }
Iterator begin() {
return std::visit(memgraph::utils::Overloaded{
[](storage::VerticesIterable &iterable_) { return Iterator(iterable_.begin()); },
[](std::unordered_set<VertexAccessor, std::hash<VertexAccessor>, std::equal_to<void>,
utils::Allocator<VertexAccessor>> *iterable_) {
return Iterator(iterable_->begin());
}},
iterable_);
}
Iterator end() { return Iterator(iterable_.end()); }
Iterator end() {
return std::visit(
memgraph::utils::Overloaded{
[](storage::VerticesIterable &iterable_) { return Iterator(iterable_.end()); },
[](std::unordered_set<VertexAccessor, std::hash<VertexAccessor>, std::equal_to<void>,
utils::Allocator<VertexAccessor>> *iterable_) { return Iterator(iterable_->end()); }},
iterable_);
}
};
class DbAccessor final {
storage::Storage::Accessor *accessor_;
public:
explicit DbAccessor(storage::Storage::Accessor *accessor) : accessor_(accessor) {}
@ -358,18 +446,44 @@ class DbAccessor final {
storage::ConstraintsInfo ListAllConstraints() const { return accessor_->ListAllConstraints(); }
};
class SubgraphDbAccessor final {
DbAccessor db_accessor_;
Graph *graph_;
public:
explicit SubgraphDbAccessor(DbAccessor db_accessor, Graph *graph);
static SubgraphDbAccessor *MakeSubgraphDbAccessor(DbAccessor *db_accessor, Graph *graph);
storage::PropertyId NameToProperty(std::string_view name);
storage::LabelId NameToLabel(std::string_view name);
storage::EdgeTypeId NameToEdgeType(std::string_view name);
const std::string &PropertyToName(storage::PropertyId prop) const;
const std::string &LabelToName(storage::LabelId label) const;
const std::string &EdgeTypeToName(storage::EdgeTypeId type) const;
storage::Result<std::optional<EdgeAccessor>> RemoveEdge(EdgeAccessor *edge);
storage::Result<EdgeAccessor> InsertEdge(SubgraphVertexAccessor *from, SubgraphVertexAccessor *to,
const storage::EdgeTypeId &edge_type);
storage::Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> DetachRemoveVertex(
SubgraphVertexAccessor *vertex_accessor);
storage::Result<std::optional<VertexAccessor>> RemoveVertex(SubgraphVertexAccessor *vertex_accessor);
SubgraphVertexAccessor InsertVertex();
VerticesIterable Vertices(storage::View view);
std::optional<VertexAccessor> FindVertex(storage::Gid gid, storage::View view);
Graph *getGraph();
};
} // namespace memgraph::query
namespace std {
template <>
struct hash<memgraph::query::VertexAccessor> {
size_t operator()(const memgraph::query::VertexAccessor &v) const { return std::hash<decltype(v.impl_)>{}(v.impl_); }
};
template <>
struct hash<memgraph::query::EdgeAccessor> {
size_t operator()(const memgraph::query::EdgeAccessor &e) const { return std::hash<decltype(e.impl_)>{}(e.impl_); }
};
} // namespace std

View File

@ -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)];
}

View File

@ -2126,6 +2126,10 @@ antlrcpp::Any CypherMainVisitor::visitFunctionInvocation(MemgraphCypher::Functio
return static_cast<Expression *>(
storage_->Create<Aggregation>(expressions[0], nullptr, Aggregation::Op::COLLECT_LIST));
}
if (function_name == Aggregation::kProject) {
return static_cast<Expression *>(
storage_->Create<Aggregation>(expressions[0], nullptr, Aggregation::Op::PROJECT));
}
}
if (expressions.size() == 2U && function_name == Aggregation::kCollect) {

73
src/query/graph.cpp Normal file
View File

@ -0,0 +1,73 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "query/graph.hpp"
#include "query/path.hpp"
namespace memgraph::query {
Graph::Graph(utils::MemoryResource *memory) : vertices_(memory), edges_(memory) {}
Graph::Graph(const Graph &other, utils::MemoryResource *memory)
: vertices_(other.vertices_, memory), edges_(other.edges_, memory) {}
Graph::Graph(Graph &&other) noexcept : Graph(std::move(other), other.GetMemoryResource()) {}
Graph::Graph(const Graph &other)
: Graph(other,
std::allocator_traits<allocator_type>::select_on_container_copy_construction(other.GetMemoryResource())
.GetMemoryResource()) {}
Graph::Graph(Graph &&other, utils::MemoryResource *memory)
: vertices_(std::move(other.vertices_), memory), edges_(std::move(other.edges_), memory) {}
void Graph::Expand(const Path &path) {
const auto &path_vertices_ = path.vertices();
const auto &path_edges_ = path.edges();
std::for_each(path_vertices_.begin(), path_vertices_.end(), [this](const VertexAccessor v) { vertices_.insert(v); });
std::for_each(path_edges_.begin(), path_edges_.end(), [this](const EdgeAccessor e) { edges_.insert(e); });
}
void Graph::InsertVertex(const VertexAccessor &vertex) { vertices_.insert(vertex); }
void Graph::InsertEdge(const EdgeAccessor &edge) { edges_.insert(edge); }
bool Graph::ContainsVertex(const VertexAccessor &vertex) { return vertices_.contains(vertex); }
bool Graph::ContainsEdge(const EdgeAccessor &edge) { return edges_.contains(edge); }
std::optional<VertexAccessor> Graph::RemoveVertex(const VertexAccessor &vertex) {
if (!ContainsVertex(vertex)) {
return std::nullopt;
}
auto value = vertices_.erase(vertex);
if (value == 0) {
return std::nullopt;
}
return vertex;
}
std::optional<EdgeAccessor> Graph::RemoveEdge(const EdgeAccessor &edge) {
auto value = edges_.erase(edge);
if (value == 0) {
return std::nullopt;
}
return edge;
}
utils::pmr::unordered_set<VertexAccessor> &Graph::vertices() { return vertices_; }
utils::pmr::unordered_set<EdgeAccessor> &Graph::edges() { return edges_; }
const utils::pmr::unordered_set<VertexAccessor> &Graph::vertices() const { return vertices_; }
const utils::pmr::unordered_set<EdgeAccessor> &Graph::edges() const { return edges_; }
utils::MemoryResource *Graph::GetMemoryResource() const { return vertices_.get_allocator().GetMemoryResource(); }
} // namespace memgraph::query

114
src/query/graph.hpp Normal file
View File

@ -0,0 +1,114 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <functional>
#include <utility>
#include "query/db_accessor.hpp"
#include "utils/logging.hpp"
#include "utils/memory.hpp"
#include "utils/pmr/unordered_set.hpp"
#include "utils/pmr/vector.hpp"
namespace memgraph::query {
class Path;
/**
* A data structure that holds a graph. A graph consists of at least one
* vertex, and zero or more edges.
*/
class Graph final {
public:
/** Allocator type so that STL containers are aware that we need one */
using allocator_type = utils::Allocator<Graph>;
/**
* Create the graph with no elements
* Allocations are done using the given MemoryResource.
*/
explicit Graph(utils::MemoryResource *memory);
/**
* Construct a copy of other.
* utils::MemoryResource is obtained by calling
* std::allocator_traits<>::
* select_on_container_copy_construction(other.GetMemoryResource()).
* Since we use utils::Allocator, which does not propagate, this means that we
* will default to utils::NewDeleteResource().
*/
Graph(const Graph &other);
/** Construct a copy using the given utils::MemoryResource */
Graph(const Graph &other, utils::MemoryResource *memory);
/**
* Construct with the value of other.
* utils::MemoryResource is obtained from other. After the move, other will be
* empty.
*/
Graph(Graph &&other) noexcept;
/**
* Construct with the value of other, but use the given utils::MemoryResource.
* After the move, other may not be empty if `*memory !=
* *other.GetMemoryResource()`, because an element-wise move will be
* performed.
*/
Graph(Graph &&other, utils::MemoryResource *memory);
/** Expands the graph with the given path. */
void Expand(const Path &path);
/** Inserts the vertex in the graph. */
void InsertVertex(const VertexAccessor &vertex);
/** Inserts the edge in the graph. */
void InsertEdge(const EdgeAccessor &edge);
/** Checks whether the graph contains the vertex. */
bool ContainsVertex(const VertexAccessor &vertex);
/** Checks whether the graph contains the edge. */
bool ContainsEdge(const EdgeAccessor &edge);
/** Removes the vertex from the graph if the vertex is in the graph. */
std::optional<VertexAccessor> RemoveVertex(const VertexAccessor &vertex);
/** Removes the vertex from the graph if the vertex is in the graph. */
std::optional<EdgeAccessor> RemoveEdge(const EdgeAccessor &edge);
/** Return the out edges of the given vertex. */
std::vector<EdgeAccessor> OutEdges(VertexAccessor vertex_accessor);
/** Copy assign other, utils::MemoryResource of `this` is used */
Graph &operator=(const Graph &) = default;
/** Move assign other, utils::MemoryResource of `this` is used. */
Graph &operator=(Graph &&) noexcept = default;
~Graph() = default;
utils::pmr::unordered_set<VertexAccessor> &vertices();
utils::pmr::unordered_set<EdgeAccessor> &edges();
const utils::pmr::unordered_set<VertexAccessor> &vertices() const;
const utils::pmr::unordered_set<EdgeAccessor> &edges() const;
utils::MemoryResource *GetMemoryResource() const;
private:
// Contains all the vertices in the Graph.
utils::pmr::unordered_set<VertexAccessor> vertices_;
// Contains all the edges in the Graph
utils::pmr::unordered_set<EdgeAccessor> edges_;
};
} // namespace memgraph::query

View File

@ -587,6 +587,8 @@ TypedValue ValueType(const TypedValue *args, int64_t nargs, const FunctionContex
return TypedValue("LOCAL_DATE_TIME", ctx.memory);
case TypedValue::Type::Duration:
return TypedValue("DURATION", ctx.memory);
case TypedValue::Type::Graph:
throw QueryRuntimeException("Cannot fetch graph as it is not standardized openCypher type name");
}
}

View File

@ -313,6 +313,25 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
}
return std::nullopt;
};
auto maybe_graph = [this](const auto &graph, const auto &prop_name) -> std::optional<TypedValue> {
if (prop_name == "nodes") {
utils::pmr::vector<TypedValue> vertices(ctx_->memory);
vertices.reserve(graph.vertices().size());
for (const auto &v : graph.vertices()) {
vertices.emplace_back(TypedValue(v, ctx_->memory));
}
return TypedValue(vertices, ctx_->memory);
}
if (prop_name == "edges") {
utils::pmr::vector<TypedValue> edges(ctx_->memory);
edges.reserve(graph.edges().size());
for (const auto &e : graph.edges()) {
edges.emplace_back(TypedValue(e, ctx_->memory));
}
return TypedValue(edges, ctx_->memory);
}
return std::nullopt;
};
switch (expression_result.type()) {
case TypedValue::Type::Null:
return TypedValue(ctx_->memory);
@ -365,6 +384,14 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
}
throw QueryRuntimeException("Invalid property name {} for LocalDateTime", prop_name);
}
case TypedValue::Type::Graph: {
const auto &prop_name = property_lookup.property_.name;
const auto &graph = expression_result.ValueGraph();
if (auto graph_field = maybe_graph(graph, prop_name); graph_field) {
return std::move(*graph_field);
}
throw QueryRuntimeException("Invalid property name {} for Graph", prop_name);
}
default:
throw QueryRuntimeException("Only nodes, edges, maps and temporal types have properties to be looked-up.");
}

View File

@ -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.

View File

@ -31,6 +31,7 @@
#include "query/exceptions.hpp"
#include "query/frontend/ast/ast.hpp"
#include "query/frontend/semantic/symbol_table.hpp"
#include "query/graph.hpp"
#include "query/interpret/eval.hpp"
#include "query/path.hpp"
#include "query/plan/scoped_profile.hpp"
@ -2918,6 +2919,8 @@ TypedValue DefaultAggregationOpValue(const Aggregate::Element &element, utils::M
return TypedValue(TypedValue::TVector(memory));
case Aggregation::Op::COLLECT_MAP:
return TypedValue(TypedValue::TMap(memory));
case Aggregation::Op::PROJECT:
return TypedValue(query::Graph(memory));
}
}
} // namespace
@ -3080,7 +3083,6 @@ class AggregateCursor : public Cursor {
"Expected as much AggregationValue.counts_ as there are "
"aggregations.");
// we iterate over counts, values and aggregation info at the same time
auto count_it = agg_value->counts_.begin();
auto value_it = agg_value->values_.begin();
auto agg_elem_it = self_.aggregations_.begin();
@ -3119,6 +3121,11 @@ class AggregateCursor : public Cursor {
case Aggregation::Op::COLLECT_LIST:
value_it->ValueList().push_back(input_value);
break;
case Aggregation::Op::PROJECT: {
EnsureOkForProject(input_value);
value_it->ValueGraph().Expand(input_value.ValuePath());
break;
}
case Aggregation::Op::COLLECT_MAP:
auto key = agg_elem_it->key->Accept(*evaluator);
if (key.type() != TypedValue::Type::String) throw QueryRuntimeException("Map key must be a string.");
@ -3167,6 +3174,11 @@ class AggregateCursor : public Cursor {
case Aggregation::Op::COLLECT_LIST:
value_it->ValueList().push_back(input_value);
break;
case Aggregation::Op::PROJECT: {
EnsureOkForProject(input_value);
value_it->ValueGraph().Expand(input_value.ValuePath());
break;
}
case Aggregation::Op::COLLECT_MAP:
auto key = agg_elem_it->key->Accept(*evaluator);
if (key.type() != TypedValue::Type::String) throw QueryRuntimeException("Map key must be a string.");
@ -3203,6 +3215,18 @@ class AggregateCursor : public Cursor {
throw QueryRuntimeException("Only numeric values allowed in SUM and AVG aggregations.");
}
}
/** Checks if the given TypedValue is legal in PROJECT and PROJECT_TRANSITIVE. If not
* an appropriate exception is thrown. */
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
void EnsureOkForProject(const TypedValue &value) const {
switch (value.type()) {
case TypedValue::Type::Path:
return;
default:
throw QueryRuntimeException("Only path values allowed in PROJECT aggregation.");
}
}
};
UniqueCursorPtr Aggregate::MakeCursor(utils::MemoryResource *mem) const {
@ -4015,6 +4039,18 @@ void CallCustomProcedure(const std::string_view fully_qualified_procedure_name,
for (auto *expression : args) {
args_list.emplace_back(expression->Accept(*evaluator));
}
std::optional<query::Graph> subgraph;
std::optional<query::SubgraphDbAccessor> db_acc;
if (!args_list.empty() && args_list.front().type() == TypedValue::Type::Graph) {
auto subgraph_value = args_list.front().ValueGraph();
subgraph = query::Graph(std::move(subgraph_value), subgraph_value.GetMemoryResource());
args_list.erase(args_list.begin());
db_acc = query::SubgraphDbAccessor(*std::get<query::DbAccessor *>(graph.impl), &*subgraph);
graph.impl = &*db_acc;
}
procedure::ConstructArguments(args_list, proc, fully_qualified_procedure_name, proc_args, graph);
if (memory_limit) {
SPDLOG_INFO("Running '{}' with memory limit of {}", fully_qualified_procedure_name,

View File

@ -38,6 +38,9 @@
#include "utils/temporal.hpp"
#include "utils/variant_helpers.hpp"
#include <cppitertools/filter.hpp>
#include <cppitertools/imap.hpp>
// This file contains implementation of top level C API functions, but this is
// all actually part of memgraph::query::procedure. So use that namespace for simplicity.
// NOLINTNEXTLINE(google-build-using-namespace)
@ -290,6 +293,8 @@ mgp_value_type FromTypedValueType(memgraph::query::TypedValue::Type type) {
return MGP_VALUE_TYPE_LOCAL_DATE_TIME;
case memgraph::query::TypedValue::Type::Duration:
return MGP_VALUE_TYPE_DURATION;
case memgraph::query::TypedValue::Type::Graph:
throw std::logic_error{"mgp_value for TypedValue::Type::Graph doesn't exist."};
}
}
} // namespace
@ -324,17 +329,17 @@ memgraph::query::TypedValue ToTypedValue(const mgp_value &val, memgraph::utils::
return memgraph::query::TypedValue(std::move(tv_map));
}
case MGP_VALUE_TYPE_VERTEX:
return memgraph::query::TypedValue(val.vertex_v->impl, memory);
return memgraph::query::TypedValue(val.vertex_v->getImpl(), memory);
case MGP_VALUE_TYPE_EDGE:
return memgraph::query::TypedValue(val.edge_v->impl, memory);
case MGP_VALUE_TYPE_PATH: {
const auto *path = val.path_v;
MG_ASSERT(!path->vertices.empty());
MG_ASSERT(path->vertices.size() == path->edges.size() + 1);
memgraph::query::Path tv_path(path->vertices[0].impl, memory);
memgraph::query::Path tv_path(path->vertices[0].getImpl(), memory);
for (size_t i = 0; i < path->edges.size(); ++i) {
tv_path.Expand(path->edges[i].impl);
tv_path.Expand(path->vertices[i + 1].impl);
tv_path.Expand(path->vertices[i + 1].getImpl());
}
return memgraph::query::TypedValue(std::move(tv_path));
}
@ -454,12 +459,31 @@ mgp_value::mgp_value(const memgraph::query::TypedValue &tv, mgp_graph *graph, me
}
case MGP_VALUE_TYPE_VERTEX: {
memgraph::utils::Allocator<mgp_vertex> allocator(m);
vertex_v = allocator.new_object<mgp_vertex>(tv.ValueVertex(), graph);
vertex_v = std::visit(
memgraph::utils::Overloaded{
[&](memgraph::query::DbAccessor *) { return allocator.new_object<mgp_vertex>(tv.ValueVertex(), graph); },
[&](memgraph::query::SubgraphDbAccessor *impl) {
return allocator.new_object<mgp_vertex>(
memgraph::query::SubgraphVertexAccessor(tv.ValueVertex(), impl->getGraph()), graph);
}},
graph->impl);
break;
}
case MGP_VALUE_TYPE_EDGE: {
memgraph::utils::Allocator<mgp_edge> allocator(m);
edge_v = allocator.new_object<mgp_edge>(tv.ValueEdge(), graph);
edge_v = std::visit(
memgraph::utils::Overloaded{
[&tv, graph, &allocator](memgraph::query::DbAccessor *) {
return allocator.new_object<mgp_edge>(tv.ValueEdge(), graph);
},
[&tv, graph, &allocator](memgraph::query::SubgraphDbAccessor *db_impl) {
return allocator.new_object<mgp_edge>(
tv.ValueEdge(), memgraph::query::SubgraphVertexAccessor(tv.ValueEdge().From(), db_impl->getGraph()),
memgraph::query::SubgraphVertexAccessor(tv.ValueEdge().To(), db_impl->getGraph()), graph);
}},
graph->impl);
break;
}
case MGP_VALUE_TYPE_PATH: {
@ -470,11 +494,24 @@ mgp_value::mgp_value(const memgraph::query::TypedValue &tv, mgp_graph *graph, me
mgp_path tmp_path(m);
tmp_path.vertices.reserve(tv.ValuePath().vertices().size());
for (const auto &v : tv.ValuePath().vertices()) {
tmp_path.vertices.emplace_back(v, graph);
std::visit(
memgraph::utils::Overloaded{
[&v, graph, &tmp_path](memgraph::query::DbAccessor *) { tmp_path.vertices.emplace_back(v, graph); },
[&v, graph, &tmp_path](memgraph::query::SubgraphDbAccessor *impl) {
tmp_path.vertices.emplace_back(memgraph::query::SubgraphVertexAccessor(v, impl->getGraph()), graph);
}},
graph->impl);
}
tmp_path.edges.reserve(tv.ValuePath().edges().size());
for (const auto &e : tv.ValuePath().edges()) {
tmp_path.edges.emplace_back(e, graph);
std::visit(memgraph::utils::Overloaded{
[&e, graph, &tmp_path](memgraph::query::DbAccessor *) { tmp_path.edges.emplace_back(e, graph); },
[&e, graph, &tmp_path](memgraph::query::SubgraphDbAccessor *db_impl) {
tmp_path.edges.emplace_back(
e, memgraph::query::SubgraphVertexAccessor(e.From(), db_impl->getGraph()),
memgraph::query::SubgraphVertexAccessor(e.To(), db_impl->getGraph()), graph);
}},
graph->impl);
}
memgraph::utils::Allocator<mgp_path> allocator(m);
path_v = allocator.new_object<mgp_path>(std::move(tmp_path));
@ -799,7 +836,15 @@ mgp_value::mgp_value(mgp_value &&other, memgraph::utils::MemoryResource *m) : ty
mgp_value::~mgp_value() noexcept { DeleteValueMember(this); }
mgp_edge *mgp_edge::Copy(const mgp_edge &edge, mgp_memory &memory) {
return NewRawMgpObject<mgp_edge>(&memory, edge.impl, edge.from.graph);
return std::visit(
memgraph::utils::Overloaded{
[&](memgraph::query::DbAccessor *) { return NewRawMgpObject<mgp_edge>(&memory, edge.impl, edge.from.graph); },
[&](memgraph::query::SubgraphDbAccessor *db_impl) {
return NewRawMgpObject<mgp_edge>(
&memory, edge.impl, memgraph::query::SubgraphVertexAccessor(edge.impl.From(), db_impl->getGraph()),
memgraph::query::SubgraphVertexAccessor(edge.impl.To(), db_impl->getGraph()), edge.to.graph);
}},
edge.to.graph->impl);
}
void mgp_value_destroy(mgp_value *val) { DeleteRawMgpObject(val); }
@ -1130,7 +1175,7 @@ mgp_error mgp_path_equal(mgp_path *p1, mgp_path *p2, int *result) {
}
const auto *start1 = Call<mgp_vertex *>(mgp_path_vertex_at, p1, 0);
const auto *start2 = Call<mgp_vertex *>(mgp_path_vertex_at, p2, 0);
static_assert(noexcept(start1->impl == start2->impl));
static_assert(noexcept(start1 == start2));
if (*start1 != *start2) {
return 0;
}
@ -1497,9 +1542,12 @@ mgp_error mgp_properties_iterator_next(mgp_properties_iterator *it, mgp_property
return nullptr;
}
memgraph::utils::OnScopeExit clean_up([it] { it->current = std::nullopt; });
it->current.emplace(memgraph::utils::pmr::string(it->graph->impl->PropertyToName(it->current_it->first),
it->GetMemoryResource()),
mgp_value(it->current_it->second, it->GetMemoryResource()));
auto propToName = std::visit(
[it](auto *impl) {
return memgraph::utils::pmr::string(impl->PropertyToName(it->current_it->first), it->GetMemoryResource());
},
it->graph->impl);
it->current.emplace(propToName, mgp_value(it->current_it->second, it->GetMemoryResource()));
it->property.name = it->current->first.c_str();
it->property.value = &it->current->second;
clean_up.Disable();
@ -1509,7 +1557,9 @@ mgp_error mgp_properties_iterator_next(mgp_properties_iterator *it, mgp_property
}
mgp_error mgp_vertex_get_id(mgp_vertex *v, mgp_vertex_id *result) {
return WrapExceptions([v] { return mgp_vertex_id{.as_int = v->impl.Gid().AsInt()}; }, result);
return WrapExceptions(
[v] { return mgp_vertex_id{.as_int = std::visit([](auto &impl) { return impl.Gid().AsInt(); }, v->impl)}; },
result);
}
mgp_error mgp_vertex_underlying_graph_is_mutable(mgp_vertex *v, int *result) {
@ -1581,8 +1631,13 @@ mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_nam
if (!MgpVertexIsMutable(*v)) {
throw ImmutableObjectException{"Cannot set a property on an immutable vertex!"};
}
const auto prop_key = v->graph->impl->NameToProperty(property_name);
const auto result = v->impl.SetProperty(prop_key, ToPropertyValue(*property_value));
const auto prop_key =
std::visit([property_name](auto *impl) { return impl->NameToProperty(property_name); }, v->graph->impl);
const auto result = std::visit(
[prop_key, property_value](auto &impl) { return impl.SetProperty(prop_key, ToPropertyValue(*property_value)); },
v->impl);
if (result.HasError()) {
switch (result.GetError()) {
case memgraph::storage::Error::DELETED_OBJECT:
@ -1608,11 +1663,11 @@ mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_nam
}
const auto old_value = memgraph::query::TypedValue(*result);
if (property_value->type == mgp_value_type::MGP_VALUE_TYPE_NULL) {
trigger_ctx_collector->RegisterRemovedObjectProperty(v->impl, prop_key, old_value);
trigger_ctx_collector->RegisterRemovedObjectProperty(v->getImpl(), prop_key, old_value);
return;
}
const auto new_value = ToTypedValue(*property_value, property_value->memory);
trigger_ctx_collector->RegisterSetObjectProperty(v->impl, prop_key, old_value, new_value);
trigger_ctx_collector->RegisterSetObjectProperty(v->getImpl(), prop_key, old_value, new_value);
});
}
@ -1621,8 +1676,9 @@ mgp_error mgp_vertex_add_label(struct mgp_vertex *v, mgp_label label) {
if (!MgpVertexIsMutable(*v)) {
throw ImmutableObjectException{"Cannot add a label to an immutable vertex!"};
}
const auto label_id = v->graph->impl->NameToLabel(label.name);
const auto result = v->impl.AddLabel(label_id);
const auto label_id = std::visit([label](auto *impl) { return impl->NameToLabel(label.name); }, v->graph->impl);
const auto result = std::visit([label_id](auto &impl) { return impl.AddLabel(label_id); }, v->impl);
if (result.HasError()) {
switch (result.GetError()) {
@ -1643,7 +1699,7 @@ mgp_error mgp_vertex_add_label(struct mgp_vertex *v, mgp_label label) {
ctx->execution_stats[memgraph::query::ExecutionStats::Key::CREATED_LABELS] += 1;
if (ctx->trigger_context_collector) {
ctx->trigger_context_collector->RegisterSetVertexLabel(v->impl, label_id);
ctx->trigger_context_collector->RegisterSetVertexLabel(v->getImpl(), label_id);
}
});
}
@ -1653,8 +1709,8 @@ mgp_error mgp_vertex_remove_label(struct mgp_vertex *v, mgp_label label) {
if (!MgpVertexIsMutable(*v)) {
throw ImmutableObjectException{"Cannot remove a label from an immutable vertex!"};
}
const auto label_id = v->graph->impl->NameToLabel(label.name);
const auto result = v->impl.RemoveLabel(label_id);
const auto label_id = std::visit([&label](auto *impl) { return impl->NameToLabel(label.name); }, v->graph->impl);
const auto result = std::visit([label_id](auto &impl) { return impl.RemoveLabel(label_id); }, v->impl);
if (result.HasError()) {
switch (result.GetError()) {
@ -1675,7 +1731,7 @@ mgp_error mgp_vertex_remove_label(struct mgp_vertex *v, mgp_label label) {
ctx->execution_stats[memgraph::query::ExecutionStats::Key::DELETED_LABELS] += 1;
if (ctx->trigger_context_collector) {
ctx->trigger_context_collector->RegisterRemovedVertexLabel(v->impl, label_id);
ctx->trigger_context_collector->RegisterRemovedVertexLabel(v->getImpl(), label_id);
}
});
}
@ -1696,7 +1752,7 @@ mgp_error mgp_vertex_equal(mgp_vertex *v1, mgp_vertex *v2, int *result) {
mgp_error mgp_vertex_labels_count(mgp_vertex *v, size_t *result) {
return WrapExceptions(
[v]() -> size_t {
auto maybe_labels = v->impl.Labels(v->graph->view);
auto maybe_labels = std::visit([v](const auto &impl) { return impl.Labels(v->graph->view); }, v->impl);
if (maybe_labels.HasError()) {
switch (maybe_labels.GetError()) {
case memgraph::storage::Error::DELETED_OBJECT:
@ -1718,7 +1774,7 @@ mgp_error mgp_vertex_label_at(mgp_vertex *v, size_t i, mgp_label *result) {
return WrapExceptions(
[v, i]() -> const char * {
// TODO: Maybe it's worth caching this in mgp_vertex.
auto maybe_labels = v->impl.Labels(v->graph->view);
auto maybe_labels = std::visit([v](const auto &impl) { return impl.Labels(v->graph->view); }, v->impl);
if (maybe_labels.HasError()) {
switch (maybe_labels.GetError()) {
case memgraph::storage::Error::DELETED_OBJECT:
@ -1735,10 +1791,12 @@ mgp_error mgp_vertex_label_at(mgp_vertex *v, size_t i, mgp_label *result) {
throw std::out_of_range("Label cannot be retrieved, because index exceeds the number of labels!");
}
const auto &label = (*maybe_labels)[i];
static_assert(std::is_lvalue_reference_v<decltype(v->graph->impl->LabelToName(label))>,
static_assert(std::is_lvalue_reference_v<
decltype(std::get<memgraph::query::DbAccessor *>(v->graph->impl)->LabelToName(label))>,
"Expected LabelToName to return a pointer or reference, so we "
"don't have to take a copy and manage memory.");
const auto &name = v->graph->impl->LabelToName(label);
const auto &name = std::visit([label](const auto *impl) { return impl->LabelToName(label); }, v->graph->impl);
return name.c_str();
},
&result->name);
@ -1748,9 +1806,10 @@ mgp_error mgp_vertex_has_label_named(mgp_vertex *v, const char *name, int *resul
return WrapExceptions(
[v, name] {
memgraph::storage::LabelId label;
label = v->graph->impl->NameToLabel(name);
label = std::visit([name](auto *impl) { return impl->NameToLabel(name); }, v->graph->impl);
auto maybe_has_label = v->impl.HasLabel(v->graph->view, label);
auto maybe_has_label =
std::visit([v, label](auto &impl) { return impl.HasLabel(v->graph->view, label); }, v->impl);
if (maybe_has_label.HasError()) {
switch (maybe_has_label.GetError()) {
case memgraph::storage::Error::DELETED_OBJECT:
@ -1778,8 +1837,9 @@ mgp_error mgp_vertex_has_label(mgp_vertex *v, mgp_label label, int *result) {
mgp_error mgp_vertex_get_property(mgp_vertex *v, const char *name, mgp_memory *memory, mgp_value **result) {
return WrapExceptions(
[v, name, memory]() -> mgp_value * {
const auto &key = v->graph->impl->NameToProperty(name);
auto maybe_prop = v->impl.GetProperty(v->graph->view, key);
const auto &key = std::visit([name](auto *impl) { return impl->NameToProperty(name); }, v->graph->impl);
auto maybe_prop = std::visit([v, key](auto &impl) { return impl.GetProperty(v->graph->view, key); }, v->impl);
if (maybe_prop.HasError()) {
switch (maybe_prop.GetError()) {
case memgraph::storage::Error::DELETED_OBJECT:
@ -1805,7 +1865,7 @@ mgp_error mgp_vertex_iter_properties(mgp_vertex *v, mgp_memory *memory, mgp_prop
// will probably require a different API in storage.
return WrapExceptions(
[v, memory] {
auto maybe_props = v->impl.Properties(v->graph->view);
auto maybe_props = std::visit([v](auto &impl) { return impl.Properties(v->graph->view); }, v->impl);
if (maybe_props.HasError()) {
switch (maybe_props.GetError()) {
case memgraph::storage::Error::DELETED_OBJECT:
@ -1833,7 +1893,7 @@ mgp_error mgp_vertex_iter_in_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges_
auto it = NewMgpObject<mgp_edges_iterator>(memory, *v);
MG_ASSERT(it != nullptr);
auto maybe_edges = v->impl.InEdges(v->graph->view);
auto maybe_edges = std::visit([v](auto &impl) { return impl.InEdges(v->graph->view); }, v->impl);
if (maybe_edges.HasError()) {
switch (maybe_edges.GetError()) {
case memgraph::storage::Error::DELETED_OBJECT:
@ -1851,7 +1911,19 @@ mgp_error mgp_vertex_iter_in_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges_
it->in.emplace(std::move(*maybe_edges));
it->in_it.emplace(it->in->begin());
if (*it->in_it != it->in->end()) {
it->current_e.emplace(**it->in_it, v->graph, it->GetMemoryResource());
std::visit(memgraph::utils::Overloaded{
[&](memgraph::query::DbAccessor *) {
it->current_e.emplace(**it->in_it, (**it->in_it).From(), (**it->in_it).To(), v->graph,
it->GetMemoryResource());
},
[&](memgraph::query::SubgraphDbAccessor *impl) {
it->current_e.emplace(
**it->in_it,
memgraph::query::SubgraphVertexAccessor((**it->in_it).From(), impl->getGraph()),
memgraph::query::SubgraphVertexAccessor((**it->in_it).To(), impl->getGraph()), v->graph,
it->GetMemoryResource());
}},
v->graph->impl);
}
return it.release();
@ -1864,8 +1936,8 @@ mgp_error mgp_vertex_iter_out_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges
[v, memory] {
auto it = NewMgpObject<mgp_edges_iterator>(memory, *v);
MG_ASSERT(it != nullptr);
auto maybe_edges = std::visit([v](auto &impl) { return impl.OutEdges(v->graph->view); }, v->impl);
auto maybe_edges = v->impl.OutEdges(v->graph->view);
if (maybe_edges.HasError()) {
switch (maybe_edges.GetError()) {
case memgraph::storage::Error::DELETED_OBJECT:
@ -1880,10 +1952,23 @@ mgp_error mgp_vertex_iter_out_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges
LOG_FATAL("Unexpected error when getting the outbound edges of a vertex.");
}
}
it->out.emplace(std::move(*maybe_edges));
it->out_it.emplace(it->out->begin());
if (*it->out_it != it->out->end()) {
it->current_e.emplace(**it->out_it, v->graph, it->GetMemoryResource());
std::visit(memgraph::utils::Overloaded{
[&](memgraph::query::DbAccessor *) {
it->current_e.emplace(**it->out_it, (**it->out_it).From(), (**it->out_it).To(), v->graph,
it->GetMemoryResource());
},
[&](memgraph::query::SubgraphDbAccessor *impl) {
it->current_e.emplace(
**it->out_it,
memgraph::query::SubgraphVertexAccessor((**it->out_it).From(), impl->getGraph()),
memgraph::query::SubgraphVertexAccessor((**it->out_it).To(), impl->getGraph()), v->graph,
it->GetMemoryResource());
}},
v->graph->impl);
}
return it.release();
@ -1921,7 +2006,19 @@ mgp_error mgp_edges_iterator_next(mgp_edges_iterator *it, mgp_edge **result) {
it->current_e = std::nullopt;
return nullptr;
}
it->current_e.emplace(**impl_it, it->source_vertex.graph, it->GetMemoryResource());
std::visit(memgraph::utils::Overloaded{
[&](memgraph::query::DbAccessor *) {
it->current_e.emplace(**impl_it, (**impl_it).From(), (**impl_it).To(),
it->source_vertex.graph, it->GetMemoryResource());
},
[&](memgraph::query::SubgraphDbAccessor *impl) {
it->current_e.emplace(
**impl_it, memgraph::query::SubgraphVertexAccessor((**impl_it).From(), impl->getGraph()),
memgraph::query::SubgraphVertexAccessor((**impl_it).To(), impl->getGraph()),
it->source_vertex.graph, it->GetMemoryResource());
}},
it->source_vertex.graph->impl);
return &*it->current_e;
};
if (it->in_it) {
@ -1956,10 +2053,8 @@ mgp_error mgp_edge_equal(mgp_edge *e1, mgp_edge *e2, int *result) {
mgp_error mgp_edge_get_type(mgp_edge *e, mgp_edge_type *result) {
return WrapExceptions(
[e] {
const auto &name = e->from.graph->impl->EdgeTypeToName(e->impl.EdgeType());
static_assert(std::is_lvalue_reference_v<decltype(e->from.graph->impl->EdgeTypeToName(e->impl.EdgeType()))>,
"Expected EdgeTypeToName to return a pointer or reference, so we "
"don't have to take a copy and manage memory.");
const auto &name =
std::visit([e](const auto *impl) { return impl->EdgeTypeToName(e->impl.EdgeType()); }, e->from.graph->impl);
return name.c_str();
},
&result->name);
@ -1978,7 +2073,7 @@ mgp_error mgp_edge_get_to(mgp_edge *e, mgp_vertex **result) {
mgp_error mgp_edge_get_property(mgp_edge *e, const char *name, mgp_memory *memory, mgp_value **result) {
return WrapExceptions(
[e, name, memory] {
const auto &key = e->from.graph->impl->NameToProperty(name);
const auto &key = std::visit([name](auto *impl) { return impl->NameToProperty(name); }, e->from.graph->impl);
auto view = e->from.graph->view;
auto maybe_prop = e->impl.GetProperty(view, key);
if (maybe_prop.HasError()) {
@ -2004,7 +2099,8 @@ mgp_error mgp_edge_set_property(struct mgp_edge *e, const char *property_name, m
if (!MgpEdgeIsMutable(*e)) {
throw ImmutableObjectException{"Cannot set a property on an immutable edge!"};
}
const auto prop_key = e->from.graph->impl->NameToProperty(property_name);
const auto prop_key =
std::visit([property_name](auto *impl) { return impl->NameToProperty(property_name); }, e->from.graph->impl);
const auto result = e->impl.SetProperty(prop_key, ToPropertyValue(*property_value));
if (result.HasError()) {
@ -2071,9 +2167,22 @@ mgp_error mgp_edge_iter_properties(mgp_edge *e, mgp_memory *memory, mgp_properti
mgp_error mgp_graph_get_vertex_by_id(mgp_graph *graph, mgp_vertex_id id, mgp_memory *memory, mgp_vertex **result) {
return WrapExceptions(
[graph, id, memory]() -> mgp_vertex * {
auto maybe_vertex = graph->impl->FindVertex(memgraph::storage::Gid::FromInt(id.as_int), graph->view);
std::optional<memgraph::query::VertexAccessor> maybe_vertex = std::visit(
[graph, id](auto *impl) {
return impl->FindVertex(memgraph::storage::Gid::FromInt(id.as_int), graph->view);
},
graph->impl);
if (maybe_vertex) {
return std::visit(memgraph::utils::Overloaded{
[memory, graph, maybe_vertex](memgraph::query::DbAccessor *) {
return NewRawMgpObject<mgp_vertex>(memory, *maybe_vertex, graph);
},
[memory, graph, maybe_vertex](memgraph::query::SubgraphDbAccessor *impl) {
return NewRawMgpObject<mgp_vertex>(
memory, memgraph::query::SubgraphVertexAccessor(*maybe_vertex, impl->getGraph()),
graph);
}},
graph->impl);
}
return nullptr;
},
@ -2091,15 +2200,16 @@ mgp_error mgp_graph_create_vertex(struct mgp_graph *graph, mgp_memory *memory, m
if (!MgpGraphIsMutable(*graph)) {
throw ImmutableObjectException{"Cannot create a vertex in an immutable graph!"};
}
auto vertex = graph->impl->InsertVertex();
auto *vertex = std::visit(
[=](auto *impl) { return NewRawMgpObject<mgp_vertex>(memory, impl->InsertVertex(), graph); }, graph->impl);
auto &ctx = graph->ctx;
ctx->execution_stats[memgraph::query::ExecutionStats::Key::CREATED_NODES] += 1;
if (ctx->trigger_context_collector) {
ctx->trigger_context_collector->RegisterCreatedObject(vertex);
ctx->trigger_context_collector->RegisterCreatedObject(vertex->getImpl());
}
return NewRawMgpObject<mgp_vertex>(memory, vertex, graph);
return vertex;
},
result);
}
@ -2109,7 +2219,16 @@ mgp_error mgp_graph_delete_vertex(struct mgp_graph *graph, mgp_vertex *vertex) {
if (!MgpGraphIsMutable(*graph)) {
throw ImmutableObjectException{"Cannot remove a vertex from an immutable graph!"};
}
const auto result = graph->impl->RemoveVertex(&vertex->impl);
const auto result =
std::visit(memgraph::utils::Overloaded{
[&](memgraph::query::DbAccessor *impl) {
return impl->RemoveVertex(&std::get<memgraph::query::VertexAccessor>(vertex->impl));
},
[&](memgraph::query::SubgraphDbAccessor *impl) {
return impl->RemoveVertex(&(std::get<memgraph::query::SubgraphVertexAccessor>(vertex->impl)));
}},
graph->impl);
if (result.HasError()) {
switch (result.GetError()) {
@ -2144,7 +2263,15 @@ mgp_error mgp_graph_detach_delete_vertex(struct mgp_graph *graph, mgp_vertex *ve
if (!MgpGraphIsMutable(*graph)) {
throw ImmutableObjectException{"Cannot remove a vertex from an immutable graph!"};
}
const auto result = graph->impl->DetachRemoveVertex(&vertex->impl);
const auto result = std::visit(
memgraph::utils::Overloaded{
[vertex](memgraph::query::DbAccessor *impl) {
return impl->DetachRemoveVertex(&std::get<memgraph::query::VertexAccessor>(vertex->impl));
},
[vertex](memgraph::query::SubgraphDbAccessor *impl) {
return impl->DetachRemoveVertex(&std::get<memgraph::query::SubgraphVertexAccessor>(vertex->impl));
}},
graph->impl);
if (result.HasError()) {
switch (result.GetError()) {
@ -2191,8 +2318,20 @@ mgp_error mgp_graph_create_edge(mgp_graph *graph, mgp_vertex *from, mgp_vertex *
if (!MgpGraphIsMutable(*graph)) {
throw ImmutableObjectException{"Cannot create an edge in an immutable graph!"};
}
auto edge =
std::visit(memgraph::utils::Overloaded{
[from, to, type](memgraph::query::DbAccessor *impl) {
return impl->InsertEdge(&std::get<memgraph::query::VertexAccessor>(from->impl),
&std::get<memgraph::query::VertexAccessor>(to->impl),
impl->NameToEdgeType(type.name));
},
[from, to, type](memgraph::query::SubgraphDbAccessor *impl) {
return impl->InsertEdge(&std::get<memgraph::query::SubgraphVertexAccessor>(from->impl),
&std::get<memgraph::query::SubgraphVertexAccessor>(to->impl),
impl->NameToEdgeType(type.name));
}},
graph->impl);
auto edge = graph->impl->InsertEdge(&from->impl, &to->impl, from->graph->impl->NameToEdgeType(type.name));
if (edge.HasError()) {
switch (edge.GetError()) {
case memgraph::storage::Error::DELETED_OBJECT:
@ -2213,7 +2352,18 @@ mgp_error mgp_graph_create_edge(mgp_graph *graph, mgp_vertex *from, mgp_vertex *
if (ctx->trigger_context_collector) {
ctx->trigger_context_collector->RegisterCreatedObject(*edge);
}
return NewRawMgpObject<mgp_edge>(memory, edge.GetValue(), from->graph);
return std::visit(
memgraph::utils::Overloaded{
[memory, edge, from](memgraph::query::DbAccessor *) {
return NewRawMgpObject<mgp_edge>(memory->impl, edge.GetValue(), from->graph);
},
[memory, edge, from](memgraph::query::SubgraphDbAccessor *db_impl) {
const auto &v_from =
memgraph::query::SubgraphVertexAccessor(edge.GetValue().From(), db_impl->getGraph());
const auto &v_to = memgraph::query::SubgraphVertexAccessor(edge.GetValue().To(), db_impl->getGraph());
return NewRawMgpObject<mgp_edge>(memory->impl, edge.GetValue(), v_from, v_to, from->graph);
}},
graph->impl);
},
result);
}
@ -2223,8 +2373,8 @@ mgp_error mgp_graph_delete_edge(struct mgp_graph *graph, mgp_edge *edge) {
if (!MgpGraphIsMutable(*graph)) {
throw ImmutableObjectException{"Cannot remove an edge from an immutable graph!"};
}
const auto result = graph->impl->RemoveEdge(&edge->impl);
const auto result = std::visit([edge](auto *impl) { return impl->RemoveEdge(&edge->impl); }, graph->impl);
if (result.HasError()) {
switch (result.GetError()) {
case memgraph::storage::Error::NONEXISTENT_OBJECT:
@ -2285,7 +2435,17 @@ mgp_error mgp_vertices_iterator_next(mgp_vertices_iterator *it, mgp_vertex **res
return nullptr;
}
memgraph::utils::OnScopeExit clean_up([it] { it->current_v = std::nullopt; });
it->current_v.emplace(*it->current_it, it->graph, it->GetMemoryResource());
std::visit(memgraph::utils::Overloaded{[it](memgraph::query::DbAccessor *) {
it->current_v.emplace(*it->current_it, it->graph,
it->GetMemoryResource());
},
[it](memgraph::query::SubgraphDbAccessor *impl) {
it->current_v.emplace(memgraph::query::SubgraphVertexAccessor(
*it->current_it, impl->getGraph()),
it->graph, it->GetMemoryResource());
}},
it->graph->impl);
clean_up.Disable();
return &*it->current_v;
},
@ -2568,6 +2728,7 @@ std::ostream &PrintValue(const TypedValue &value, std::ostream *stream) {
case TypedValue::Type::Vertex:
case TypedValue::Type::Edge:
case TypedValue::Type::Path:
case TypedValue::Type::Graph:
LOG_FATAL("value must not be a graph element");
}
}

View File

@ -32,6 +32,7 @@
#include "utils/pmr/string.hpp"
#include "utils/pmr/vector.hpp"
#include "utils/temporal.hpp"
#include "utils/variant_helpers.hpp"
/// Wraps memory resource used in custom procedures.
///
/// This should have been `using mgp_memory = memgraph::utils::MemoryResource`, but that's
@ -442,6 +443,10 @@ struct mgp_vertex {
mgp_vertex(memgraph::query::VertexAccessor v, mgp_graph *graph, memgraph::utils::MemoryResource *memory) noexcept
: memory(memory), impl(v), graph(graph) {}
mgp_vertex(memgraph::query::SubgraphVertexAccessor v, mgp_graph *graph,
memgraph::utils::MemoryResource *memory) noexcept
: memory(memory), impl(v), graph(graph) {}
mgp_vertex(const mgp_vertex &other, memgraph::utils::MemoryResource *memory) noexcept
: memory(memory), impl(other.impl), graph(other.graph) {}
@ -450,13 +455,21 @@ struct mgp_vertex {
mgp_vertex(mgp_vertex &&other) noexcept : memory(other.memory), impl(other.impl), graph(other.graph) {}
memgraph::query::VertexAccessor getImpl() const {
return std::visit(
memgraph::utils::Overloaded{[](memgraph::query::VertexAccessor impl) { return impl; },
[](memgraph::query::SubgraphVertexAccessor impl) { return impl.impl_; }},
this->impl);
}
/// Copy construction without memgraph::utils::MemoryResource is not allowed.
mgp_vertex(const mgp_vertex &) = delete;
mgp_vertex &operator=(const mgp_vertex &) = delete;
mgp_vertex &operator=(mgp_vertex &&) = delete;
bool operator==(const mgp_vertex &other) const noexcept { return this->impl == other.impl; }
bool operator==(const mgp_vertex &other) const noexcept { return other.getImpl() == this->getImpl(); }
bool operator!=(const mgp_vertex &other) const noexcept { return !(*this == other); };
~mgp_vertex() = default;
@ -464,7 +477,7 @@ struct mgp_vertex {
memgraph::utils::MemoryResource *GetMemoryResource() const noexcept { return memory; }
memgraph::utils::MemoryResource *memory;
memgraph::query::VertexAccessor impl;
std::variant<memgraph::query::VertexAccessor, memgraph::query::SubgraphVertexAccessor> impl;
mgp_graph *graph;
};
@ -484,6 +497,16 @@ struct mgp_edge {
memgraph::utils::MemoryResource *memory) noexcept
: memory(memory), impl(impl), from(impl.From(), graph, memory), to(impl.To(), graph, memory) {}
mgp_edge(const memgraph::query::EdgeAccessor &impl, const memgraph::query::VertexAccessor &from_v,
const memgraph::query::VertexAccessor &to_v, mgp_graph *graph,
memgraph::utils::MemoryResource *memory) noexcept
: memory(memory), impl(impl), from(from_v, graph, memory), to(to_v, graph, memory) {}
mgp_edge(const memgraph::query::EdgeAccessor &impl, const memgraph::query::SubgraphVertexAccessor &from_v,
const memgraph::query::SubgraphVertexAccessor &to_v, mgp_graph *graph,
memgraph::utils::MemoryResource *memory) noexcept
: memory(memory), impl(impl), from(from_v, graph, memory), to(to_v, graph, memory) {}
mgp_edge(const mgp_edge &other, memgraph::utils::MemoryResource *memory) noexcept
: memory(memory), impl(other.impl), from(other.from, memory), to(other.to, memory) {}
@ -541,6 +564,32 @@ struct mgp_path {
memgraph::utils::pmr::vector<mgp_edge> edges;
};
struct mgp_graph {
std::variant<memgraph::query::DbAccessor *, memgraph::query::SubgraphDbAccessor *> impl;
memgraph::storage::View view;
// TODO: Merge `mgp_graph` and `mgp_memory` into a single `mgp_context`. The
// `ctx` field is out of place here.
memgraph::query::ExecutionContext *ctx;
static mgp_graph WritableGraph(memgraph::query::DbAccessor &acc, memgraph::storage::View view,
memgraph::query::ExecutionContext &ctx) {
return mgp_graph{&acc, view, &ctx};
}
static mgp_graph NonWritableGraph(memgraph::query::DbAccessor &acc, memgraph::storage::View view) {
return mgp_graph{&acc, view, nullptr};
}
static mgp_graph WritableGraph(memgraph::query::SubgraphDbAccessor &acc, memgraph::storage::View view,
memgraph::query::ExecutionContext &ctx) {
return mgp_graph{&acc, view, &ctx};
}
static mgp_graph NonWritableGraph(memgraph::query::SubgraphDbAccessor &acc, memgraph::storage::View view) {
return mgp_graph{&acc, view, nullptr};
}
};
struct mgp_result_record {
/// Result record signature as defined for mgp_proc.
const memgraph::utils::pmr::map<memgraph::utils::pmr::string,
@ -570,23 +619,6 @@ struct mgp_func_result {
std::optional<memgraph::utils::pmr::string> error_msg;
};
struct mgp_graph {
memgraph::query::DbAccessor *impl;
memgraph::storage::View view;
// TODO: Merge `mgp_graph` and `mgp_memory` into a single `mgp_context`. The
// `ctx` field is out of place here.
memgraph::query::ExecutionContext *ctx;
static mgp_graph WritableGraph(memgraph::query::DbAccessor &acc, memgraph::storage::View view,
memgraph::query::ExecutionContext &ctx) {
return mgp_graph{&acc, view, &ctx};
}
static mgp_graph NonWritableGraph(memgraph::query::DbAccessor &acc, memgraph::storage::View view) {
return mgp_graph{&acc, view, nullptr};
}
};
// Prevents user to use ExecutionContext in writable callables
struct mgp_func_context {
memgraph::query::DbAccessor *impl;
@ -615,8 +647,13 @@ struct mgp_properties_iterator {
mgp_properties_iterator(mgp_graph *graph, decltype(pvs) pvs, memgraph::utils::MemoryResource *memory)
: memory(memory), graph(graph), pvs(std::move(pvs)), current_it(this->pvs.begin()) {
if (current_it != this->pvs.end()) {
current.emplace(memgraph::utils::pmr::string(graph->impl->PropertyToName(current_it->first), memory),
mgp_value(current_it->second, memory));
auto value = std::visit(
[this, memory](const auto *impl) {
return memgraph::utils::pmr::string(impl->PropertyToName(current_it->first), memory);
},
graph->impl);
current.emplace(value, mgp_value(current_it->second, memory));
property.name = current->first.c_str();
property.value = &current->second;
}
@ -635,7 +672,6 @@ struct mgp_properties_iterator {
struct mgp_edges_iterator {
using allocator_type = memgraph::utils::Allocator<mgp_edges_iterator>;
// Hopefully mgp_vertex copy constructor remains noexcept, so that we can
// have everything noexcept here.
static_assert(std::is_nothrow_constructible_v<mgp_vertex, const mgp_vertex &, memgraph::utils::MemoryResource *>);
@ -662,9 +698,14 @@ struct mgp_edges_iterator {
memgraph::utils::MemoryResource *memory;
mgp_vertex source_vertex;
std::optional<std::remove_reference_t<decltype(*source_vertex.impl.InEdges(source_vertex.graph->view))>> in;
std::optional<std::remove_reference_t<
decltype(*std::get<memgraph::query::VertexAccessor>(source_vertex.impl).InEdges(source_vertex.graph->view))>>
in;
std::optional<decltype(in->begin())> in_it;
std::optional<std::remove_reference_t<decltype(*source_vertex.impl.OutEdges(source_vertex.graph->view))>> out;
std::optional<std::remove_reference_t<
decltype(*std::get<memgraph::query::VertexAccessor>(source_vertex.impl).OutEdges(source_vertex.graph->view))>>
out;
std::optional<decltype(out->begin())> out_it;
std::optional<mgp_edge> current_e;
};
@ -674,9 +715,19 @@ struct mgp_vertices_iterator {
/// @throw anything VerticesIterable may throw
mgp_vertices_iterator(mgp_graph *graph, memgraph::utils::MemoryResource *memory)
: memory(memory), graph(graph), vertices(graph->impl->Vertices(graph->view)), current_it(vertices.begin()) {
: memory(memory),
graph(graph),
vertices(std::visit([graph](auto *impl) { return impl->Vertices(graph->view); }, graph->impl)),
current_it(vertices.begin()) {
if (current_it != vertices.end()) {
current_v.emplace(*current_it, graph, memory);
std::visit(
memgraph::utils::Overloaded{
[this, graph, memory](memgraph::query::DbAccessor *) { current_v.emplace(*current_it, graph, memory); },
[this, graph, memory](memgraph::query::SubgraphDbAccessor *impl) {
current_v.emplace(memgraph::query::SubgraphVertexAccessor(*current_it, impl->getGraph()), graph,
memory);
}},
graph->impl);
}
}
@ -684,7 +735,7 @@ struct mgp_vertices_iterator {
memgraph::utils::MemoryResource *memory;
mgp_graph *graph;
decltype(graph->impl->Vertices(graph->view)) vertices;
memgraph::query::VerticesIterable vertices;
decltype(vertices.begin()) current_it;
std::optional<mgp_vertex> current_v;
};

View File

@ -214,6 +214,9 @@ TypedValue::TypedValue(const TypedValue &other, utils::MemoryResource *memory) :
case Type::Duration:
new (&duration_v) utils::Duration(other.duration_v);
return;
case Type::Graph:
new (&graph_v) Graph(other.graph_v, memory_);
return;
}
LOG_FATAL("Unsupported TypedValue::Type");
}
@ -263,6 +266,8 @@ TypedValue::TypedValue(TypedValue &&other, utils::MemoryResource *memory) : memo
case Type::Duration:
new (&duration_v) utils::Duration(other.duration_v);
break;
case Type::Graph:
new (&graph_v) Graph(std::move(other.graph_v), memory_);
}
other.DestroyValue();
}
@ -331,6 +336,7 @@ DEFINE_VALUE_AND_TYPE_GETTERS(utils::Date, Date, date_v)
DEFINE_VALUE_AND_TYPE_GETTERS(utils::LocalTime, LocalTime, local_time_v)
DEFINE_VALUE_AND_TYPE_GETTERS(utils::LocalDateTime, LocalDateTime, local_date_time_v)
DEFINE_VALUE_AND_TYPE_GETTERS(utils::Duration, Duration, duration_v)
DEFINE_VALUE_AND_TYPE_GETTERS(Graph, Graph, graph_v)
#undef DEFINE_VALUE_AND_TYPE_GETTERS
@ -387,6 +393,8 @@ std::ostream &operator<<(std::ostream &os, const TypedValue::Type &type) {
return os << "local_date_time";
case TypedValue::Type::Duration:
return os << "duration";
case TypedValue::Type::Graph:
return os << "graph";
}
LOG_FATAL("Unsupported TypedValue::Type");
}
@ -522,6 +530,9 @@ TypedValue &TypedValue::operator=(const TypedValue &other) {
case TypedValue::Type::Path:
new (&path_v) Path(other.path_v, memory_);
return *this;
case TypedValue::Type::Graph:
new (&graph_v) Graph(other.graph_v, memory_);
return *this;
case Type::Date:
new (&date_v) utils::Date(other.date_v);
return *this;
@ -593,6 +604,9 @@ TypedValue &TypedValue::operator=(TypedValue &&other) noexcept(false) {
case Type::Duration:
new (&duration_v) utils::Duration(other.duration_v);
break;
case Type::Graph:
new (&graph_v) Graph(std::move(other.graph_v), memory_);
break;
}
other.DestroyValue();
}
@ -633,6 +647,9 @@ void TypedValue::DestroyValue() {
case Type::LocalDateTime:
case Type::Duration:
break;
case Type::Graph:
graph_v.~Graph();
break;
}
type_ = TypedValue::Type::Null;
@ -792,6 +809,8 @@ TypedValue operator==(const TypedValue &a, const TypedValue &b) {
return TypedValue(a.ValueLocalDateTime() == b.ValueLocalDateTime(), a.GetMemoryResource());
case TypedValue::Type::Duration:
return TypedValue(a.ValueDuration() == b.ValueDuration(), a.GetMemoryResource());
case TypedValue::Type::Graph:
throw TypedValueException("Unsupported comparison operator");
default:
LOG_FATAL("Unhandled comparison for types");
}
@ -1100,6 +1119,8 @@ size_t TypedValue::Hash::operator()(const TypedValue &value) const {
case TypedValue::Type::Duration:
return utils::DurationHash{}(value.ValueDuration());
break;
case TypedValue::Type::Graph:
throw TypedValueException("Unsupported hash function for Graph");
}
LOG_FATAL("Unhandled TypedValue.type() in hash function");
}

View File

@ -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;
};
/**

View File

@ -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)

View File

@ -22,6 +22,65 @@ def graph_is_mutable(ctx: mgp.ProcCtx) -> mgp.Record(mutable=bool):
return mgp.Record(mutable=ctx.graph.is_mutable())
@mgp.read_proc
def subgraph_empty(ctx: mgp.ProcCtx, arg1: mgp.Any, arg2: mgp.Any, arg3: mgp.Any = 2) -> mgp.Record(result=int):
return mgp.Record(result=1)
@mgp.read_proc
def subgraph_get_vertices(ctx: mgp.ProcCtx) -> mgp.Record(node=mgp.Vertex):
return [mgp.Record(node=vertex) for vertex in ctx.graph.vertices]
@mgp.read_proc
def subgraph_get_out_edges(ctx: mgp.ProcCtx, vertex: mgp.Vertex) -> mgp.Record(edge=mgp.Edge):
return [mgp.Record(edge=edge) for edge in vertex.out_edges]
@mgp.read_proc
def subgraph_get_in_edges(ctx: mgp.ProcCtx, vertex: mgp.Vertex) -> mgp.Record(edge=mgp.Edge):
return [mgp.Record(edge=edge) for edge in vertex.in_edges]
@mgp.read_proc
def subgraph_get_2_hop_edges(ctx: mgp.ProcCtx, vertex: mgp.Vertex) -> mgp.Record(edge=mgp.Edge):
out_edges = vertex.out_edges
records = []
for edge in out_edges:
vertex = edge.to_vertex
properties = vertex.properties
print(properties)
records.extend([mgp.Record(edge=edge) for edge in edge.to_vertex.out_edges])
return records
@mgp.read_proc
def subgraph_get_out_edges_vertex_id(ctx: mgp.ProcCtx, vertex: mgp.Vertex) -> mgp.Record(edge=mgp.Edge):
vertex = ctx.graph.get_vertex_by_id(vertex.id)
return [mgp.Record(edge=edge) for edge in vertex.out_edges]
@mgp.read_proc
def subgraph_get_path_vertices(ctx: mgp.ProcCtx, path: mgp.Path) -> mgp.Record(node=mgp.Vertex):
return [mgp.Record(node=node) for node in path.vertices]
@mgp.read_proc
def subgraph_get_path_edges(ctx: mgp.ProcCtx, path: mgp.Path) -> mgp.Record(edge=mgp.Edge):
return [mgp.Record(edge=edge) for edge in path.edges]
@mgp.read_proc
def subgraph_get_path_vertices_in_subgraph(ctx: mgp.ProcCtx, path: mgp.Path) -> mgp.Record(node=mgp.Vertex):
path_vertices = path.vertices
graph_vertices = ctx.graph.vertices
records = []
for path_vertex in path_vertices:
if path_vertex in graph_vertices:
records.append(mgp.Record(node=path_vertex))
return records
@mgp.read_proc
def log_message(ctx: mgp.ProcCtx, message: str) -> mgp.Record(success=bool):
logger = mgp.Logger()

View File

@ -35,13 +35,12 @@ def detach_delete_vertex(ctx: mgp.ProcCtx, v: mgp.Any) -> mgp.Record():
@mgp.write_proc
def create_edge(ctx: mgp.ProcCtx, from_vertex: mgp.Vertex,
to_vertex: mgp.Vertex,
edge_type: str) -> mgp.Record(e=mgp.Any):
def create_edge(
ctx: mgp.ProcCtx, from_vertex: mgp.Vertex, to_vertex: mgp.Vertex, edge_type: str
) -> mgp.Record(e=mgp.Any):
e = None
try:
e = ctx.graph.create_edge(
from_vertex, to_vertex, mgp.EdgeType(edge_type))
e = ctx.graph.create_edge(from_vertex, to_vertex, mgp.EdgeType(edge_type))
except RuntimeError as ex:
return mgp.Record(e=str(ex))
return mgp.Record(e=e)
@ -54,32 +53,60 @@ def delete_edge(ctx: mgp.ProcCtx, edge: mgp.Edge) -> mgp.Record():
@mgp.write_proc
def set_property(ctx: mgp.ProcCtx, object: mgp.Any,
name: str, value: mgp.Nullable[mgp.Any]) -> mgp.Record():
def set_property(ctx: mgp.ProcCtx, object: mgp.Any, name: str, value: mgp.Nullable[mgp.Any]) -> mgp.Record():
object.properties.set(name, value)
return mgp.Record()
@mgp.write_proc
def add_label(ctx: mgp.ProcCtx, object: mgp.Any,
name: str) -> mgp.Record(o=mgp.Any):
def add_label(ctx: mgp.ProcCtx, object: mgp.Any, name: str) -> mgp.Record(o=mgp.Any):
object.add_label(name)
return mgp.Record(o=object)
@mgp.write_proc
def remove_label(ctx: mgp.ProcCtx, object: mgp.Any,
name: str) -> mgp.Record(o=mgp.Any):
def remove_label(ctx: mgp.ProcCtx, object: mgp.Any, name: str) -> mgp.Record(o=mgp.Any):
object.remove_label(name)
return mgp.Record(o=object)
@mgp.write_proc
def underlying_graph_is_mutable(ctx: mgp.ProcCtx,
object: mgp.Any) -> mgp.Record(mutable=bool):
def underlying_graph_is_mutable(ctx: mgp.ProcCtx, object: mgp.Any) -> mgp.Record(mutable=bool):
return mgp.Record(mutable=object.underlying_graph_is_mutable())
@mgp.write_proc
def graph_is_mutable(ctx: mgp.ProcCtx) -> mgp.Record(mutable=bool):
return mgp.Record(mutable=ctx.graph.is_mutable())
@mgp.write_proc
def subgraph_insert_vertex_get_vertices(ctx: mgp.ProcCtx) -> mgp.Record(node=mgp.Vertex):
ctx.graph.create_vertex()
return [mgp.Record(node=node) for node in ctx.graph.vertices]
@mgp.write_proc
def subgraph_insert_edge_get_vertex_out_edges(
ctx: mgp.ProcCtx, vertex1: mgp.Vertex, vertex2: mgp.Vertex
) -> mgp.Record(edge=mgp.Edge):
ctx.graph.create_edge(vertex1, vertex2, edge_type=mgp.EdgeType("EDGE_TYPE"))
return [mgp.Record(edge=edge) for edge in vertex1.out_edges]
@mgp.write_proc
def subgraph_remove_edge_get_vertex_out_edges(ctx: mgp.ProcCtx, edge: mgp.Edge) -> mgp.Record(edge=mgp.Edge):
from_vertex = edge.from_vertex
ctx.graph.delete_edge(edge)
return [mgp.Record(edge=edge) for edge in from_vertex.out_edges]
@mgp.write_proc
def subgraph_remove_vertex_and_out_edges_get_vertices(
ctx: mgp.ProcCtx, vertex: mgp.Vertex
) -> mgp.Record(node=mgp.Vertex):
out_edges = vertex.out_edges
for edge in out_edges:
ctx.graph.delete_edge(edge)
ctx.graph.delete_vertex(vertex)
return [mgp.Record(node=vertex) for vertex in ctx.graph.vertices]

View File

@ -0,0 +1,319 @@
# Copyright 2022 Memgraph Ltd.
#
# Use of this software is governed by the Business Source License
# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
# License, and you may not use this file except in compliance with the Business Source License.
#
# As of the Change Date specified in that file, in accordance with
# the Business Source License, use of this software will be governed
# by the Apache License, Version 2.0, included in the file
# licenses/APL.txt.
import typing
import mgclient
import sys
import pytest
from common import execute_and_fetch_all, has_n_result_row
def create_subgraph(cursor):
execute_and_fetch_all(cursor, "CREATE (n:Person {id: 1});")
execute_and_fetch_all(cursor, "CREATE (n:Person {id: 2});")
execute_and_fetch_all(cursor, "CREATE (n:Person {id: 3});")
execute_and_fetch_all(cursor, "CREATE (n:Person {id: 4});")
execute_and_fetch_all(cursor, "CREATE (n:Team {id: 5});")
execute_and_fetch_all(cursor, "CREATE (n:Team {id: 6});")
execute_and_fetch_all(cursor, "MATCH (p:Person {id: 1}) MATCH (t:Team {id:5}) CREATE (p)-[:SUPPORTS]->(t);")
execute_and_fetch_all(cursor, "MATCH (p:Person {id: 1}) MATCH (t:Team {id:6}) CREATE (p)-[:SUPPORTS]->(t);")
execute_and_fetch_all(cursor, "MATCH (p:Person {id: 2}) MATCH (t:Team {id:6}) CREATE (p)-[:SUPPORTS]->(t);")
execute_and_fetch_all(cursor, "MATCH (p1:Person {id: 1}) MATCH (p2:Person {id:2}) CREATE (p1)-[:KNOWS]->(p2);")
execute_and_fetch_all(cursor, "MATCH (t1:Team {id: 5}) MATCH (t2:Team {id:6}) CREATE (t1)-[:IS_RIVAL_TO]->(t2);")
execute_and_fetch_all(cursor, "MATCH (p1:Person {id: 3}) MATCH (p2:Person {id:4}) CREATE (p1)-[:KNOWS]->(p2);")
def create_smaller_subgraph(cursor):
execute_and_fetch_all(cursor, "CREATE (n:Person {id: 1});")
execute_and_fetch_all(cursor, "CREATE (n:Person {id: 2});")
execute_and_fetch_all(cursor, "CREATE (n:Team {id: 5});")
execute_and_fetch_all(cursor, "CREATE (n:Team {id: 6});")
execute_and_fetch_all(cursor, "MATCH (p:Person {id: 1}) MATCH (t:Team {id:5}) CREATE (p)-[:SUPPORTS]->(t);")
execute_and_fetch_all(cursor, "MATCH (p:Person {id: 1}) MATCH (t:Team {id:6}) CREATE (p)-[:SUPPORTS]->(t);")
execute_and_fetch_all(cursor, "MATCH (p:Person {id: 2}) MATCH (t:Team {id:6}) CREATE (p)-[:SUPPORTS]->(t);")
def test_is_callable(connection):
cursor = connection.cursor()
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
create_subgraph(cursor)
assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6)
assert has_n_result_row(
cursor,
f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) AS graph CALL read.subgraph_empty(graph, 2, 3) YIELD result RETURN result;",
1,
)
execute_and_fetch_all(
cursor,
f"MATCH (n) DETACH DELETE n;",
)
def test_incorrect_graph_argument_placement(connection):
cursor = connection.cursor()
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
create_subgraph(cursor)
assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6)
queries = [
f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) AS graph CALL read.subgraph_empty(2, graph, 3) YIELD result RETURN result;",
f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) AS graph CALL read.subgraph_empty(2, 3, graph) YIELD result RETURN result;",
]
for query in queries:
with pytest.raises(mgclient.DatabaseError):
execute_and_fetch_all(cursor, query)
execute_and_fetch_all(
cursor,
f"MATCH (n) DETACH DELETE n;",
)
def test_get_vertices(connection):
cursor = connection.cursor()
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
create_subgraph(cursor)
assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6)
assert has_n_result_row(
cursor,
f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) AS graph CALL read.subgraph_get_vertices(graph) YIELD node RETURN node;",
4,
)
execute_and_fetch_all(
cursor,
f"MATCH (n) DETACH DELETE n;",
)
def test_get_out_edges(connection):
cursor = connection.cursor()
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
create_subgraph(cursor)
assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6)
assert has_n_result_row(
cursor,
f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) AS graph MATCH (n1:Person {{id:1}}) CALL read.subgraph_get_out_edges(graph, n1) YIELD edge RETURN edge;",
2,
)
execute_and_fetch_all(
cursor,
f"MATCH (n) DETACH DELETE n;",
)
def test_get_in_edges(connection):
cursor = connection.cursor()
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
create_subgraph(cursor)
assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6)
assert has_n_result_row(
cursor,
f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) AS graph MATCH (t1:Team {{id:6}}) CALL read.subgraph_get_in_edges(graph, t1) YIELD edge RETURN edge;",
2,
)
execute_and_fetch_all(
cursor,
f"MATCH (n) DETACH DELETE n;",
)
def test_get_2_hop_edges(connection):
cursor = connection.cursor()
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
create_subgraph(cursor)
assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6)
assert has_n_result_row(
cursor,
f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) AS graph MATCH (n1:Person {{id:1}}) CALL read.subgraph_get_2_hop_edges(graph, n1) YIELD edge RETURN edge;",
0,
)
execute_and_fetch_all(
cursor,
f"MATCH (n) DETACH DELETE n;",
)
def test_get_out_edges_vertex_id(connection):
cursor = connection.cursor()
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
create_subgraph(cursor=cursor)
assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6)
assert has_n_result_row(
cursor,
f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) AS graph MATCH (n1:Person {{id:1}}) CALL read.subgraph_get_out_edges_vertex_id(graph, n1) YIELD edge RETURN edge;",
2,
)
execute_and_fetch_all(
cursor,
f"MATCH (n) DETACH DELETE n;",
)
def test_subgraph_get_path_vertices(connection):
cursor = connection.cursor()
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
create_subgraph(cursor)
assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6)
assert has_n_result_row(
cursor,
f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) as graph MATCH path=(a:Person {{id: 1}})-[:SUPPORTS]->(b:Team {{id:5}}) CALL read.subgraph_get_path_vertices(graph, path) YIELD node RETURN node;",
2,
)
execute_and_fetch_all(
cursor,
f"MATCH (n) DETACH DELETE n;",
)
def test_subgraph_get_path_edges(connection):
cursor = connection.cursor()
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
create_subgraph(cursor)
assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6)
assert has_n_result_row(
cursor,
f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) as graph MATCH path=(:Person {{id: 1}})-[:SUPPORTS]->(:Team {{id:5}}) CALL read.subgraph_get_path_edges(graph, path) YIELD edge RETURN edge;",
1,
)
execute_and_fetch_all(
cursor,
f"MATCH (n) DETACH DELETE n;",
)
def test_subgraph_get_path_vertices_in_subgraph(connection):
cursor = connection.cursor()
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
create_subgraph(cursor)
assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6)
assert has_n_result_row(
cursor,
f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) as graph MATCH path=(:Person {{id: 1}})-[:SUPPORTS]->(:Team {{id:5}}) CALL read.subgraph_get_path_vertices_in_subgraph(graph, path) YIELD node RETURN node;",
2,
)
execute_and_fetch_all(
cursor,
f"MATCH (n) DETACH DELETE n;",
)
def test_subgraph_insert_vertex_get_vertices(connection):
cursor = connection.cursor()
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
create_subgraph(cursor)
assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6)
assert has_n_result_row(
cursor,
f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) as graph CALL write.subgraph_insert_vertex_get_vertices(graph) YIELD node RETURN node;",
5,
)
execute_and_fetch_all(
cursor,
f"MATCH (n) DETACH DELETE n;",
)
def test_subgraph_insert_edge_get_vertex_out_edges(connection):
cursor = connection.cursor()
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
create_subgraph(cursor)
assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6)
assert has_n_result_row(
cursor,
f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) as graph MATCH (p1:Person {{id:2}}) MATCH (t1:Team {{id:6}}) CALL write.subgraph_insert_edge_get_vertex_out_edges(graph, p1, t1) YIELD edge RETURN edge;",
2,
)
execute_and_fetch_all(
cursor,
f"MATCH (n) DETACH DELETE n;",
)
def test_subgraph_create_edge_both_vertices_not_in_projected_graph_error(connection):
cursor = connection.cursor()
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
create_subgraph(cursor)
assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6)
with pytest.raises(mgclient.DatabaseError):
execute_and_fetch_all(
cursor,
f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) as graph MATCH (p1:Person {{id:2}}) MATCH (p2:Person {{id:4}}) CALL write.subgraph_insert_edge_get_vertex_out_edges(graph, p1, p2) YIELD edge RETURN edge;",
)
execute_and_fetch_all(
cursor,
f"MATCH (n) DETACH DELETE n;",
)
def test_subgraph_remove_edge_get_vertex_out_edges(connection):
cursor = connection.cursor()
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
create_subgraph(cursor)
assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6)
assert has_n_result_row(
cursor,
f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) as graph MATCH (p1:Person {{id:1}})-[e:SUPPORTS]->(t1:Team {{id:5}}) CALL write.subgraph_remove_edge_get_vertex_out_edges(graph, e) YIELD edge RETURN edge;",
1,
)
execute_and_fetch_all(
cursor,
f"MATCH (n) DETACH DELETE n;",
)
def test_subgraph_remove_edge_not_in_subgraph_error(connection):
cursor = connection.cursor()
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
create_subgraph(cursor)
assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6)
with pytest.raises(mgclient.DatabaseError):
execute_and_fetch_all(
cursor,
f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) as graph MATCH (p1:Person {{id:1}})-[e:KNOWS]->(p2:Person {{id:2}}) CALL write.subgraph_remove_edge_get_vertex_out_edges(graph, e) YIELD edge RETURN edge;",
)
execute_and_fetch_all(
cursor,
f"MATCH (n) DETACH DELETE n;",
)
def test_subgraph_remove_vertex_and_out_edges_get_vertices(connection):
cursor = connection.cursor()
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
create_smaller_subgraph(cursor)
assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 4)
assert has_n_result_row(
cursor,
f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) as graph MATCH (p1:Person {{id:1}}) CALL write.subgraph_remove_vertex_and_out_edges_get_vertices(graph, p1) YIELD node RETURN node;",
3,
)
execute_and_fetch_all(
cursor,
f"MATCH (n) DETACH DELETE n;",
)
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-rA"]))

View File

@ -12,3 +12,8 @@ workloads:
proc: "tests/e2e/write_procedures/procedures/"
args: ["write_procedures/simple_write.py"]
<<: *template_cluster
- name: "Graph projection procedures"
binary: "tests/e2e/pytest_runner.sh"
proc: "tests/e2e/write_procedures/procedures/"
args: ["write_procedures/read_subgraph.py"]
<<: *template_cluster

View File

@ -344,3 +344,61 @@ Feature: Aggregations
| count(n) < n.property | count(n.property) | count(n) | avg(n.property) | min(n.property) | max(n.property) | sum(n.property) |
| false | 1 | 1 | 1.0 | 1 | 1 | 1 |
| null | 0 | 1 | null | null | null | 0 |
Scenario: Graph projection test 01:
Given an empty graph
And having executed
"""
CREATE (a{x: 1}), (b{x: 2}), (c{x: 3}), (d{x: 4}), (a)-[:X]->(b), (b)-[:X]->(c), (c)-[:X]->(a), (a)-[:B]->(d)
"""
When executing query:
"""
MATCH p=()-[:X]->() WITH project(p) as graph WITH graph.nodes as nodes UNWIND nodes as n RETURN n.x as x ORDER BY x DESC
"""
Then the result should be:
| x |
| 3 |
| 2 |
| 1 |
Scenario: Graph projection test 02:
Given an empty graph
And having executed
"""
CREATE (a{x: 1}), (b{x: 2}), (c{x: 3}), (d{x: 4}), (a)-[:X]->(b), (b)-[:X]->(c), (c)-[:X]->(a), (a)-[:B]->(d)
"""
When executing query:
"""
MATCH p=()-[:Z]->() WITH project(p) as graph WITH graph.nodes as nodes UNWIND nodes as n RETURN n.x as x ORDER BY x DESC
"""
Then the result should be:
| x |
Scenario: Graph projection test 03:
Given an empty graph
And having executed
"""
CREATE (a{x: 1}), (b{x: 2}), (c{x: 3}), (d{x: 4}), (a)-[:X {prop:1}]->(b), (b)-[:X {prop:2}]->(c), (c)-[:X {prop:3}]->(a), (a)-[:B {prop:4}]->(d)
"""
When executing query:
"""
MATCH p=()-[:X]->() WITH project(p) as graph WITH graph.edges as edges UNWIND edges as e RETURN e.prop as y ORDER BY y DESC
"""
Then the result should be:
| y |
| 3 |
| 2 |
| 1 |
Scenario: Graph projection test 04:
Given an empty graph
And having executed
"""
CREATE (a{x: 1}), (b{x: 2}), (c{x: 3}), (d{x: 4}), (a)-[:X {prop:1}]->(b), (b)-[:X {prop:2}]->(c), (c)-[:X {prop:3}]->(a), (a)-[:B {prop:4}]->(d)
"""
When executing query:
"""
MATCH p=()-[:Z]->() WITH project(p) as graph WITH graph.edges as edges UNWIND edges as e RETURN e.prop as y ORDER BY y DESC
"""
Then the result should be:
| y |

View File

@ -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();
}

View File

@ -30,6 +30,7 @@
#include "storage_test_utils.hpp"
#include "test_utils.hpp"
#include "utils/memory.hpp"
#include "utils/variant_helpers.hpp"
#define EXPECT_SUCCESS(...) EXPECT_EQ(__VA_ARGS__, mgp_error::MGP_ERROR_NO_ERROR)
@ -90,11 +91,21 @@ size_t CountMaybeIterables(TMaybeIterable &&maybe_iterable) {
return std::distance(iterable.begin(), iterable.end());
}
;
void CheckEdgeCountBetween(const MgpVertexPtr &from, const MgpVertexPtr &to, const size_t number_of_edges_between) {
EXPECT_EQ(CountMaybeIterables(from->impl.InEdges(memgraph::storage::View::NEW)), 0);
EXPECT_EQ(CountMaybeIterables(from->impl.OutEdges(memgraph::storage::View::NEW)), number_of_edges_between);
EXPECT_EQ(CountMaybeIterables(to->impl.InEdges(memgraph::storage::View::NEW)), number_of_edges_between);
EXPECT_EQ(CountMaybeIterables(to->impl.OutEdges(memgraph::storage::View::NEW)), 0);
EXPECT_EQ(
CountMaybeIterables(std::visit([](auto impl) { return impl.InEdges(memgraph::storage::View::NEW); }, from->impl)),
0);
EXPECT_EQ(CountMaybeIterables(
std::visit([](auto impl) { return impl.OutEdges(memgraph::storage::View::NEW); }, from->impl)),
number_of_edges_between);
EXPECT_EQ(
CountMaybeIterables(std::visit([](auto impl) { return impl.InEdges(memgraph::storage::View::NEW); }, to->impl)),
number_of_edges_between);
EXPECT_EQ(
CountMaybeIterables(std::visit([](auto impl) { return impl.OutEdges(memgraph::storage::View::NEW); }, to->impl)),
0);
}
} // namespace