diff --git a/src/query/procedure/mg_procedure_impl.cpp b/src/query/procedure/mg_procedure_impl.cpp index 4292c59cd..efd019aa1 100644 --- a/src/query/procedure/mg_procedure_impl.cpp +++ b/src/query/procedure/mg_procedure_impl.cpp @@ -702,13 +702,74 @@ const mgp_map_item *mgp_map_items_iterator_get( const mgp_map_item *mgp_map_items_iterator_next(mgp_map_items_iterator *it) { if (it->current_it == it->map->items.end()) return nullptr; - ++it->current_it; - if (it->current_it == it->map->items.end()) return nullptr; + if (++it->current_it == it->map->items.end()) return nullptr; it->current.key = it->current_it->first.c_str(); it->current.value = &it->current_it->second; return &it->current; } +mgp_path *mgp_path_make_with_start(const mgp_vertex *vertex, + mgp_memory *memory) { + auto *path = new_mgp_object<mgp_path>(memory); + if (!path) return nullptr; + try { + path->vertices.push_back(*vertex); + } catch (...) { + delete_mgp_object(path); + return nullptr; + } + return path; +} + +void mgp_path_destroy(mgp_path *path) { delete_mgp_object(path); } + +int mgp_path_expand(mgp_path *path, const mgp_edge *edge) { + CHECK(mgp_path_size(path) == path->vertices.size() - 1) << "Invalid mgp_path"; + // Check that the both the last vertex on path and dst_vertex are endpoints of + // the given edge. + const auto *src_vertex = &path->vertices.back(); + const mgp_vertex *dst_vertex = nullptr; + if (mgp_vertex_equal(mgp_edge_get_to(edge), src_vertex)) { + dst_vertex = mgp_edge_get_from(edge); + } else if (mgp_vertex_equal(mgp_edge_get_from(edge), src_vertex)) { + dst_vertex = mgp_edge_get_to(edge); + } else { + // edge is not a continuation on src_vertex + return 0; + } + // Try appending edge and dst_vertex to path, preserving the original mgp_path + // instance if anything fails. + try { + path->edges.push_back(*edge); + } catch (...) { + CHECK(mgp_path_size(path) == path->vertices.size() - 1); + return 0; + } + try { + path->vertices.push_back(*dst_vertex); + } catch (...) { + path->edges.pop_back(); + CHECK(mgp_path_size(path) == path->vertices.size() - 1); + return 0; + } + CHECK(mgp_path_size(path) == path->vertices.size() - 1); + return 1; +} + +size_t mgp_path_size(const mgp_path *path) { return path->edges.size(); } + +const mgp_vertex *mgp_path_vertex_at(const mgp_path *path, size_t i) { + CHECK(mgp_path_size(path) == path->vertices.size() - 1); + if (i > mgp_path_size(path)) return nullptr; + return &path->vertices[i]; +} + +const mgp_edge *mgp_path_edge_at(const mgp_path *path, size_t i) { + CHECK(mgp_path_size(path) == path->vertices.size() - 1); + if (i >= mgp_path_size(path)) return nullptr; + return &path->edges[i]; +} + /// Plugin Result int mgp_result_set_error_msg(mgp_result *res, const char *msg) { @@ -744,3 +805,402 @@ int mgp_result_record_insert(mgp_result_record *record, const char *field_name, } return 1; } + +/// Graph Constructs + +void mgp_properties_iterator_destroy(mgp_properties_iterator *it) { + delete_mgp_object(it); +} + +const mgp_property *mgp_properties_iterator_get( + const mgp_properties_iterator *it) { + if (it->current) return &it->property; + return nullptr; +} + +const mgp_property *mgp_properties_iterator_next(mgp_properties_iterator *it) { + // Incrementing the iterator either for on-disk or in-memory + // storage, so perhaps the underlying thing can throw. + // Both copying TypedValue and/or string from PropertyName may fail to + // allocate. Also, dereferencing `it->current_it` could also throw, so + // either way return nullptr and leave `it` in undefined state. + // Hopefully iterator comparison doesn't throw, but wrap the whole thing in + // try ... catch just to be sure. + try { + if (it->current_it == it->pvs.end()) { + CHECK(!it->current) << "Iteration is already done, so it->current should " + "have been set to std::nullopt"; + return nullptr; + } + if (++it->current_it == it->pvs.end()) { + it->current = std::nullopt; + return nullptr; + } + it->current.emplace( + utils::pmr::string( + it->graph->impl->PropertyToName(it->current_it->first), + it->GetMemoryResource()), + mgp_value(it->current_it->second, it->GetMemoryResource())); + it->property.name = it->current->first.c_str(); + it->property.value = &it->current->second; + return &it->property; + } catch (...) { + it->current = std::nullopt; + return nullptr; + } +} + +mgp_vertex *mgp_vertex_copy(const mgp_vertex *v, mgp_memory *memory) { + return new_mgp_object<mgp_vertex>(memory, *v); +} + +void mgp_vertex_destroy(mgp_vertex *v) { delete_mgp_object(v); } + +int mgp_vertex_equal(const mgp_vertex *a, const mgp_vertex *b) { + return a->impl == b->impl ? 1 : 0; +} + +size_t mgp_vertex_labels_count(const mgp_vertex *v) { + auto maybe_labels = v->impl.Labels(v->graph->view); + if (maybe_labels.HasError()) { + switch (maybe_labels.GetError()) { + case storage::Error::DELETED_OBJECT: + // Treat deleted vertex as having no labels. + return 0; + case storage::Error::PROPERTIES_DISABLED: + case storage::Error::VERTEX_HAS_EDGES: + case storage::Error::SERIALIZATION_ERROR: + LOG(ERROR) << "Unexpected error when getting vertex labels."; + return 0; + } + } + return maybe_labels->size(); +} + +mgp_label mgp_vertex_label_at(const mgp_vertex *v, size_t i) { + // TODO: Maybe it's worth caching this in mgp_vertex. + auto maybe_labels = v->impl.Labels(v->graph->view); + if (maybe_labels.HasError()) { + switch (maybe_labels.GetError()) { + case storage::Error::DELETED_OBJECT: + return mgp_label{nullptr}; + case storage::Error::PROPERTIES_DISABLED: + case storage::Error::VERTEX_HAS_EDGES: + case storage::Error::SERIALIZATION_ERROR: + LOG(ERROR) << "Unexpected error when getting vertex labels."; + return mgp_label{nullptr}; + } + } + if (i >= maybe_labels->size()) return mgp_label{nullptr}; + const auto &label = (*maybe_labels)[i]; + static_assert( + std::is_lvalue_reference_v<decltype(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); + return mgp_label{name.c_str()}; +} + +int mgp_vertex_has_label_named(const mgp_vertex *v, const char *name) { + storage::Label label; + try { + // This will allocate a std::string from `name`, which may throw + // std::bad_alloc or std::length_error. This could be avoided with a + // std::string_view. Although storage API may be updated with + // std::string_view, NameToLabel itself may still throw std::bad_alloc when + // creating a new LabelId mapping and we need to handle that. + label = v->graph->impl->NameToLabel(name); + } catch (...) { + LOG(ERROR) << "Unable to allocate a LabelId mapping"; + // If we need to allocate a new mapping, then the vertex does not have such + // a label, so return 0. + return 0; + } + auto maybe_has_label = v->impl.HasLabel(v->graph->view, label); + if (maybe_has_label.HasError()) { + switch (maybe_has_label.GetError()) { + case storage::Error::DELETED_OBJECT: + return 0; + case storage::Error::PROPERTIES_DISABLED: + case storage::Error::VERTEX_HAS_EDGES: + case storage::Error::SERIALIZATION_ERROR: + LOG(ERROR) << "Unexpected error when checking vertex has label."; + return 0; + } + } + return *maybe_has_label; +} + +int mgp_vertex_has_label(const mgp_vertex *v, mgp_label label) { + return mgp_vertex_has_label_named(v, label.name); +} + +mgp_value *mgp_vertex_get_property(const mgp_vertex *v, const char *name, + mgp_memory *memory) { + try { + const auto &key = v->graph->impl->NameToProperty(name); + auto maybe_prop = v->impl.GetProperty(v->graph->view, key); + if (maybe_prop.HasError()) { + switch (maybe_prop.GetError()) { + case storage::Error::DELETED_OBJECT: + // Treat deleted vertex as having no properties. + return new_mgp_object<mgp_value>(memory); + case storage::Error::PROPERTIES_DISABLED: + case storage::Error::VERTEX_HAS_EDGES: + case storage::Error::SERIALIZATION_ERROR: + LOG(ERROR) << "Unexpected error when getting vertex property"; + return nullptr; + } + } + return new_mgp_object<mgp_value>(memory, std::move(*maybe_prop)); + } catch (...) { + // In case NameToProperty or GetProperty throw an exception, most likely + // std::bad_alloc. + return nullptr; + } +} + +mgp_properties_iterator *mgp_vertex_iter_properties(const mgp_vertex *v, + mgp_memory *memory) { + // NOTE: This copies the whole properties into the iterator. + // TODO: Think of a good way to avoid the copy which doesn't just rely on some + // assumption that storage may return a pointer to the property store. This + // will probably require a different API in storage. + try { + auto maybe_props = v->impl.Properties(v->graph->view); + if (maybe_props.HasError()) { + switch (maybe_props.GetError()) { + case storage::Error::DELETED_OBJECT: + // Treat deleted vertex as having no properties. + return new_mgp_object<mgp_properties_iterator>(memory, v->graph); + case storage::Error::PROPERTIES_DISABLED: + case storage::Error::VERTEX_HAS_EDGES: + case storage::Error::SERIALIZATION_ERROR: + LOG(ERROR) << "Unexpected error when getting vertex properties"; + return nullptr; + } + } + return new_mgp_object<mgp_properties_iterator>(memory, v->graph, + std::move(*maybe_props)); + } catch (...) { + // Since we are copying stuff, we may get std::bad_alloc. Hopefully, no + // other exceptions are possible, but catch them all just in case. + return nullptr; + } +} + +void mgp_edges_iterator_destroy(mgp_edges_iterator *it) { + delete_mgp_object(it); +} + +mgp_edges_iterator *mgp_vertex_iter_in_edges(const mgp_vertex *v, + mgp_memory *memory) { + auto *it = new_mgp_object<mgp_edges_iterator>(memory, *v); + if (!it) return nullptr; + try { + auto maybe_edges = v->impl.InEdges(v->graph->view); + if (maybe_edges.HasError()) { + switch (maybe_edges.GetError()) { + case storage::Error::DELETED_OBJECT: + // Treat deleted vertex as having no edges. + return it; + case storage::Error::PROPERTIES_DISABLED: + case storage::Error::VERTEX_HAS_EDGES: + case storage::Error::SERIALIZATION_ERROR: + LOG(ERROR) << "Unexpected error when getting in edges"; + mgp_edges_iterator_destroy(it); + return nullptr; + } + } + 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()); + } + } catch (...) { + // We are probably copying edges, and that may throw std::bad_alloc. + mgp_edges_iterator_destroy(it); + return nullptr; + } + return it; +} + +mgp_edges_iterator *mgp_vertex_iter_out_edges(const mgp_vertex *v, + mgp_memory *memory) { + auto *it = new_mgp_object<mgp_edges_iterator>(memory, *v); + if (!it) return nullptr; + try { + auto maybe_edges = v->impl.OutEdges(v->graph->view); + if (maybe_edges.HasError()) { + switch (maybe_edges.GetError()) { + case storage::Error::DELETED_OBJECT: + // Treat deleted vertex as having no edges. + return it; + case storage::Error::PROPERTIES_DISABLED: + case storage::Error::VERTEX_HAS_EDGES: + case storage::Error::SERIALIZATION_ERROR: + LOG(ERROR) << "Unexpected error when getting out edges"; + mgp_edges_iterator_destroy(it); + return nullptr; + } + } + 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()); + } + } catch (...) { + // We are probably copying edges, and that may throw std::bad_alloc. + mgp_edges_iterator_destroy(it); + return nullptr; + } + return it; +} + +const mgp_edge *mgp_edges_iterator_get(const mgp_edges_iterator *it) { + if (it->current_e) return &*it->current_e; + return nullptr; +} + +const mgp_edge *mgp_edges_iterator_next(mgp_edges_iterator *it) { + if (!it->in && !it->out) return nullptr; + auto next = [&](auto *impl_it, const auto &end) -> const mgp_edge * { + if (*impl_it == end) { + CHECK(!it->current_e) << "Iteration is already done, so it->current_e " + "should have been set to std::nullopt"; + return nullptr; + } + if (++(*impl_it) == end) { + it->current_e = std::nullopt; + return nullptr; + } + it->current_e.emplace(**impl_it, it->source_vertex.graph, + it->GetMemoryResource()); + return &*it->current_e; + }; + try { + if (it->in_it) { + return next(&*it->in_it, it->in->end()); + } else { + return next(&*it->out_it, it->out->end()); + } + } catch (...) { + // Just to be sure that operator++ or anything else has thrown something. + it->current_e = std::nullopt; + return nullptr; + } +} + +mgp_edge *mgp_edge_copy(const mgp_edge *v, mgp_memory *memory) { + return new_mgp_object<mgp_edge>(memory, v->impl, v->from.graph); +} + +void mgp_edge_destroy(mgp_edge *e) { delete_mgp_object(e); } + +mgp_edge_type mgp_edge_get_type(const mgp_edge *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."); + return mgp_edge_type{name.c_str()}; +} + +const mgp_vertex *mgp_edge_get_from(const mgp_edge *e) { return &e->from; } + +const mgp_vertex *mgp_edge_get_to(const mgp_edge *e) { return &e->to; } + +mgp_value *mgp_edge_get_property(const mgp_edge *e, const char *name, + mgp_memory *memory) { + try { + const auto &key = e->from.graph->impl->NameToProperty(name); + auto view = e->from.graph->view; + auto maybe_prop = e->impl.GetProperty(view, key); + if (maybe_prop.HasError()) { + switch (maybe_prop.GetError()) { + case storage::Error::DELETED_OBJECT: + // Treat deleted edge as having no properties. + return new_mgp_object<mgp_value>(memory); + case storage::Error::PROPERTIES_DISABLED: + case storage::Error::VERTEX_HAS_EDGES: + case storage::Error::SERIALIZATION_ERROR: + LOG(ERROR) << "Unexpected error when getting edge property"; + return nullptr; + } + } + return new_mgp_object<mgp_value>(memory, std::move(*maybe_prop)); + } catch (...) { + // In case NameToProperty or GetProperty throw an exception, most likely + // std::bad_alloc. + return nullptr; + } +} + +mgp_properties_iterator *mgp_edge_iter_properties(const mgp_edge *e, + mgp_memory *memory) { + // NOTE: This copies the whole properties into iterator. + // TODO: Think of a good way to avoid the copy which doesn't just rely on some + // assumption that storage may return a pointer to the property store. This + // will probably require a different API in storage. + try { + auto view = e->from.graph->view; + auto maybe_props = e->impl.Properties(view); + if (maybe_props.HasError()) { + switch (maybe_props.GetError()) { + case storage::Error::DELETED_OBJECT: + // Treat deleted edge as having no properties. + return new_mgp_object<mgp_properties_iterator>(memory, e->from.graph); + case storage::Error::PROPERTIES_DISABLED: + case storage::Error::VERTEX_HAS_EDGES: + case storage::Error::SERIALIZATION_ERROR: + LOG(ERROR) << "Unexpected error when getting edge properties"; + return nullptr; + } + } + return new_mgp_object<mgp_properties_iterator>(memory, e->from.graph, + std::move(*maybe_props)); + } catch (...) { + // Since we are copying stuff, we may get std::bad_alloc. Hopefully, no + // other exceptions are possible, but catch them all just in case. + return nullptr; + } +} + +void mgp_vertices_iterator_destroy(mgp_vertices_iterator *it) { + delete_mgp_object(it); +} + +mgp_vertices_iterator *mgp_graph_iter_vertices(const mgp_graph *graph, + mgp_memory *memory) { + try { + return new_mgp_object<mgp_vertices_iterator>(memory, graph); + } catch (...) { + return nullptr; + } +} + +const mgp_vertex *mgp_vertices_iterator_get(const mgp_vertices_iterator *it) { + if (it->current_v) return &*it->current_v; + return nullptr; +} + +const mgp_vertex *mgp_vertices_iterator_next(mgp_vertices_iterator *it) { + try { + if (it->current_it == it->vertices.end()) { + CHECK(!it->current_v) << "Iteration is already done, so it->current_v " + "should have been set to std::nullopt"; + return nullptr; + } + if (++it->current_it == it->vertices.end()) { + it->current_v = std::nullopt; + return nullptr; + } + it->current_v.emplace(*it->current_it, it->graph, it->GetMemoryResource()); + return &*it->current_v; + } catch (...) { + // VerticesIterable::Iterator::operator++ may throw + it->current_v = std::nullopt; + return nullptr; + } +} diff --git a/src/query/procedure/mg_procedure_impl.hpp b/src/query/procedure/mg_procedure_impl.hpp index 8b74ab22f..c9d5d2dd5 100644 --- a/src/query/procedure/mg_procedure_impl.hpp +++ b/src/query/procedure/mg_procedure_impl.hpp @@ -339,3 +339,119 @@ struct mgp_graph { query::DbAccessor *impl; storage::View view; }; + +struct mgp_properties_iterator { + using allocator_type = utils::Allocator<mgp_properties_iterator>; + + // Define members at the start because we use decltype a lot here, so members + // need to be visible in method definitions. + + utils::MemoryResource *memory; + const mgp_graph *graph; + std::remove_reference_t<decltype( + *std::declval<query::VertexAccessor>().Properties(graph->view))> + pvs; + decltype(pvs.begin()) current_it; + std::optional<std::pair<utils::pmr::string, mgp_value>> current; + mgp_property property{nullptr, nullptr}; + + // Construct with no properties. + explicit mgp_properties_iterator(const mgp_graph *graph, + utils::MemoryResource *memory) + : memory(memory), graph(graph), current_it(pvs.begin()) {} + + // May throw who the #$@! knows what because PropertyValueStore doesn't + // document what it throws, and it may surely throw some piece of !@#$ + // exception because it's built on top of STL and other libraries. + mgp_properties_iterator(const mgp_graph *graph, decltype(pvs) pvs, + utils::MemoryResource *memory) + : memory(memory), + graph(graph), + pvs(std::move(pvs)), + current_it(this->pvs.begin()) { + if (current_it != this->pvs.end()) { + current.emplace( + utils::pmr::string(graph->impl->PropertyToName(current_it->first), + memory), + mgp_value(current_it->second, memory)); + property.name = current->first.c_str(); + property.value = ¤t->second; + } + } + + mgp_properties_iterator(const mgp_properties_iterator &) = delete; + mgp_properties_iterator(mgp_properties_iterator &&) = delete; + + mgp_properties_iterator &operator=(const mgp_properties_iterator &) = delete; + mgp_properties_iterator &operator=(mgp_properties_iterator &&) = delete; + + ~mgp_properties_iterator() = default; + + utils::MemoryResource *GetMemoryResource() const { return memory; } +}; + +struct mgp_edges_iterator { + using allocator_type = 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 &, + utils::MemoryResource *>); + + mgp_edges_iterator(const mgp_vertex &v, + utils::MemoryResource *memory) noexcept + : memory(memory), source_vertex(v, memory) {} + + mgp_edges_iterator(mgp_edges_iterator &&other) noexcept + : memory(other.memory), + source_vertex(std::move(other.source_vertex)), + in(std::move(other.in)), + in_it(std::move(other.in_it)), + out(std::move(other.out)), + out_it(std::move(other.out_it)), + current_e(std::move(other.current_e)) {} + + mgp_edges_iterator(const mgp_edges_iterator &) = delete; + mgp_edges_iterator &operator=(const mgp_edges_iterator &) = delete; + mgp_edges_iterator &operator=(mgp_edges_iterator &&) = delete; + + ~mgp_edges_iterator() = default; + + utils::MemoryResource *GetMemoryResource() const { return memory; } + + 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<decltype(in->begin())> in_it; + std::optional<std::remove_reference_t<decltype( + *source_vertex.impl.OutEdges(source_vertex.graph->view))>> + out; + std::optional<decltype(out->begin())> out_it; + std::optional<mgp_edge> current_e; +}; + +struct mgp_vertices_iterator { + using allocator_type = utils::Allocator<mgp_vertices_iterator>; + + /// @throw anything VerticesIterable may throw + mgp_vertices_iterator(const mgp_graph *graph, utils::MemoryResource *memory) + : memory(memory), + graph(graph), + vertices(graph->impl->Vertices(graph->view)), + current_it(vertices.begin()) { + if (current_it != vertices.end()) { + current_v.emplace(*current_it, graph, memory); + } + } + + utils::MemoryResource *GetMemoryResource() const { return memory; } + + utils::MemoryResource *memory; + const mgp_graph *graph; + decltype(graph->impl->Vertices(graph->view)) vertices; + decltype(vertices.begin()) current_it; + std::optional<mgp_vertex> current_v; + +};