Set properties C API extension (#1131)

Add SetProperties into the C++ query module API
This commit is contained in:
Josipmrden
2023-09-04 16:17:43 +02:00
committed by GitHub
parent 9661c52179
commit 02eab6ab9c
15 changed files with 505 additions and 14 deletions

View File

@@ -245,6 +245,12 @@ class SubgraphVertexAccessor final {
storage::Result<storage::PropertyValue> SetProperty(storage::PropertyId key, const storage::PropertyValue &value) {
return impl_.SetProperty(key, value);
}
storage::Result<std::vector<std::tuple<storage::PropertyId, storage::PropertyValue, storage::PropertyValue>>>
UpdateProperties(std::map<storage::PropertyId, storage::PropertyValue> &properties) const {
return impl_.UpdateProperties(properties);
}
VertexAccessor GetVertexAccessor() const;
};
} // namespace memgraph::query

View File

@@ -1721,6 +1721,69 @@ mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_nam
});
}
mgp_error mgp_vertex_set_properties(struct mgp_vertex *v, struct mgp_map *properties) {
return WrapExceptions([=] {
auto *ctx = v->graph->ctx;
#ifdef MG_ENTERPRISE
if (memgraph::license::global_license_checker.IsEnterpriseValidFast() && ctx && ctx->auth_checker &&
!ctx->auth_checker->Has(v->getImpl(), v->graph->view,
memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE)) {
throw AuthorizationException{"Insufficient permissions for setting properties on the vertex!"};
}
#endif
if (!MgpVertexIsMutable(*v)) {
throw ImmutableObjectException{"Cannot set properties of an immutable vertex!"};
}
std::map<memgraph::storage::PropertyId, memgraph::storage::PropertyValue> props;
for (const auto &item : properties->items) {
props.insert(std::visit(
[&item](auto *impl) {
return std::make_pair(impl->NameToProperty(item.first), ToPropertyValue(item.second));
},
v->graph->impl));
}
const auto result = v->getImpl().UpdateProperties(props);
if (result.HasError()) {
switch (result.GetError()) {
case memgraph::storage::Error::DELETED_OBJECT:
throw DeletedObjectException{"Cannot set the properties of a deleted vertex!"};
case memgraph::storage::Error::NONEXISTENT_OBJECT:
LOG_FATAL("Query modules shouldn't have access to nonexistent objects when setting a property of a vertex!");
case memgraph::storage::Error::PROPERTIES_DISABLED:
case memgraph::storage::Error::VERTEX_HAS_EDGES:
LOG_FATAL("Unexpected error when setting a property of a vertex.");
case memgraph::storage::Error::SERIALIZATION_ERROR:
throw SerializationException{"Cannot serialize setting a property of a vertex."};
}
}
ctx->execution_stats[memgraph::query::ExecutionStats::Key::UPDATED_PROPERTIES] +=
static_cast<int64_t>(properties->items.size());
auto *trigger_ctx_collector = ctx->trigger_context_collector;
if (!trigger_ctx_collector ||
!trigger_ctx_collector->ShouldRegisterObjectPropertyChange<memgraph::query::VertexAccessor>()) {
return;
}
for (const auto &res : *result) {
const auto property_key = std::get<0>(res);
const auto old_value = memgraph::query::TypedValue(std::get<1>(res));
const auto new_value = memgraph::query::TypedValue(std::get<2>(res));
if (new_value.IsNull()) {
trigger_ctx_collector->RegisterRemovedObjectProperty(v->getImpl(), property_key, old_value);
continue;
}
trigger_ctx_collector->RegisterSetObjectProperty(v->getImpl(), property_key, old_value, new_value);
}
});
}
mgp_error mgp_vertex_add_label(struct mgp_vertex *v, mgp_label label) {
return WrapExceptions([=] {
auto *ctx = v->graph->ctx;
@@ -2288,6 +2351,69 @@ mgp_error mgp_edge_set_property(struct mgp_edge *e, const char *property_name, m
});
}
mgp_error mgp_edge_set_properties(struct mgp_edge *e, struct mgp_map *properties) {
return WrapExceptions([=] {
auto *ctx = e->from.graph->ctx;
#ifdef MG_ENTERPRISE
if (memgraph::license::global_license_checker.IsEnterpriseValidFast() && ctx && ctx->auth_checker &&
!ctx->auth_checker->Has(e->impl, memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE)) {
throw AuthorizationException{"Insufficient permissions for setting properties on the edge!"};
}
#endif
if (!MgpEdgeIsMutable(*e)) {
throw ImmutableObjectException{"Cannot set properties of an immutable edge!"};
}
std::map<memgraph::storage::PropertyId, memgraph::storage::PropertyValue> props;
for (const auto &item : properties->items) {
props.insert(std::visit(
[&item](auto *impl) {
return std::make_pair(impl->NameToProperty(item.first), ToPropertyValue(item.second));
},
e->from.graph->impl));
}
const auto result = e->impl.UpdateProperties(props);
if (result.HasError()) {
switch (result.GetError()) {
case memgraph::storage::Error::DELETED_OBJECT:
throw DeletedObjectException{"Cannot set the properties of a deleted edge!"};
case memgraph::storage::Error::NONEXISTENT_OBJECT:
LOG_FATAL("Query modules shouldn't have access to nonexistent objects when setting a property of an edge!");
case memgraph::storage::Error::PROPERTIES_DISABLED:
throw std::logic_error{"Cannot set the properties of edges, because properties on edges are disabled!"};
case memgraph::storage::Error::VERTEX_HAS_EDGES:
LOG_FATAL("Unexpected error when setting a property of an edge.");
case memgraph::storage::Error::SERIALIZATION_ERROR:
throw SerializationException{"Cannot serialize setting a property of an edge."};
}
}
ctx->execution_stats[memgraph::query::ExecutionStats::Key::UPDATED_PROPERTIES] +=
static_cast<int64_t>(properties->items.size());
auto *trigger_ctx_collector = ctx->trigger_context_collector;
if (!trigger_ctx_collector ||
!trigger_ctx_collector->ShouldRegisterObjectPropertyChange<memgraph::query::EdgeAccessor>()) {
return;
}
for (const auto &res : *result) {
const auto property_key = std::get<0>(res);
const auto old_value = memgraph::query::TypedValue(std::get<1>(res));
const auto new_value = memgraph::query::TypedValue(std::get<2>(res));
if (new_value.IsNull()) {
trigger_ctx_collector->RegisterRemovedObjectProperty(e->impl, property_key, old_value);
continue;
}
trigger_ctx_collector->RegisterSetObjectProperty(e->impl, property_key, old_value, new_value);
}
});
}
mgp_error mgp_edge_iter_properties(mgp_edge *e, mgp_memory *memory, mgp_properties_iterator **result) {
// 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

View File

@@ -1685,6 +1685,60 @@ PyObject *PyEdgeSetProperty(PyEdge *self, PyObject *args) {
Py_RETURN_NONE;
}
PyObject *PyEdgeSetProperties(PyEdge *self, PyObject *args) {
MG_ASSERT(self);
MG_ASSERT(self->edge);
MG_ASSERT(self->py_graph);
MG_ASSERT(self->py_graph->graph);
PyObject *props{nullptr};
if (!PyArg_ParseTuple(args, "O", &props)) {
return nullptr;
}
MgpUniquePtr<mgp_map> properties_map{nullptr, mgp_map_destroy};
const auto map_err = CreateMgpObject(properties_map, mgp_map_make_empty, self->py_graph->memory);
if (map_err == mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE) {
throw std::bad_alloc{};
}
if (map_err != mgp_error::MGP_ERROR_NO_ERROR) {
throw std::runtime_error{"Unexpected error during creating mgp_map"};
}
PyObject *key{nullptr};
PyObject *value{nullptr};
Py_ssize_t pos{0};
while (PyDict_Next(props, &pos, &key, &value)) {
// NOLINTNEXTLINE(hicpp-signed-bitwise)
if (!PyUnicode_Check(key)) {
throw std::invalid_argument("Dictionary keys must be strings");
}
const char *k = PyUnicode_AsUTF8(key);
if (!k) {
PyErr_Clear();
throw std::bad_alloc{};
}
MgpUniquePtr<mgp_value> prop_value{PyObjectToMgpValueWithPythonExceptions(value, self->py_graph->memory),
mgp_value_destroy};
if (const auto err = mgp_map_insert(properties_map.get(), k, prop_value.get());
err == mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE) {
throw std::bad_alloc{};
} else if (err != mgp_error::MGP_ERROR_NO_ERROR) {
throw std::runtime_error{"Unexpected error during inserting an item to mgp_map"};
}
}
if (RaiseExceptionFromErrorCode(mgp_edge_set_properties(self->edge, properties_map.get()))) {
return nullptr;
}
Py_RETURN_NONE;
}
static PyMethodDef PyEdgeMethods[] = {
{"__reduce__", reinterpret_cast<PyCFunction>(DisallowPickleAndCopy), METH_NOARGS, "__reduce__ is not supported."},
{"is_valid", reinterpret_cast<PyCFunction>(PyEdgeIsValid), METH_NOARGS,
@@ -1701,6 +1755,8 @@ static PyMethodDef PyEdgeMethods[] = {
"Return edge property with given name."},
{"set_property", reinterpret_cast<PyCFunction>(PyEdgeSetProperty), METH_VARARGS,
"Set the value of the property on the edge."},
{"set_properties", reinterpret_cast<PyCFunction>(PyEdgeSetProperties), METH_VARARGS,
"Set the values of the properties on the edge."},
{nullptr, {}, {}, {}},
};
@@ -1935,6 +1991,61 @@ PyObject *PyVertexSetProperty(PyVertex *self, PyObject *args) {
Py_RETURN_NONE;
}
PyObject *PyVertexSetProperties(PyVertex *self, PyObject *args) {
MG_ASSERT(self);
MG_ASSERT(self->vertex);
MG_ASSERT(self->py_graph);
MG_ASSERT(self->py_graph->graph);
PyObject *props{nullptr};
if (!PyArg_ParseTuple(args, "O", &props)) {
return nullptr;
}
MgpUniquePtr<mgp_map> properties_map{nullptr, mgp_map_destroy};
const auto map_err = CreateMgpObject(properties_map, mgp_map_make_empty, self->py_graph->memory);
if (map_err == mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE) {
throw std::bad_alloc{};
}
if (map_err != mgp_error::MGP_ERROR_NO_ERROR) {
throw std::runtime_error{"Unexpected error during creating mgp_map"};
}
PyObject *key{nullptr};
PyObject *value{nullptr};
Py_ssize_t pos{0};
while (PyDict_Next(props, &pos, &key, &value)) {
// NOLINTNEXTLINE(hicpp-signed-bitwise)
if (!PyUnicode_Check(key)) {
throw std::invalid_argument("Dictionary keys must be strings");
}
const char *k = PyUnicode_AsUTF8(key);
if (!k) {
PyErr_Clear();
throw std::bad_alloc{};
}
MgpUniquePtr<mgp_value> prop_value{PyObjectToMgpValueWithPythonExceptions(value, self->py_graph->memory),
mgp_value_destroy};
if (const auto err = mgp_map_insert(properties_map.get(), k, prop_value.get());
err == mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE) {
throw std::bad_alloc{};
} else if (err != mgp_error::MGP_ERROR_NO_ERROR) {
throw std::runtime_error{"Unexpected error during inserting an item to mgp_map"};
}
}
if (RaiseExceptionFromErrorCode(mgp_vertex_set_properties(self->vertex, properties_map.get()))) {
return nullptr;
}
Py_RETURN_NONE;
}
PyObject *PyVertexAddLabel(PyVertex *self, PyObject *args) {
MG_ASSERT(self);
MG_ASSERT(self->vertex);
@@ -1989,6 +2100,8 @@ static PyMethodDef PyVertexMethods[] = {
"Return vertex property with given name."},
{"set_property", reinterpret_cast<PyCFunction>(PyVertexSetProperty), METH_VARARGS,
"Set the value of the property on the vertex."},
{"set_properties", reinterpret_cast<PyCFunction>(PyVertexSetProperties), METH_VARARGS,
"Set the values of the properties on the vertex."},
{nullptr, {}, {}, {}},
};