diff --git a/src/query/procedure/mg_procedure_impl.cpp b/src/query/procedure/mg_procedure_impl.cpp index f2ac5d14f..296ae400c 100644 --- a/src/query/procedure/mg_procedure_impl.cpp +++ b/src/query/procedure/mg_procedure_impl.cpp @@ -1,11 +1,12 @@ #include "query/procedure/mg_procedure_impl.hpp" +#include #include #include - -#include #include +#include + #include "utils/math.hpp" void *mgp_alloc(mgp_memory *memory, size_t size_in_bytes) { @@ -64,3 +65,500 @@ void mgp_free(mgp_memory *memory, void *const p) { void *const original_ptr = data - bytes_for_header; memory->impl->Deallocate(original_ptr, alloc_size, alloc_align); } + +namespace { + +// May throw whatever the constructor of U throws. `std::bad_alloc` is handled +// by returning nullptr. +template +U *new_mgp_object(utils::MemoryResource *memory, TArgs &&... args) { + utils::Allocator allocator(memory); + try { + return allocator.template new_object(std::forward(args)...); + } catch (const std::bad_alloc &) { + return nullptr; + } +} + +template +U *new_mgp_object(mgp_memory *memory, TArgs &&... args) { + return new_mgp_object(memory->impl, + std::forward(args)...); +} + +// Assume that deallocation and object destruction never throws. If it does, +// we are in big trouble. +template +void delete_mgp_object(T *ptr) noexcept { + if (!ptr) return; + utils::Allocator allocator(ptr->GetMemoryResource()); + allocator.delete_object(ptr); +} + +mgp_value_type FromTypedValueType(query::TypedValue::Type type) { + switch (type) { + case query::TypedValue::Type::Null: + return MGP_VALUE_TYPE_NULL; + case query::TypedValue::Type::Bool: + return MGP_VALUE_TYPE_BOOL; + case query::TypedValue::Type::Int: + return MGP_VALUE_TYPE_INT; + case query::TypedValue::Type::Double: + return MGP_VALUE_TYPE_DOUBLE; + case query::TypedValue::Type::String: + return MGP_VALUE_TYPE_STRING; + case query::TypedValue::Type::List: + return MGP_VALUE_TYPE_LIST; + case query::TypedValue::Type::Map: + return MGP_VALUE_TYPE_MAP; + case query::TypedValue::Type::Vertex: + return MGP_VALUE_TYPE_VERTEX; + case query::TypedValue::Type::Edge: + return MGP_VALUE_TYPE_EDGE; + case query::TypedValue::Type::Path: + return MGP_VALUE_TYPE_PATH; + } +} + +} // namespace + +mgp_value::mgp_value(utils::MemoryResource *m) noexcept + : type(MGP_VALUE_TYPE_NULL), memory(m) {} + +mgp_value::mgp_value(bool val, utils::MemoryResource *m) noexcept + : type(MGP_VALUE_TYPE_BOOL), memory(m), bool_v(val) {} + +mgp_value::mgp_value(int64_t val, utils::MemoryResource *m) noexcept + : type(MGP_VALUE_TYPE_INT), memory(m), int_v(val) {} + +mgp_value::mgp_value(double val, utils::MemoryResource *m) noexcept + : type(MGP_VALUE_TYPE_DOUBLE), memory(m), double_v(val) {} + +mgp_value::mgp_value(const char *val, utils::MemoryResource *m) + : type(MGP_VALUE_TYPE_STRING), memory(m), string_v(val, m) {} + +mgp_value::mgp_value(mgp_list *val, utils::MemoryResource *m) noexcept + : type(MGP_VALUE_TYPE_LIST), memory(m), list_v(val) { + CHECK(val->GetMemoryResource() == m) + << "Unable to take ownership of a pointer with different allocator."; +} + +mgp_value::mgp_value(mgp_map *val, utils::MemoryResource *m) noexcept + : type(MGP_VALUE_TYPE_MAP), memory(m), map_v(val) { + CHECK(val->GetMemoryResource() == m) + << "Unable to take ownership of a pointer with different allocator."; +} + +mgp_value::mgp_value(mgp_vertex *val, utils::MemoryResource *m) noexcept + : type(MGP_VALUE_TYPE_VERTEX), memory(m), vertex_v(val) { + CHECK(val->GetMemoryResource() == m) + << "Unable to take ownership of a pointer with different allocator."; +} + +mgp_value::mgp_value(mgp_edge *val, utils::MemoryResource *m) noexcept + : type(MGP_VALUE_TYPE_EDGE), memory(m), edge_v(val) { + CHECK(val->GetMemoryResource() == m) + << "Unable to take ownership of a pointer with different allocator."; +} + +mgp_value::mgp_value(mgp_path *val, utils::MemoryResource *m) noexcept + : type(MGP_VALUE_TYPE_PATH), memory(m), path_v(val) { + CHECK(val->GetMemoryResource() == m) + << "Unable to take ownership of a pointer with different allocator."; +} + +mgp_value::mgp_value(const query::TypedValue &tv, const mgp_graph *graph, + utils::MemoryResource *m) + : type(FromTypedValueType(tv.type())), memory(m) { + switch (type) { + case MGP_VALUE_TYPE_NULL: + break; + case MGP_VALUE_TYPE_BOOL: + bool_v = tv.ValueBool(); + break; + case MGP_VALUE_TYPE_INT: + int_v = tv.ValueInt(); + break; + case MGP_VALUE_TYPE_DOUBLE: + double_v = tv.ValueDouble(); + break; + case MGP_VALUE_TYPE_STRING: + new (&string_v) utils::pmr::string(tv.ValueString(), m); + break; + case MGP_VALUE_TYPE_LIST: { + // Fill the stack allocated container and then construct the actual member + // value. This handles the case when filling the container throws + // something and our destructor doesn't get called so member value isn't + // released. + utils::pmr::vector elems(m); + elems.reserve(tv.ValueList().size()); + for (const auto &elem : tv.ValueList()) { + elems.emplace_back(elem, graph); + } + utils::Allocator allocator(m); + list_v = allocator.new_object(std::move(elems)); + break; + } + case MGP_VALUE_TYPE_MAP: { + // Fill the stack allocated container and then construct the actual member + // value. This handles the case when filling the container throws + // something and our destructor doesn't get called so member value isn't + // released. + utils::pmr::map items(m); + for (const auto &item : tv.ValueMap()) { + items.emplace(item.first, mgp_value(item.second, graph, m)); + } + utils::Allocator allocator(m); + map_v = allocator.new_object(std::move(items)); + break; + } + case MGP_VALUE_TYPE_VERTEX: { + utils::Allocator allocator(m); + vertex_v = allocator.new_object(tv.ValueVertex(), graph); + break; + } + case MGP_VALUE_TYPE_EDGE: { + utils::Allocator allocator(m); + edge_v = allocator.new_object(tv.ValueEdge(), graph); + break; + } + case MGP_VALUE_TYPE_PATH: { + // Fill the stack allocated container and then construct the actual member + // value. This handles the case when filling the container throws + // something and our destructor doesn't get called so member value isn't + // released. + 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); + } + tmp_path.edges.reserve(tv.ValuePath().edges().size()); + for (const auto &e : tv.ValuePath().edges()) { + tmp_path.edges.emplace_back(e, graph); + } + utils::Allocator allocator(m); + path_v = allocator.new_object(std::move(tmp_path)); + break; + } + } +} + +mgp_value::mgp_value(const PropertyValue &pv, utils::MemoryResource *m) + : memory(m) { + switch (pv.type()) { + case PropertyValue::Type::Null: + type = MGP_VALUE_TYPE_NULL; + break; + case PropertyValue::Type::Bool: + type = MGP_VALUE_TYPE_BOOL; + bool_v = pv.ValueBool(); + break; + case PropertyValue::Type::Int: + type = MGP_VALUE_TYPE_INT; + int_v = pv.ValueInt(); + break; + case PropertyValue::Type::Double: + type = MGP_VALUE_TYPE_DOUBLE; + double_v = pv.ValueDouble(); + break; + case PropertyValue::Type::String: + type = MGP_VALUE_TYPE_STRING; + new (&string_v) utils::pmr::string(pv.ValueString(), m); + break; + case PropertyValue::Type::List: { + // Fill the stack allocated container and then construct the actual member + // value. This handles the case when filling the container throws + // something and our destructor doesn't get called so member value isn't + // released. + type = MGP_VALUE_TYPE_LIST; + utils::pmr::vector elems(m); + elems.reserve(pv.ValueList().size()); + for (const auto &elem : pv.ValueList()) { + elems.emplace_back(elem); + } + utils::Allocator allocator(m); + list_v = allocator.new_object(std::move(elems)); + break; + } + case PropertyValue::Type::Map: { + // Fill the stack allocated container and then construct the actual member + // value. This handles the case when filling the container throws + // something and our destructor doesn't get called so member value isn't + // released. + type = MGP_VALUE_TYPE_MAP; + utils::pmr::map items(m); + for (const auto &item : pv.ValueMap()) { + items.emplace(item.first, item.second); + } + utils::Allocator allocator(m); + map_v = allocator.new_object(std::move(items)); + break; + } + } +} + +mgp_value::mgp_value(const mgp_value &other, utils::MemoryResource *m) + : type(other.type), memory(m) { + switch (other.type) { + case MGP_VALUE_TYPE_NULL: + break; + case MGP_VALUE_TYPE_BOOL: + bool_v = other.bool_v; + break; + case MGP_VALUE_TYPE_INT: + int_v = other.int_v; + break; + case MGP_VALUE_TYPE_DOUBLE: + double_v = other.double_v; + break; + case MGP_VALUE_TYPE_STRING: + new (&string_v) utils::pmr::string(other.string_v, m); + break; + case MGP_VALUE_TYPE_LIST: { + utils::Allocator allocator(m); + list_v = allocator.new_object(*other.list_v); + break; + } + case MGP_VALUE_TYPE_MAP: { + utils::Allocator allocator(m); + map_v = allocator.new_object(*other.map_v); + break; + } + case MGP_VALUE_TYPE_VERTEX: { + utils::Allocator allocator(m); + vertex_v = allocator.new_object(*other.vertex_v); + break; + } + case MGP_VALUE_TYPE_EDGE: { + utils::Allocator allocator(m); + edge_v = allocator.new_object(*other.edge_v); + break; + } + case MGP_VALUE_TYPE_PATH: { + utils::Allocator allocator(m); + path_v = allocator.new_object(*other.path_v); + break; + } + } +} + +namespace { + +void DeleteValueMember(mgp_value *value) noexcept { + CHECK(value); + utils::Allocator allocator(value->GetMemoryResource()); + switch (mgp_value_get_type(value)) { + case MGP_VALUE_TYPE_NULL: + case MGP_VALUE_TYPE_BOOL: + case MGP_VALUE_TYPE_INT: + case MGP_VALUE_TYPE_DOUBLE: + return; + case MGP_VALUE_TYPE_STRING: + using TString = utils::pmr::string; + value->string_v.~TString(); + return; + case MGP_VALUE_TYPE_LIST: + allocator.delete_object(value->list_v); + return; + case MGP_VALUE_TYPE_MAP: + allocator.delete_object(value->map_v); + return; + case MGP_VALUE_TYPE_VERTEX: + allocator.delete_object(value->vertex_v); + return; + case MGP_VALUE_TYPE_EDGE: + allocator.delete_object(value->edge_v); + return; + case MGP_VALUE_TYPE_PATH: + allocator.delete_object(value->path_v); + return; + } +} + +} // namespace + +mgp_value::mgp_value(mgp_value &&other, utils::MemoryResource *m) + : type(other.type), memory(m) { + switch (other.type) { + case MGP_VALUE_TYPE_NULL: + break; + case MGP_VALUE_TYPE_BOOL: + bool_v = other.bool_v; + break; + case MGP_VALUE_TYPE_INT: + int_v = other.int_v; + break; + case MGP_VALUE_TYPE_DOUBLE: + double_v = other.double_v; + break; + case MGP_VALUE_TYPE_STRING: + new (&string_v) utils::pmr::string(std::move(other.string_v), m); + break; + case MGP_VALUE_TYPE_LIST: + static_assert(std::is_pointer_v, + "Expected to move list_v by copying pointers."); + if (*other.GetMemoryResource() == *m) { + list_v = other.list_v; + } else { + utils::Allocator allocator(m); + list_v = allocator.new_object(std::move(*other.list_v)); + } + break; + case MGP_VALUE_TYPE_MAP: + static_assert(std::is_pointer_v, + "Expected to move map_v by copying pointers."); + if (*other.GetMemoryResource() == *m) { + map_v = other.map_v; + } else { + utils::Allocator allocator(m); + map_v = allocator.new_object(std::move(*other.map_v)); + } + break; + case MGP_VALUE_TYPE_VERTEX: + static_assert(std::is_pointer_v, + "Expected to move vertex_v by copying pointers."); + if (*other.GetMemoryResource() == *m) { + vertex_v = other.vertex_v; + } else { + utils::Allocator allocator(m); + vertex_v = allocator.new_object(std::move(*other.vertex_v)); + } + break; + case MGP_VALUE_TYPE_EDGE: + static_assert(std::is_pointer_v, + "Expected to move edge_v by copying pointers."); + if (*other.GetMemoryResource() == *m) { + edge_v = other.edge_v; + } else { + utils::Allocator allocator(m); + edge_v = allocator.new_object(std::move(*other.edge_v)); + } + break; + case MGP_VALUE_TYPE_PATH: + static_assert(std::is_pointer_v, + "Expected to move path_v by copying pointers."); + if (*other.GetMemoryResource() == *m) { + path_v = other.path_v; + } else { + utils::Allocator allocator(m); + path_v = allocator.new_object(std::move(*other.path_v)); + } + break; + } + DeleteValueMember(&other); + other.type = MGP_VALUE_TYPE_NULL; +} + +mgp_value::~mgp_value() noexcept { DeleteValueMember(this); } + +void mgp_value_destroy(mgp_value *val) { delete_mgp_object(val); } + +mgp_value *mgp_value_make_null(mgp_memory *memory) { + return new_mgp_object(memory); +} + +mgp_value *mgp_value_make_bool(int val, mgp_memory *memory) { + return new_mgp_object(memory, val != 0); +} + +mgp_value *mgp_value_make_int(int64_t val, mgp_memory *memory) { + return new_mgp_object(memory, val); +} + +mgp_value *mgp_value_make_double(double val, mgp_memory *memory) { + return new_mgp_object(memory, val); +} + +mgp_value *mgp_value_make_string(const char *val, mgp_memory *memory) { + try { + // This may throw something from std::string constructor, it could be + // std::length_error, but it's not really well defined, so catch all. + return new_mgp_object(memory, val); + } catch (...) { + return nullptr; + } +} + +mgp_value *mgp_value_make_list(mgp_list *val) { + return new_mgp_object(val->GetMemoryResource(), val); +} + +mgp_value *mgp_value_make_map(mgp_map *val) { + return new_mgp_object(val->GetMemoryResource(), val); +} + +mgp_value *mgp_value_make_vertex(mgp_vertex *val) { + return new_mgp_object(val->GetMemoryResource(), val); +} + +mgp_value *mgp_value_make_edge(mgp_edge *val) { + return new_mgp_object(val->GetMemoryResource(), val); +} + +mgp_value *mgp_value_make_path(mgp_path *val) { + return new_mgp_object(val->GetMemoryResource(), val); +} + +mgp_value_type mgp_value_get_type(const mgp_value *val) { return val->type; } + +int mgp_value_is_null(const mgp_value *val) { + return mgp_value_get_type(val) == MGP_VALUE_TYPE_NULL; +} + +int mgp_value_is_bool(const mgp_value *val) { + return mgp_value_get_type(val) == MGP_VALUE_TYPE_BOOL; +} + +int mgp_value_is_int(const mgp_value *val) { + return mgp_value_get_type(val) == MGP_VALUE_TYPE_INT; +} + +int mgp_value_is_double(const mgp_value *val) { + return mgp_value_get_type(val) == MGP_VALUE_TYPE_DOUBLE; +} + +int mgp_value_is_string(const mgp_value *val) { + return mgp_value_get_type(val) == MGP_VALUE_TYPE_STRING; +} + +int mgp_value_is_list(const mgp_value *val) { + return mgp_value_get_type(val) == MGP_VALUE_TYPE_LIST; +} + +int mgp_value_is_map(const mgp_value *val) { + return mgp_value_get_type(val) == MGP_VALUE_TYPE_MAP; +} + +int mgp_value_is_vertex(const mgp_value *val) { + return mgp_value_get_type(val) == MGP_VALUE_TYPE_VERTEX; +} + +int mgp_value_is_edge(const mgp_value *val) { + return mgp_value_get_type(val) == MGP_VALUE_TYPE_EDGE; +} + +int mgp_value_is_path(const mgp_value *val) { + return mgp_value_get_type(val) == MGP_VALUE_TYPE_PATH; +} + +int mgp_value_get_bool(const mgp_value *val) { return val->bool_v ? 1 : 0; } + +int64_t mgp_value_get_int(const mgp_value *val) { return val->int_v; } + +double mgp_value_get_double(const mgp_value *val) { return val->double_v; } + +const char *mgp_value_get_string(const mgp_value *val) { + return val->string_v.c_str(); +} + +const mgp_list *mgp_value_get_list(const mgp_value *val) { return val->list_v; } + +const mgp_map *mgp_value_get_map(const mgp_value *val) { return val->map_v; } + +const mgp_vertex *mgp_value_get_vertex(const mgp_value *val) { + return val->vertex_v; +} + +const mgp_edge *mgp_value_get_edge(const mgp_value *val) { return val->edge_v; } + +const mgp_path *mgp_value_get_path(const mgp_value *val) { return val->path_v; } diff --git a/src/query/procedure/mg_procedure_impl.hpp b/src/query/procedure/mg_procedure_impl.hpp index 1fab98eed..e45fbdfc9 100644 --- a/src/query/procedure/mg_procedure_impl.hpp +++ b/src/query/procedure/mg_procedure_impl.hpp @@ -4,8 +4,13 @@ #pragma once #include "mg_procedure.h" - +#include "query/db_accessor.hpp" +#include "query/typed_value.hpp" +#include "storage/v2/view.hpp" #include "utils/memory.hpp" +#include "utils/pmr/map.hpp" +#include "utils/pmr/string.hpp" +#include "utils/pmr/vector.hpp" /// Wraps memory resource used in custom procedures. /// @@ -17,3 +22,275 @@ struct mgp_memory { utils::MemoryResource *impl; }; + +/// Immutable container of various values that appear in openCypher. +struct mgp_value { + /// Allocator type so that STL containers are aware that we need one. + using allocator_type = utils::Allocator; + + // Construct MGP_VALUE_TYPE_NULL. + explicit mgp_value(utils::MemoryResource *) noexcept; + + mgp_value(bool, utils::MemoryResource *) noexcept; + mgp_value(int64_t, utils::MemoryResource *) noexcept; + mgp_value(double, utils::MemoryResource *) noexcept; + /// @throw std::bad_alloc + mgp_value(const char *, utils::MemoryResource *); + /// Take ownership of the mgp_list, MemoryResource must match. + mgp_value(mgp_list *, utils::MemoryResource *) noexcept; + /// Take ownership of the mgp_map, MemoryResource must match. + mgp_value(mgp_map *, utils::MemoryResource *) noexcept; + /// Take ownership of the mgp_vertex, MemoryResource must match. + mgp_value(mgp_vertex *, utils::MemoryResource *) noexcept; + /// Take ownership of the mgp_edge, MemoryResource must match. + mgp_value(mgp_edge *, utils::MemoryResource *) noexcept; + /// Take ownership of the mgp_path, MemoryResource must match. + mgp_value(mgp_path *, utils::MemoryResource *) noexcept; + + /// Construct by copying query::TypedValue using utils::MemoryResource. + /// mgp_graph is needed to construct mgp_vertex and mgp_edge. + /// @throw std::bad_alloc + mgp_value(const query::TypedValue &, const mgp_graph *, + utils::MemoryResource *); + + /// Construct by copying PropertyValue using utils::MemoryResource. + /// @throw std::bad_alloc + mgp_value(const PropertyValue &, utils::MemoryResource *); + + /// Copy construction without utils::MemoryResource is not allowed. + mgp_value(const mgp_value &) = delete; + + /// Copy construct using given utils::MemoryResource. + /// @throw std::bad_alloc + mgp_value(const mgp_value &, utils::MemoryResource *); + + /// Move construct using given utils::MemoryResource. + /// @throw std::bad_alloc if MemoryResource is different, so we cannot move. + mgp_value(mgp_value &&, utils::MemoryResource *); + + /// Move construct, utils::MemoryResource is inherited. + mgp_value(mgp_value &&other) noexcept : mgp_value(other, other.memory) {} + + /// Copy-assignment is not allowed to preserve immutability. + mgp_value &operator=(const mgp_value &) = delete; + + /// Move-assignment is not allowed to preserve immutability. + mgp_value &operator=(mgp_value &&) = delete; + + ~mgp_value() noexcept; + + utils::MemoryResource *GetMemoryResource() const noexcept { return memory; } + + mgp_value_type type; + utils::MemoryResource *memory; + + union { + bool bool_v; + int64_t int_v; + double double_v; + utils::pmr::string string_v; + // We use pointers so that taking ownership via C API is easier. Besides, + // mgp_map cannot use incomplete mgp_value type, because that would be + // undefined behaviour. + mgp_list *list_v; + mgp_map *map_v; + mgp_vertex *vertex_v; + mgp_edge *edge_v; + mgp_path *path_v; + }; +}; + +struct mgp_list { + /// Allocator type so that STL containers are aware that we need one. + using allocator_type = utils::Allocator; + + explicit mgp_list(utils::MemoryResource *memory) : elems(memory) {} + + mgp_list(utils::pmr::vector &&elems, utils::MemoryResource *memory) + : elems(std::move(elems), memory) {} + + mgp_list(const mgp_list &other, utils::MemoryResource *memory) + : elems(other.elems, memory) {} + + mgp_list(mgp_list &&other, utils::MemoryResource *memory) + : elems(std::move(other.elems), memory) {} + + mgp_list(mgp_list &&other) noexcept : elems(std::move(other.elems)) {} + + /// Copy construction without utils::MemoryResource is not allowed. + mgp_list(const mgp_list &) = delete; + + mgp_list &operator=(const mgp_list &) = delete; + mgp_list &operator=(mgp_list &&) = delete; + + ~mgp_list() = default; + + utils::MemoryResource *GetMemoryResource() const noexcept { + return elems.get_allocator().GetMemoryResource(); + } + + // C++17 vector can work with incomplete type. + utils::pmr::vector elems; +}; + +struct mgp_map { + /// Allocator type so that STL containers are aware that we need one. + using allocator_type = utils::Allocator; + + explicit mgp_map(utils::MemoryResource *memory) : items(memory) {} + + mgp_map(utils::pmr::map &&items, + utils::MemoryResource *memory) + : items(std::move(items), memory) {} + + mgp_map(const mgp_map &other, utils::MemoryResource *memory) + : items(other.items, memory) {} + + mgp_map(mgp_map &&other, utils::MemoryResource *memory) + : items(std::move(other.items), memory) {} + + mgp_map(mgp_map &&other) noexcept : items(std::move(other.items)) {} + + /// Copy construction without utils::MemoryResource is not allowed. + mgp_map(const mgp_map &) = delete; + + mgp_map &operator=(const mgp_map &) = delete; + mgp_map &operator=(mgp_map &&) = delete; + + ~mgp_map() = default; + + utils::MemoryResource *GetMemoryResource() const noexcept { + return items.get_allocator().GetMemoryResource(); + } + + // Unfortunately using incomplete type with map is undefined, so mgp_map + // needs to be defined after mgp_value. + utils::pmr::map items; +}; + +struct mgp_vertex { + /// 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 + /// the allocator which was used to allocate `this`. + using allocator_type = utils::Allocator; + + // Hopefully VertexAccessor copy constructor remains noexcept, so that we can + // have everything noexcept here. + static_assert(std::is_nothrow_copy_constructible_v); + + mgp_vertex(query::VertexAccessor v, const mgp_graph *graph, + utils::MemoryResource *memory) noexcept + : memory(memory), impl(v), graph(graph) {} + + mgp_vertex(const mgp_vertex &other, utils::MemoryResource *memory) noexcept + : memory(memory), impl(other.impl), graph(other.graph) {} + + mgp_vertex(mgp_vertex &&other, utils::MemoryResource *memory) noexcept + : memory(memory), impl(other.impl), graph(other.graph) {} + + mgp_vertex(mgp_vertex &&other) noexcept + : memory(other.memory), impl(other.impl), graph(other.graph) {} + + /// Copy construction without utils::MemoryResource is not allowed. + mgp_vertex(const mgp_vertex &) = delete; + + mgp_vertex &operator=(const mgp_vertex &) = delete; + mgp_vertex &operator=(mgp_vertex &&) = delete; + + ~mgp_vertex() = default; + + utils::MemoryResource *GetMemoryResource() const noexcept { return memory; } + + utils::MemoryResource *memory; + query::VertexAccessor impl; + const mgp_graph *graph; +}; + +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 + /// the allocator which was used to allocate `this`. + using allocator_type = utils::Allocator; + + // Hopefully EdgeAccessor copy constructor remains noexcept, so that we can + // have everything noexcept here. + static_assert(std::is_nothrow_copy_constructible_v); + + mgp_edge(const query::EdgeAccessor &impl, const mgp_graph *graph, + utils::MemoryResource *memory) noexcept + : memory(memory), + impl(impl), + from(impl.From(), graph, memory), + to(impl.To(), graph, memory) {} + + mgp_edge(const mgp_edge &other, utils::MemoryResource *memory) noexcept + : memory(memory), + impl(other.impl), + from(other.from, memory), + to(other.to, memory) {} + + mgp_edge(mgp_edge &&other, utils::MemoryResource *memory) noexcept + : memory(other.memory), + impl(other.impl), + from(std::move(other.from), memory), + to(std::move(other.to), memory) {} + + mgp_edge(mgp_edge &&other) noexcept + : memory(other.memory), + impl(other.impl), + from(std::move(other.from)), + to(std::move(other.to)) {} + + /// Copy construction without utils::MemoryResource is not allowed. + mgp_edge(const mgp_edge &) = delete; + + mgp_edge &operator=(const mgp_edge &) = delete; + mgp_edge &operator=(mgp_edge &&) = delete; + + ~mgp_edge() = default; + + utils::MemoryResource *GetMemoryResource() const noexcept { return memory; } + + utils::MemoryResource *memory; + query::EdgeAccessor impl; + mgp_vertex from; + mgp_vertex to; +}; + +struct mgp_path { + /// Allocator type so that STL containers are aware that we need one. + using allocator_type = utils::Allocator; + + explicit mgp_path(utils::MemoryResource *memory) + : vertices(memory), edges(memory) {} + + mgp_path(const mgp_path &other, utils::MemoryResource *memory) + : vertices(other.vertices, memory), edges(other.edges, memory) {} + + mgp_path(mgp_path &&other, utils::MemoryResource *memory) + : vertices(std::move(other.vertices), memory), + edges(std::move(other.edges), memory) {} + + mgp_path(mgp_path &&other) noexcept + : vertices(std::move(other.vertices)), edges(std::move(other.edges)) {} + + /// Copy construction without utils::MemoryResource is not allowed. + mgp_path(const mgp_path &) = delete; + + mgp_path &operator=(const mgp_path &) = delete; + mgp_path &operator=(mgp_path &&) = delete; + + ~mgp_path() = default; + + utils::MemoryResource *GetMemoryResource() const noexcept { + return vertices.get_allocator().GetMemoryResource(); + } + + utils::pmr::vector vertices; + utils::pmr::vector edges; +}; + +struct mgp_graph { + query::DbAccessor *impl; + storage::View view; +};