query/procedure: Implement graph related API

Reviewers: mferencevic, llugovic, ipaljak

Reviewed By: mferencevic

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D2522
This commit is contained in:
Teon Banek 2019-10-28 16:21:40 +01:00
parent bd998dc618
commit 043ae87a83
2 changed files with 578 additions and 2 deletions

View File

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

View File

@ -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 = &current->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;
};