Set properties C API extension (#1131)
Add SetProperties into the C++ query module API
This commit is contained in:
parent
9661c52179
commit
02eab6ab9c
@ -401,6 +401,10 @@ inline void vertex_set_property(mgp_vertex *v, const char *property_name, mgp_va
|
||||
MgInvokeVoid(mgp_vertex_set_property, v, property_name, property_value);
|
||||
}
|
||||
|
||||
inline void vertex_set_properties(mgp_vertex *v, struct mgp_map *properties) {
|
||||
MgInvokeVoid(mgp_vertex_set_properties, v, properties);
|
||||
}
|
||||
|
||||
inline mgp_properties_iterator *vertex_iter_properties(mgp_vertex *v, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_properties_iterator *>(mgp_vertex_iter_properties, v, memory);
|
||||
}
|
||||
@ -437,6 +441,10 @@ inline void edge_set_property(mgp_edge *e, const char *property_name, mgp_value
|
||||
MgInvokeVoid(mgp_edge_set_property, e, property_name, property_value);
|
||||
}
|
||||
|
||||
inline void edge_set_properties(mgp_edge *e, struct mgp_map *properties) {
|
||||
MgInvokeVoid(mgp_edge_set_properties, e, properties);
|
||||
}
|
||||
|
||||
inline mgp_properties_iterator *edge_iter_properties(mgp_edge *e, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_properties_iterator *>(mgp_edge_iter_properties, e, memory);
|
||||
}
|
||||
|
@ -664,6 +664,15 @@ enum mgp_error mgp_vertex_underlying_graph_is_mutable(struct mgp_vertex *v, int
|
||||
enum mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_name,
|
||||
struct mgp_value *property_value);
|
||||
|
||||
/// Set the value of properties on a vertex.
|
||||
/// When the value is `null`, then the property is removed from the vertex.
|
||||
/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for storing the property.
|
||||
/// Return mgp_error::MGP_ERROR_IMMUTABLE_OBJECT if `v` is immutable.
|
||||
/// Return mgp_error::MGP_ERROR_DELETED_OBJECT if `v` has been deleted.
|
||||
/// Return mgp_error::MGP_ERROR_SERIALIZATION_ERROR if `v` has been modified by another transaction.
|
||||
/// Return mgp_error::MGP_ERROR_VALUE_CONVERSION if `property_value` is vertex, edge or path.
|
||||
enum mgp_error mgp_vertex_set_properties(struct mgp_vertex *v, struct mgp_map *properties);
|
||||
|
||||
/// Add the label to the vertex.
|
||||
/// If the vertex already has the label, this function does nothing.
|
||||
/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for storing the label.
|
||||
@ -814,6 +823,15 @@ enum mgp_error mgp_edge_get_property(struct mgp_edge *e, const char *property_na
|
||||
/// Return mgp_error::MGP_ERROR_VALUE_CONVERSION if `property_value` is vertex, edge or path.
|
||||
enum mgp_error mgp_edge_set_property(struct mgp_edge *e, const char *property_name, struct mgp_value *property_value);
|
||||
|
||||
/// Set the value of properties on a vertex.
|
||||
/// When the value is `null`, then the property is removed from the vertex.
|
||||
/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for storing the property.
|
||||
/// Return mgp_error::MGP_ERROR_IMMUTABLE_OBJECT if `v` is immutable.
|
||||
/// Return mgp_error::MGP_ERROR_DELETED_OBJECT if `v` has been deleted.
|
||||
/// Return mgp_error::MGP_ERROR_SERIALIZATION_ERROR if `v` has been modified by another transaction.
|
||||
/// Return mgp_error::MGP_ERROR_VALUE_CONVERSION if `property_value` is vertex, edge or path.
|
||||
enum mgp_error mgp_edge_set_properties(struct mgp_edge *e, struct mgp_map *properties);
|
||||
|
||||
/// Start iterating over properties stored in the given edge.
|
||||
/// The properties of the edge are copied when the iterator is created, therefore later changes won't affect them.
|
||||
/// Resulting mgp_properties_iterator needs to be deallocated with
|
||||
|
@ -715,11 +715,14 @@ class Node {
|
||||
bool HasLabel(std::string_view label) const;
|
||||
|
||||
/// @brief Returns an std::map of the node’s properties.
|
||||
std::map<std::string, Value> Properties() const;
|
||||
std::unordered_map<std::string, Value> Properties() const;
|
||||
|
||||
/// @brief Sets the chosen property to the given value.
|
||||
void SetProperty(std::string property, Value value);
|
||||
|
||||
/// @brief Sets the chosen properties to the given values.
|
||||
void SetProperties(std::unordered_map<std::string_view, Value> properties);
|
||||
|
||||
/// @brief Removes the chosen property.
|
||||
void RemoveProperty(std::string property);
|
||||
|
||||
@ -784,11 +787,14 @@ class Relationship {
|
||||
std::string_view Type() const;
|
||||
|
||||
/// @brief Returns an std::map of the relationship’s properties.
|
||||
std::map<std::string, Value> Properties() const;
|
||||
std::unordered_map<std::string, Value> Properties() const;
|
||||
|
||||
/// @brief Sets the chosen property to the given value.
|
||||
void SetProperty(std::string property, Value value);
|
||||
|
||||
/// @brief Sets the chosen properties to the given values.
|
||||
void SetProperties(std::unordered_map<std::string_view, Value> properties);
|
||||
|
||||
/// @brief Removes the chosen property.
|
||||
void RemoveProperty(std::string property);
|
||||
|
||||
@ -2710,9 +2716,9 @@ inline void Node::RemoveLabel(const std::string_view label) {
|
||||
mgp::vertex_remove_label(this->ptr_, mgp_label{.name = label.data()});
|
||||
}
|
||||
|
||||
inline std::map<std::string, Value> Node::Properties() const {
|
||||
inline std::unordered_map<std::string, Value> Node::Properties() const {
|
||||
mgp_properties_iterator *properties_iterator = mgp::MemHandlerCallback(vertex_iter_properties, ptr_);
|
||||
std::map<std::string, Value> property_map;
|
||||
std::unordered_map<std::string, Value> property_map;
|
||||
for (auto *property = mgp::properties_iterator_get(properties_iterator); property;
|
||||
property = mgp::properties_iterator_next(properties_iterator)) {
|
||||
property_map.emplace(std::string(property->name), Value(property->value));
|
||||
@ -2725,6 +2731,17 @@ inline void Node::SetProperty(std::string property, Value value) {
|
||||
mgp::vertex_set_property(ptr_, property.data(), value.ptr());
|
||||
}
|
||||
|
||||
inline void Node::SetProperties(std::unordered_map<std::string_view, Value> properties) {
|
||||
mgp_map *map = mgp::MemHandlerCallback(map_make_empty);
|
||||
|
||||
for (auto const &[k, v] : properties) {
|
||||
mgp::map_insert(map, k.data(), v.ptr());
|
||||
}
|
||||
|
||||
mgp::vertex_set_properties(ptr_, map);
|
||||
mgp::map_destroy(map);
|
||||
}
|
||||
|
||||
inline void Node::RemoveProperty(std::string property) { SetProperty(property, Value()); }
|
||||
|
||||
inline Value Node::GetProperty(const std::string &property) const {
|
||||
@ -2740,7 +2757,7 @@ inline bool Node::operator!=(const Node &other) const { return !(*this == other)
|
||||
|
||||
// this functions is used both in relationship and node ToString
|
||||
inline std::string PropertiesToString(const std::map<std::string, Value> &property_map) {
|
||||
std::string properties{""};
|
||||
std::string properties;
|
||||
const auto map_size = property_map.size();
|
||||
size_t i = 0;
|
||||
for (const auto &[key, value] : property_map) {
|
||||
@ -2762,8 +2779,14 @@ inline const std::string Node::ToString() const {
|
||||
if (labels == ", ") {
|
||||
labels = ""; // dont use labels if they dont exist
|
||||
}
|
||||
std::map<std::string, Value> properties_map{Properties()};
|
||||
std::string properties{PropertiesToString(properties_map)};
|
||||
std::unordered_map<std::string, Value> properties_map{Properties()};
|
||||
std::map<std::string, Value> properties_map_sorted{};
|
||||
|
||||
for (const auto &[k, v] : properties_map) {
|
||||
properties_map_sorted.emplace(k, v);
|
||||
}
|
||||
std::string properties{PropertiesToString(properties_map_sorted)};
|
||||
|
||||
return "(id: " + std::to_string(Id().AsInt()) + labels + ", properties: {" + properties + "})";
|
||||
}
|
||||
|
||||
@ -2807,9 +2830,9 @@ inline mgp::Id Relationship::Id() const { return Id::FromInt(mgp::edge_get_id(pt
|
||||
|
||||
inline std::string_view Relationship::Type() const { return mgp::edge_get_type(ptr_).name; }
|
||||
|
||||
inline std::map<std::string, Value> Relationship::Properties() const {
|
||||
inline std::unordered_map<std::string, Value> Relationship::Properties() const {
|
||||
mgp_properties_iterator *properties_iterator = mgp::MemHandlerCallback(edge_iter_properties, ptr_);
|
||||
std::map<std::string, Value> property_map;
|
||||
std::unordered_map<std::string, Value> property_map;
|
||||
for (mgp_property *property = mgp::properties_iterator_get(properties_iterator); property;
|
||||
property = mgp::properties_iterator_next(properties_iterator)) {
|
||||
property_map.emplace(property->name, Value(property->value));
|
||||
@ -2822,6 +2845,17 @@ inline void Relationship::SetProperty(std::string property, Value value) {
|
||||
mgp::edge_set_property(ptr_, property.data(), value.ptr());
|
||||
}
|
||||
|
||||
inline void Relationship::SetProperties(std::unordered_map<std::string_view, Value> properties) {
|
||||
mgp_map *map = mgp::MemHandlerCallback(map_make_empty);
|
||||
|
||||
for (auto const &[k, v] : properties) {
|
||||
mgp::map_insert(map, k.data(), v.ptr());
|
||||
}
|
||||
|
||||
mgp::edge_set_properties(ptr_, map);
|
||||
mgp::map_destroy(map);
|
||||
}
|
||||
|
||||
inline void Relationship::RemoveProperty(std::string property) { SetProperty(property, Value()); }
|
||||
|
||||
inline Value Relationship::GetProperty(const std::string &property) const {
|
||||
@ -2846,8 +2880,14 @@ inline const std::string Relationship::ToString() const {
|
||||
const auto to = To();
|
||||
|
||||
const std::string type{Type()};
|
||||
std::map<std::string, Value> properties_map{Properties()};
|
||||
std::string properties{PropertiesToString(properties_map)};
|
||||
std::unordered_map<std::string, Value> properties_map{Properties()};
|
||||
std::map<std::string, Value> properties_map_sorted{};
|
||||
|
||||
for (const auto &[k, v] : properties_map) {
|
||||
properties_map_sorted.emplace(k, v);
|
||||
}
|
||||
std::string properties{PropertiesToString(properties_map_sorted)};
|
||||
|
||||
const std::string relationship{"[type: " + type + ", id: " + std::to_string(Id().AsInt()) + ", properties: {" +
|
||||
properties + "}]"};
|
||||
|
||||
@ -2924,8 +2964,14 @@ inline const std::string Path::ToString() const {
|
||||
return_string.append(node.ToString() + "-");
|
||||
|
||||
const Relationship rel = GetRelationshipAt(i);
|
||||
std::map<std::string, Value> properties_map{rel.Properties()};
|
||||
std::string properties = PropertiesToString(properties_map);
|
||||
std::unordered_map<std::string, Value> properties_map{rel.Properties()};
|
||||
std::map<std::string, Value> properties_map_sorted{};
|
||||
|
||||
for (const auto &[k, v] : properties_map) {
|
||||
properties_map_sorted.emplace(k, v);
|
||||
}
|
||||
std::string properties{PropertiesToString(properties_map_sorted)};
|
||||
|
||||
return_string.append("[type: " + std::string(rel.Type()) + ", id: " + std::to_string(rel.Id().AsInt()) +
|
||||
", properties: {" + properties + "}]->");
|
||||
}
|
||||
|
@ -479,6 +479,12 @@ class Properties:
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
def set_properties(self, properties: dict) -> None:
|
||||
if not self._vertex_or_edge.is_valid():
|
||||
raise InvalidContextError()
|
||||
|
||||
self._vertex_or_edge.set_properties(properties)
|
||||
|
||||
|
||||
class EdgeType:
|
||||
"""Type of an Edge."""
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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, {}, {}, {}},
|
||||
};
|
||||
|
||||
|
@ -62,6 +62,7 @@ add_subdirectory(init_file_flags)
|
||||
add_subdirectory(analytical_mode)
|
||||
add_subdirectory(batched_procedures)
|
||||
add_subdirectory(concurrent_query_modules)
|
||||
add_subdirectory(set_properties)
|
||||
|
||||
copy_e2e_python_files(pytest_runner pytest_runner.sh "")
|
||||
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/memgraph-selfsigned.crt DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
8
tests/e2e/set_properties/CMakeLists.txt
Normal file
8
tests/e2e/set_properties/CMakeLists.txt
Normal file
@ -0,0 +1,8 @@
|
||||
function(copy_set_properties_e2e_python_files FILE_NAME)
|
||||
copy_e2e_python_files(set_properties ${FILE_NAME})
|
||||
endfunction()
|
||||
|
||||
copy_set_properties_e2e_python_files(common.py)
|
||||
copy_set_properties_e2e_python_files(set_properties.py)
|
||||
|
||||
add_subdirectory(procedures)
|
47
tests/e2e/set_properties/common.py
Normal file
47
tests/e2e/set_properties/common.py
Normal file
@ -0,0 +1,47 @@
|
||||
# Copyright 2023 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 pytest
|
||||
from gqlalchemy import Memgraph
|
||||
|
||||
|
||||
def execute_and_fetch_all(cursor: mgclient.Cursor, query: str, params: dict = {}) -> typing.List[tuple]:
|
||||
cursor.execute(query, params)
|
||||
return cursor.fetchall()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def connect(**kwargs) -> mgclient.Connection:
|
||||
connection = mgclient.connect(host="localhost", port=7687, **kwargs)
|
||||
connection.autocommit = True
|
||||
cursor = connection.cursor()
|
||||
execute_and_fetch_all(cursor, "USE DATABASE memgraph")
|
||||
try:
|
||||
execute_and_fetch_all(cursor, "DROP DATABASE clean")
|
||||
except:
|
||||
pass
|
||||
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n")
|
||||
yield connection
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def memgraph(**kwargs) -> Memgraph:
|
||||
memgraph = Memgraph()
|
||||
|
||||
yield memgraph
|
||||
|
||||
memgraph.drop_database()
|
||||
memgraph.execute("analyze graph delete statistics;")
|
||||
memgraph.drop_indexes()
|
||||
memgraph.drop_triggers()
|
1
tests/e2e/set_properties/procedures/CMakeLists.txt
Normal file
1
tests/e2e/set_properties/procedures/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
||||
copy_set_properties_e2e_python_files(set_properties_module.py)
|
23
tests/e2e/set_properties/procedures/set_properties_module.py
Normal file
23
tests/e2e/set_properties/procedures/set_properties_module.py
Normal file
@ -0,0 +1,23 @@
|
||||
# Copyright 2021 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 mgp
|
||||
|
||||
|
||||
@mgp.write_proc
|
||||
def set_multiple_properties(ctx: mgp.ProcCtx, vertex_or_edge: mgp.Any) -> mgp.Record(updated=bool):
|
||||
props = dict()
|
||||
props["prop1"] = 1
|
||||
props["prop2"] = 2
|
||||
props["prop3"] = 3
|
||||
|
||||
vertex_or_edge.properties.set_properties(props)
|
||||
return mgp.Record(updated=True)
|
73
tests/e2e/set_properties/set_properties.py
Normal file
73
tests/e2e/set_properties/set_properties.py
Normal file
@ -0,0 +1,73 @@
|
||||
# Copyright 2023 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 sys
|
||||
|
||||
import pytest
|
||||
from common import memgraph
|
||||
|
||||
|
||||
def test_set_multiple_properties_on_vertex_via_query_module(memgraph):
|
||||
memgraph.execute(
|
||||
"""
|
||||
CREATE TRIGGER trigger ON () UPDATE BEFORE COMMIT EXECUTE
|
||||
UNWIND updatedVertices AS updatedVertex
|
||||
SET updatedVertex.vertex.updated = true;
|
||||
"""
|
||||
)
|
||||
|
||||
memgraph.execute("CREATE (n)")
|
||||
has_updated = list(
|
||||
memgraph.execute_and_fetch(
|
||||
"MATCH (n) CALL set_properties_module.set_multiple_properties(n) YIELD updated RETURN updated"
|
||||
)
|
||||
)
|
||||
|
||||
assert len(has_updated) == 1
|
||||
assert has_updated[0]["updated"] == True
|
||||
|
||||
updated_vertex = next(memgraph.execute_and_fetch("MATCH (n) RETURN n"))["n"]
|
||||
|
||||
assert updated_vertex._properties["prop1"] == 1
|
||||
assert updated_vertex._properties["prop2"] == 2
|
||||
assert updated_vertex._properties["prop3"] == 3
|
||||
assert updated_vertex._properties["updated"] == True
|
||||
|
||||
|
||||
def test_set_multiple_properties_on_edge_via_query_module(memgraph):
|
||||
memgraph.execute(
|
||||
"""
|
||||
CREATE TRIGGER trigger ON --> UPDATE BEFORE COMMIT EXECUTE
|
||||
UNWIND updatedEdges AS updatedEdge
|
||||
SET updatedEdge.edge.updated = true;
|
||||
"""
|
||||
)
|
||||
|
||||
memgraph.execute("CREATE (n)-[r:TYPE]->(m)")
|
||||
has_updated = list(
|
||||
memgraph.execute_and_fetch(
|
||||
"MATCH ()-[r]->() CALL set_properties_module.set_multiple_properties(r) YIELD updated RETURN updated"
|
||||
)
|
||||
)
|
||||
|
||||
assert len(has_updated) == 1
|
||||
assert has_updated[0]["updated"] == True
|
||||
|
||||
updated_edge = next(memgraph.execute_and_fetch("MATCH ()-[r]->() RETURN r"))["r"]
|
||||
|
||||
assert updated_edge._properties["prop1"] == 1
|
||||
assert updated_edge._properties["prop2"] == 2
|
||||
assert updated_edge._properties["prop3"] == 3
|
||||
assert updated_edge._properties["updated"] == True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(pytest.main([__file__, "-rA"]))
|
15
tests/e2e/set_properties/workloads.yaml
Normal file
15
tests/e2e/set_properties/workloads.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
set_properties_cluster: &set_properties_cluster
|
||||
cluster:
|
||||
main:
|
||||
args: ["--bolt-port", "7687", "--log-level=TRACE"]
|
||||
log_file: "analyze_graph.log"
|
||||
setup_queries: []
|
||||
validation_queries: []
|
||||
|
||||
|
||||
workloads:
|
||||
- name: "Setting multiple properties"
|
||||
binary: "tests/e2e/pytest_runner.sh"
|
||||
proc: "tests/e2e/set_properties/procedures/"
|
||||
args: ["set_properties/set_properties.py"]
|
||||
<<: *set_properties_cluster
|
@ -466,7 +466,7 @@ TYPED_TEST(CppApiTestFixture, TestNodeProperties) {
|
||||
|
||||
ASSERT_EQ(node_1.Properties().size(), 0);
|
||||
|
||||
std::map<std::string, mgp::Value> node1_prop = node_1.Properties();
|
||||
std::unordered_map<std::string, mgp::Value> node1_prop = node_1.Properties();
|
||||
node_1.SetProperty("b", mgp::Value("b"));
|
||||
|
||||
ASSERT_EQ(node_1.Properties().size(), 1);
|
||||
|
Loading…
Reference in New Issue
Block a user