Python wrapper for write procedures (#234)

* Rename mgp_graph_remove to mgp_graph_delete

* Add mgp_graph_detach_delete

* Add PyGraph functions

* Add _mgp exceptions

* Use unified error handling in python wrapper

* Ignore clang-tidy warnings

* Add mgp.Graph, mgp.Vertex and mgp.Edge mutable functions

* Add python write procedure registration

* Add `is_write` result field to mg.procedures

* Use storage::View::NEW for write procedures

* Add simple tests for write procedures

* Remove false information about IDs
This commit is contained in:
János Benjamin Antal 2021-09-22 08:49:29 +02:00 committed by Antonio Andelic
parent 482798295e
commit be9ed7e879
20 changed files with 1580 additions and 460 deletions

View File

@ -324,7 +324,6 @@ enum mgp_error mgp_list_append(struct mgp_list *list, struct mgp_value *val);
/// original value.
/// In case of a capacity change, the previously contained elements will move in
/// memory and any references to them will be invalid.
/// Return MGP_ERROR_INSUFFICIENT_BUFFER if there's no capacity.
/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_value.
enum mgp_error mgp_list_append_extend(struct mgp_list *list, struct mgp_value *val);
@ -527,13 +526,11 @@ struct mgp_vertex_id {
};
/// Get the ID of given vertex.
/// The ID is only valid for a single query execution, you should never store it
/// globally in a query module.
enum mgp_error mgp_vertex_get_id(struct mgp_vertex *v, struct mgp_vertex_id *result);
/// Result is non-zero if the vertex can be modified.
/// The mutability of the vertex is the same as the graph which it is part of. If a vertex is immutable, then edges
/// cannot be added or removed, properties and labels cannot be set or removed and all of the returned edges will be
/// cannot be created or deleted, properties and labels cannot be set or removed and all of the returned edges will be
/// immutable also.
/// Current implementation always returns without errors.
enum mgp_error mgp_vertex_underlying_graph_is_mutable(struct mgp_vertex *v, int *result);
@ -542,7 +539,7 @@ enum mgp_error mgp_vertex_underlying_graph_is_mutable(struct mgp_vertex *v, int
/// When the value is `null`, then the property is removed from the vertex.
/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for storing the property.
/// Return MGP_ERROR_IMMUTABLE_OBJECT if `v` is immutable.
/// Return MGP_ERROR_DELETED_OBJECT if `v` has been removed.
/// Return MGP_ERROR_DELETED_OBJECT if `v` has been deleted.
/// Return MGP_ERROR_SERIALIZATION_ERROR if `v` has been modified by another transaction.
/// Return MGP_ERROR_VALUE_CONVERSION if `property_value` is vertex, edge or path.
enum mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_name,
@ -552,14 +549,14 @@ enum mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *propert
/// If the vertex already has the label, this function does nothing.
/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for storing the label.
/// Return MGP_ERROR_IMMUTABLE_OBJECT if `v` is immutable.
/// Return MGP_ERROR_DELETED_OBJECT if `v` has been removed.
/// Return MGP_ERROR_DELETED_OBJECT if `v` has been deleted.
/// Return MGP_ERROR_SERIALIZATION_ERROR if `v` has been modified by another transaction.
enum mgp_error mgp_vertex_add_label(struct mgp_vertex *v, struct mgp_label label);
/// Remove the label from the vertex.
/// If the vertex doesn't have the label, this function does nothing.
/// Return MGP_ERROR_IMMUTABLE_OBJECT if `v` is immutable.
/// Return MGP_ERROR_DELETED_OBJECT if `v` has been removed.
/// Return MGP_ERROR_DELETED_OBJECT if `v` has been deleted.
/// Return MGP_ERROR_SERIALIZATION_ERROR if `v` has been modified by another transaction.
enum mgp_error mgp_vertex_remove_label(struct mgp_vertex *v, struct mgp_label label);
@ -575,26 +572,26 @@ void mgp_vertex_destroy(struct mgp_vertex *v);
enum mgp_error mgp_vertex_equal(struct mgp_vertex *v1, struct mgp_vertex *v2, int *result);
/// Get the number of labels a given vertex has.
/// Return MGP_ERROR_DELETED_OBJECT if `v` has been removed.
/// Return MGP_ERROR_DELETED_OBJECT if `v` has been deleted.
enum mgp_error mgp_vertex_labels_count(struct mgp_vertex *v, size_t *result);
/// Get mgp_label in mgp_vertex at given index.
/// Return MGP_ERROR_OUT_OF_RANGE if the index is out of range.
/// Return MGP_ERROR_DELETED_OBJECT if `v` has been removed.
/// Return MGP_ERROR_DELETED_OBJECT if `v` has been deleted.
enum mgp_error mgp_vertex_label_at(struct mgp_vertex *v, size_t index, struct mgp_label *result);
/// Result is non-zero if the given vertex has the given label.
/// Return MGP_ERROR_DELETED_OBJECT if `v` has been removed.
/// Return MGP_ERROR_DELETED_OBJECT if `v` has been deleted.
enum mgp_error mgp_vertex_has_label(struct mgp_vertex *v, struct mgp_label label, int *result);
/// Result is non-zero if the given vertex has a label with given name.
/// Return MGP_ERROR_DELETED_OBJECT if `v` has been removed.
/// Return MGP_ERROR_DELETED_OBJECT if `v` has been deleted.
enum mgp_error mgp_vertex_has_label_named(struct mgp_vertex *v, const char *label_name, int *result);
/// Get a copy of a vertex property mapped to a given name.
/// Resulting value must be freed with mgp_value_destroy.
/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_value.
/// Return MGP_ERROR_DELETED_OBJECT if `v` has been removed.
/// Return MGP_ERROR_DELETED_OBJECT if `v` has been deleted.
enum mgp_error mgp_vertex_get_property(struct mgp_vertex *v, const char *property_name, struct mgp_memory *memory,
struct mgp_value **result);
@ -602,7 +599,7 @@ enum mgp_error mgp_vertex_get_property(struct mgp_vertex *v, const char *propert
/// The resulting mgp_properties_iterator needs to be deallocated with
/// mgp_properties_iterator_destroy.
/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_properties_iterator.
/// Return MGP_ERROR_DELETED_OBJECT if `v` has been removed.
/// Return MGP_ERROR_DELETED_OBJECT if `v` has been deleted.
enum mgp_error mgp_vertex_iter_properties(struct mgp_vertex *v, struct mgp_memory *memory,
struct mgp_properties_iterator **result);
@ -610,7 +607,7 @@ enum mgp_error mgp_vertex_iter_properties(struct mgp_vertex *v, struct mgp_memor
/// The resulting mgp_edges_iterator needs to be deallocated with
/// mgp_edges_iterator_destroy.
/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_edges_iterator.
/// Return MGP_ERROR_DELETED_OBJECT if `v` has been removed.
/// Return MGP_ERROR_DELETED_OBJECT if `v` has been deleted.
enum mgp_error mgp_vertex_iter_in_edges(struct mgp_vertex *v, struct mgp_memory *memory,
struct mgp_edges_iterator **result);
@ -618,7 +615,7 @@ enum mgp_error mgp_vertex_iter_in_edges(struct mgp_vertex *v, struct mgp_memory
/// The resulting mgp_edges_iterator needs to be deallocated with
/// mgp_edges_iterator_destroy.
/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_edges_iterator.
/// Return MGP_ERROR_DELETED_OBJECT if `v` has been removed.
/// Return MGP_ERROR_DELETED_OBJECT if `v` has been deleted.
enum mgp_error mgp_vertex_iter_out_edges(struct mgp_vertex *v, struct mgp_memory *memory,
struct mgp_edges_iterator **result);
@ -646,8 +643,6 @@ struct mgp_edge_id {
};
/// Get the ID of given edge.
/// The ID is only valid for a single query execution, you should never store it
/// globally in a query module.
enum mgp_error mgp_edge_get_id(struct mgp_edge *e, struct mgp_edge_id *result);
/// Result is non-zero if the edge can be modified.
@ -683,7 +678,7 @@ enum mgp_error mgp_edge_get_to(struct mgp_edge *e, struct mgp_vertex **result);
/// Get a copy of a edge property mapped to a given name.
/// Resulting value must be freed with mgp_value_destroy.
/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_value.
/// Return MGP_ERROR_DELETED_OBJECT if `e` has been removed.
/// Return MGP_ERROR_DELETED_OBJECT if `e` has been deleted.
enum mgp_error mgp_edge_get_property(struct mgp_edge *e, const char *property_name, struct mgp_memory *memory,
struct mgp_value **result);
@ -691,7 +686,7 @@ enum mgp_error mgp_edge_get_property(struct mgp_edge *e, const char *property_na
/// When the value is `null`, then the property is removed from the edge.
/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for storing the property.
/// Return MGP_ERROR_IMMUTABLE_OBJECT if `e` is immutable.
/// Return MGP_ERROR_DELETED_OBJECT if `e` has been removed.
/// Return MGP_ERROR_DELETED_OBJECT if `e` has been deleted.
/// Return MGP_ERROR_LOGIC_ERROR if properties on edges are disabled.
/// Return MGP_ERROR_SERIALIZATION_ERROR if `e` has been modified by another transaction.
/// Return MGP_ERROR_VALUE_CONVERSION if `property_value` is vertex, edge or path.
@ -701,7 +696,7 @@ enum mgp_error mgp_edge_set_property(struct mgp_edge *e, const char *property_na
/// Resulting mgp_properties_iterator needs to be deallocated with
/// mgp_properties_iterator_destroy.
/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_properties_iterator.
/// Return MGP_ERROR_DELETED_OBJECT if `e` has been removed.
/// Return MGP_ERROR_DELETED_OBJECT if `e` has been deleted.
enum mgp_error mgp_edge_iter_properties(struct mgp_edge *e, struct mgp_memory *memory,
struct mgp_properties_iterator **result);
@ -710,13 +705,13 @@ struct mgp_graph;
/// Get the vertex corresponding to given ID, or NULL if no such vertex exists.
/// Resulting vertex must be freed using mgp_vertex_destroy.
/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the vertex or if ID is not valid.
/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the vertex.
enum mgp_error mgp_graph_get_vertex_by_id(struct mgp_graph *g, struct mgp_vertex_id id, struct mgp_memory *memory,
struct mgp_vertex **result);
/// Result is non-zero if the graph can be modified.
/// If a graph is immutable, then vertices cannot be added or removed, and all of the returned vertices will be
/// immutable also.
/// If a graph is immutable, then vertices cannot be created or deleted, and all of the returned vertices will be
/// immutable also. The same applies for edges.
/// Current implementation always returns without errors.
enum mgp_error mgp_graph_is_mutable(struct mgp_graph *graph, int *result);
@ -725,27 +720,31 @@ enum mgp_error mgp_graph_is_mutable(struct mgp_graph *graph, int *result);
/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_vertex.
enum mgp_error mgp_graph_create_vertex(struct mgp_graph *graph, struct mgp_memory *memory, struct mgp_vertex **result);
/// Remove a vertex from the graph.
/// Delete a vertex from the graph.
/// Return MGP_ERROR_IMMUTABLE_OBJECT if `graph` is immutable.
/// Return MGP_ERROR_LOGIC_ERROR if `vertex` has edges.
/// Return MGP_ERROR_SERIALIZATION_ERROR if `vertex` has been modified by another transaction.
enum mgp_error mgp_graph_remove_vertex(struct mgp_graph *graph, struct mgp_vertex *vertex);
enum mgp_error mgp_graph_delete_vertex(struct mgp_graph *graph, struct mgp_vertex *vertex);
/// Delete a vertex and all of its edges from the graph.
/// Return MGP_ERROR_IMMUTABLE_OBJECT if `graph` is immutable.
/// Return MGP_ERROR_SERIALIZATION_ERROR if `vertex` has been modified by another transaction.
enum mgp_error mgp_graph_detach_delete_vertex(struct mgp_graph *graph, struct mgp_vertex *vertex);
/// Add a new directed edge between the two vertices with the specified label.
/// NULL is returned if the the edge creation fails for any reason.
/// Return MGP_ERROR_IMMUTABLE_OBJECT if `graph` is immutable.
/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_edge.
/// Return MGP_ERROR_DELETED_OBJECT if `from` or `to` has been removed.
/// Return MGP_ERROR_DELETED_OBJECT if `from` or `to` has been deleted.
/// Return MGP_ERROR_SERIALIZATION_ERROR if `from` or `to` has been modified by another transaction.
enum mgp_error mgp_graph_create_edge(struct mgp_graph *graph, struct mgp_vertex *from, struct mgp_vertex *to,
struct mgp_edge_type type, struct mgp_memory *memory, struct mgp_edge **result);
/// Remove an edge from the graph.
/// Delete an edge from the graph.
/// Return MGP_ERROR_IMMUTABLE_OBJECT if `graph` is immutable.
/// Return MGP_ERROR_LOGIC_ERROR if `vertex` has edges.
/// Return MGP_ERROR_SERIALIZATION_ERROR if `edge`, its source or destination vertex has been modified by another
/// transaction.
enum mgp_error mgp_graph_remove_edge(struct mgp_graph *graph, struct mgp_edge *edge);
enum mgp_error mgp_graph_delete_edge(struct mgp_graph *graph, struct mgp_edge *edge);
/// Iterator over vertices.
struct mgp_vertices_iterator;

File diff suppressed because it is too large Load Diff

View File

@ -3728,11 +3728,6 @@ class CallProcedureCursor : public Cursor {
result_.signature = nullptr;
result_.rows.clear();
result_.error_msg.reset();
// TODO: When we add support for write and eager procedures, we will need
// to plan this operator with Accumulate and pass in storage::View::NEW.
auto graph_view = storage::View::OLD;
ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor,
graph_view);
// It might be a good idea to resolve the procedure name once, at the
// start. Unfortunately, this could deadlock if we tried to invoke a
// procedure from a module (read lock) and reload a module (write lock)
@ -3746,6 +3741,10 @@ class CallProcedureCursor : public Cursor {
throw QueryRuntimeException("There is no procedure named '{}'.", self_->procedure_name_);
}
const auto &[module, proc] = *maybe_found;
const auto graph_view = proc->is_write_procedure ? storage::View::NEW : storage::View::OLD;
ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor,
graph_view);
result_.signature = &proc->results;
// Use evaluation memory, as invoking a procedure is akin to a simple
// evaluation of an expression.

View File

@ -1122,7 +1122,7 @@ mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_nam
case storage::Error::DELETED_OBJECT:
throw DeletedObjectException{"Cannot set the properties of a deleted vertex!"};
case storage::Error::NONEXISTENT_OBJECT:
LOG_FATAL("Query modules mustn't have access to nonexistent objects when setting a property of a vertex!");
LOG_FATAL("Query modules shouldn't have access to nonexistent objects when setting a property of a vertex!");
case storage::Error::PROPERTIES_DISABLED:
case storage::Error::VERTEX_HAS_EDGES:
LOG_FATAL("Unexpected error when setting a property of a vertex.");
@ -1145,7 +1145,7 @@ mgp_error mgp_vertex_add_label(struct mgp_vertex *v, mgp_label label) {
case storage::Error::DELETED_OBJECT:
throw DeletedObjectException{"Cannot add a label to a deleted vertex!"};
case storage::Error::NONEXISTENT_OBJECT:
LOG_FATAL("Query modules mustn't have access to nonexistent objects when adding a label to a vertex!");
LOG_FATAL("Query modules shouldn't have access to nonexistent objects when adding a label to a vertex!");
case storage::Error::PROPERTIES_DISABLED:
case storage::Error::VERTEX_HAS_EDGES:
LOG_FATAL("Unexpected error when adding a label to a vertex.");
@ -1168,7 +1168,7 @@ mgp_error mgp_vertex_remove_label(struct mgp_vertex *v, mgp_label label) {
case storage::Error::DELETED_OBJECT:
throw DeletedObjectException{"Cannot remove a label from a deleted vertex!"};
case storage::Error::NONEXISTENT_OBJECT:
LOG_FATAL("Query modules mustn't have access to nonexistent objects when removing a label from a vertex!");
LOG_FATAL("Query modules shouldn't have access to nonexistent objects when removing a label from a vertex!");
case storage::Error::PROPERTIES_DISABLED:
case storage::Error::VERTEX_HAS_EDGES:
LOG_FATAL("Unexpected error when removing a label from a vertex.");
@ -1201,7 +1201,7 @@ mgp_error mgp_vertex_labels_count(mgp_vertex *v, size_t *result) {
case storage::Error::DELETED_OBJECT:
throw DeletedObjectException{"Cannot get the labels of a deleted vertex!"};
case storage::Error::NONEXISTENT_OBJECT:
LOG_FATAL("Query modules mustn't have access to nonexistent objects when getting vertex labels!");
LOG_FATAL("Query modules shouldn't have access to nonexistent objects when getting vertex labels!");
case storage::Error::PROPERTIES_DISABLED:
case storage::Error::VERTEX_HAS_EDGES:
case storage::Error::SERIALIZATION_ERROR:
@ -1223,7 +1223,7 @@ mgp_error mgp_vertex_label_at(mgp_vertex *v, size_t i, mgp_label *result) {
case storage::Error::DELETED_OBJECT:
throw DeletedObjectException{"Cannot get a label of a deleted vertex!"};
case storage::Error::NONEXISTENT_OBJECT:
LOG_FATAL("Query modules mustn't have access to nonexistent objects when getting a label of a vertex!");
LOG_FATAL("Query modules shouldn't have access to nonexistent objects when getting a label of a vertex!");
case storage::Error::PROPERTIES_DISABLED:
case storage::Error::VERTEX_HAS_EDGES:
case storage::Error::SERIALIZATION_ERROR:
@ -1256,7 +1256,8 @@ mgp_error mgp_vertex_has_label_named(mgp_vertex *v, const char *name, int *resul
throw DeletedObjectException{"Cannot check the existence of a label on a deleted vertex!"};
case storage::Error::NONEXISTENT_OBJECT:
LOG_FATAL(
"Query modules mustn't have access to nonexistent objects when checking the existence of a label on "
"Query modules shouldn't have access to nonexistent objects when checking the existence of a label "
"on "
"a vertex!");
case storage::Error::PROPERTIES_DISABLED:
case storage::Error::VERTEX_HAS_EDGES:
@ -1284,7 +1285,7 @@ mgp_error mgp_vertex_get_property(mgp_vertex *v, const char *name, mgp_memory *m
throw DeletedObjectException{"Cannot get a property of a deleted vertex!"};
case storage::Error::NONEXISTENT_OBJECT:
LOG_FATAL(
"Query modules mustn't have access to nonexistent objects when getting a property of a vertex.");
"Query modules shouldn't have access to nonexistent objects when getting a property of a vertex.");
case storage::Error::PROPERTIES_DISABLED:
case storage::Error::VERTEX_HAS_EDGES:
case storage::Error::SERIALIZATION_ERROR:
@ -1310,7 +1311,8 @@ mgp_error mgp_vertex_iter_properties(mgp_vertex *v, mgp_memory *memory, mgp_prop
throw DeletedObjectException{"Cannot get the properties of a deleted vertex!"};
case storage::Error::NONEXISTENT_OBJECT:
LOG_FATAL(
"Query modules mustn't have access to nonexistent objects when getting the properties of a vertex.");
"Query modules shouldn't have access to nonexistent objects when getting the properties of a "
"vertex.");
case storage::Error::PROPERTIES_DISABLED:
case storage::Error::VERTEX_HAS_EDGES:
case storage::Error::SERIALIZATION_ERROR:
@ -1337,7 +1339,7 @@ mgp_error mgp_vertex_iter_in_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges_
throw DeletedObjectException{"Cannot get the inbound edges of a deleted vertex!"};
case storage::Error::NONEXISTENT_OBJECT:
LOG_FATAL(
"Query modules mustn't have access to nonexistent objects when getting the inbound edges of a "
"Query modules shouldn't have access to nonexistent objects when getting the inbound edges of a "
"vertex.");
case storage::Error::PROPERTIES_DISABLED:
case storage::Error::VERTEX_HAS_EDGES:
@ -1369,7 +1371,7 @@ mgp_error mgp_vertex_iter_out_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges
throw DeletedObjectException{"Cannot get the outbound edges of a deleted vertex!"};
case storage::Error::NONEXISTENT_OBJECT:
LOG_FATAL(
"Query modules mustn't have access to nonexistent objects when getting the outbound edges of a "
"Query modules shouldn't have access to nonexistent objects when getting the outbound edges of a "
"vertex.");
case storage::Error::PROPERTIES_DISABLED:
case storage::Error::VERTEX_HAS_EDGES:
@ -1483,7 +1485,8 @@ mgp_error mgp_edge_get_property(mgp_edge *e, const char *name, mgp_memory *memor
case storage::Error::DELETED_OBJECT:
throw DeletedObjectException{"Cannot get a property of a deleted edge!"};
case storage::Error::NONEXISTENT_OBJECT:
LOG_FATAL("Query modules mustn't have access to nonexistent objects when getting a property of an edge.");
LOG_FATAL(
"Query modules shouldn't have access to nonexistent objects when getting a property of an edge.");
case storage::Error::PROPERTIES_DISABLED:
case storage::Error::VERTEX_HAS_EDGES:
case storage::Error::SERIALIZATION_ERROR:
@ -1508,7 +1511,7 @@ mgp_error mgp_edge_set_property(struct mgp_edge *e, const char *property_name, m
case storage::Error::DELETED_OBJECT:
throw DeletedObjectException{"Cannot set the properties of a deleted edge!"};
case storage::Error::NONEXISTENT_OBJECT:
LOG_FATAL("Query modules mustn't have access to nonexistent objects when setting a property of an edge!");
LOG_FATAL("Query modules shouldn't have access to nonexistent objects when setting a property of an edge!");
case storage::Error::PROPERTIES_DISABLED:
throw std::logic_error{"Cannot set the properties of edges, because properties on edges are disabled!"};
case storage::Error::VERTEX_HAS_EDGES:
@ -1535,7 +1538,7 @@ mgp_error mgp_edge_iter_properties(mgp_edge *e, mgp_memory *memory, mgp_properti
throw DeletedObjectException{"Cannot get the properties of a deleted edge!"};
case storage::Error::NONEXISTENT_OBJECT:
LOG_FATAL(
"Query modules mustn't have access to nonexistent objects when getting the properties of an edge.");
"Query modules shouldn't have access to nonexistent objects when getting the properties of an edge.");
case storage::Error::PROPERTIES_DISABLED:
case storage::Error::VERTEX_HAS_EDGES:
case storage::Error::SERIALIZATION_ERROR:
@ -1577,7 +1580,7 @@ mgp_error mgp_graph_create_vertex(struct mgp_graph *graph, mgp_memory *memory, m
result);
}
mgp_error mgp_graph_remove_vertex(struct mgp_graph *graph, mgp_vertex *vertex) {
mgp_error mgp_graph_delete_vertex(struct mgp_graph *graph, mgp_vertex *vertex) {
return WrapExceptions([=] {
if (!MgpGraphIsMutable(*graph)) {
throw ImmutableObjectException{"Cannot remove a vertex from an immutable graph!"};
@ -1587,7 +1590,7 @@ mgp_error mgp_graph_remove_vertex(struct mgp_graph *graph, mgp_vertex *vertex) {
if (result.HasError()) {
switch (result.GetError()) {
case storage::Error::NONEXISTENT_OBJECT:
LOG_FATAL("Query modules mustn't have access to nonexistent objects when removing a vertex!");
LOG_FATAL("Query modules shouldn't have access to nonexistent objects when removing a vertex!");
case storage::Error::DELETED_OBJECT:
case storage::Error::PROPERTIES_DISABLED:
LOG_FATAL("Unexpected error when removing a vertex.");
@ -1600,6 +1603,28 @@ mgp_error mgp_graph_remove_vertex(struct mgp_graph *graph, mgp_vertex *vertex) {
});
}
mgp_error mgp_graph_detach_delete_vertex(struct mgp_graph *graph, mgp_vertex *vertex) {
return WrapExceptions([=] {
if (!MgpGraphIsMutable(*graph)) {
throw ImmutableObjectException{"Cannot remove a vertex from an immutable graph!"};
}
const auto result = graph->impl->DetachRemoveVertex(&vertex->impl);
if (result.HasError()) {
switch (result.GetError()) {
case storage::Error::NONEXISTENT_OBJECT:
LOG_FATAL("Query modules shouldn't have access to nonexistent objects when removing a vertex!");
case storage::Error::DELETED_OBJECT:
case storage::Error::PROPERTIES_DISABLED:
case storage::Error::VERTEX_HAS_EDGES:
LOG_FATAL("Unexpected error when removing a vertex.");
case storage::Error::SERIALIZATION_ERROR:
throw SerializationException{"Cannot serialize removing a vertex."};
}
}
});
}
mgp_error mgp_graph_create_edge(mgp_graph *graph, mgp_vertex *from, mgp_vertex *to, mgp_edge_type type,
mgp_memory *memory, mgp_edge **result) {
return WrapExceptions(
@ -1614,7 +1639,7 @@ mgp_error mgp_graph_create_edge(mgp_graph *graph, mgp_vertex *from, mgp_vertex *
case storage::Error::DELETED_OBJECT:
throw DeletedObjectException{"Cannot add an edge to a deleted vertex!"};
case storage::Error::NONEXISTENT_OBJECT:
LOG_FATAL("Query modules mustn't have access to nonexistent objects when creating an edge!");
LOG_FATAL("Query modules shouldn't have access to nonexistent objects when creating an edge!");
case storage::Error::PROPERTIES_DISABLED:
case storage::Error::VERTEX_HAS_EDGES:
LOG_FATAL("Unexpected error when creating an edge.");
@ -1627,7 +1652,7 @@ mgp_error mgp_graph_create_edge(mgp_graph *graph, mgp_vertex *from, mgp_vertex *
result);
}
mgp_error mgp_graph_remove_edge(struct mgp_graph *graph, mgp_edge *edge) {
mgp_error mgp_graph_delete_edge(struct mgp_graph *graph, mgp_edge *edge) {
return WrapExceptions([=] {
if (!MgpGraphIsMutable(*graph)) {
throw ImmutableObjectException{"Cannot remove an edge from an immutable graph!"};
@ -1637,7 +1662,7 @@ mgp_error mgp_graph_remove_edge(struct mgp_graph *graph, mgp_edge *edge) {
if (result.HasError()) {
switch (result.GetError()) {
case storage::Error::NONEXISTENT_OBJECT:
LOG_FATAL("Query modules mustn't have access to nonexistent objects when removing an edge!");
LOG_FATAL("Query modules shouldn't have access to nonexistent objects when removing an edge!");
case storage::Error::DELETED_OBJECT:
case storage::Error::PROPERTIES_DISABLED:
case storage::Error::VERTEX_HAS_EDGES:

View File

@ -162,7 +162,18 @@ void RegisterMgProcedures(
PrintProcSignature(proc, &ss);
const auto signature = ss.str();
MgpUniquePtr<mgp_value> signature_value{nullptr, mgp_value_destroy};
if (const auto err = CreateMgpObject(signature_value, mgp_value_make_string, full_name.c_str(), memory);
if (const auto err = CreateMgpObject(signature_value, mgp_value_make_string, signature.c_str(), memory);
err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
static_cast<void>(mgp_result_set_error_msg(result, "Not enough memory!"));
return;
} else if (err != MGP_ERROR_NO_ERROR) {
static_cast<void>(mgp_result_set_error_msg(result, "Unexpected error"));
return;
}
MgpUniquePtr<mgp_value> is_write_value{nullptr, mgp_value_destroy};
if (const auto err =
CreateMgpObject(is_write_value, mgp_value_make_bool, proc.is_write_procedure ? 1 : 0, memory);
err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
static_cast<void>(mgp_result_set_error_msg(result, "Not enough memory!"));
return;
@ -172,7 +183,8 @@ void RegisterMgProcedures(
}
const auto err1 = mgp_result_record_insert(record, "name", name_value.get());
const auto err2 = mgp_result_record_insert(record, "signature", signature_value.get());
if (err1 != MGP_ERROR_NO_ERROR || err2 != MGP_ERROR_NO_ERROR) {
const auto err3 = mgp_result_record_insert(record, "is_write", is_write_value.get());
if (err1 != MGP_ERROR_NO_ERROR || err2 != MGP_ERROR_NO_ERROR || err3 != MGP_ERROR_NO_ERROR) {
static_cast<void>(mgp_result_set_error_msg(result, "Unable to set the result!"));
return;
}
@ -182,13 +194,14 @@ void RegisterMgProcedures(
mgp_proc procedures("procedures", procedures_cb, utils::NewDeleteResource(), false);
MG_ASSERT(mgp_proc_add_result(&procedures, "name", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
MG_ASSERT(mgp_proc_add_result(&procedures, "signature", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
MG_ASSERT(mgp_proc_add_result(&procedures, "is_write", Call<mgp_type *>(mgp_type_bool)) == MGP_ERROR_NO_ERROR);
module->AddProcedure("procedures", std::move(procedures));
}
void RegisterMgTransformations(const std::map<std::string, std::unique_ptr<Module>, std::less<>> *all_modules,
BuiltinModule *module) {
auto procedures_cb = [all_modules](mgp_list * /*unused*/, mgp_graph * /*unused*/, mgp_result *result,
mgp_memory *memory) {
auto transformations_cb = [all_modules](mgp_list * /*unused*/, mgp_graph * /*unused*/, mgp_result *result,
mgp_memory *memory) {
for (const auto &[module_name, module] : *all_modules) {
// Return the results in sorted order by module and by transformation.
static_assert(
@ -225,7 +238,7 @@ void RegisterMgTransformations(const std::map<std::string, std::unique_ptr<Modul
}
}
};
mgp_proc procedures("transformations", procedures_cb, utils::NewDeleteResource(), false);
mgp_proc procedures("transformations", transformations_cb, utils::NewDeleteResource(), false);
MG_ASSERT(mgp_proc_add_result(&procedures, "name", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
module->AddProcedure("transformations", std::move(procedures));
}

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,16 @@
function(copy_e2e_python_files TARGET_PREFIX FILE_NAME)
add_custom_target(memgraph__e2e__${TARGET_PREFIX}__${FILE_NAME} ALL
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/${FILE_NAME}
${CMAKE_CURRENT_BINARY_DIR}/${FILE_NAME}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${FILE_NAME})
endfunction()
add_subdirectory(replication)
add_subdirectory(memory)
add_subdirectory(triggers)
add_subdirectory(isolation_levels)
add_subdirectory(streams)
add_subdirectory(write_procedures)
copy_e2e_python_files(pytest_runner pytest_runner.sh "")

View File

@ -1,14 +1,10 @@
function(copy_streams_e2e_python_files FILE_NAME)
add_custom_target(memgraph__e2e__streams__${FILE_NAME} ALL
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/${FILE_NAME}
${CMAKE_CURRENT_BINARY_DIR}/${FILE_NAME}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${FILE_NAME})
copy_e2e_python_files(streams ${FILE_NAME})
endfunction()
copy_streams_e2e_python_files(common.py)
copy_streams_e2e_python_files(conftest.py)
copy_streams_e2e_python_files(streams_tests.py)
copy_streams_e2e_python_files(streams_owner_tests.py)
copy_streams_e2e_python_files(streams_test_runner.sh)
add_subdirectory(transformations)

View File

@ -8,12 +8,12 @@ template_cluster: &template_cluster
workloads:
- name: "Streams start, stop and show"
binary: "tests/e2e/streams/streams_test_runner.sh"
binary: "tests/e2e/pytest_runner.sh"
proc: "tests/e2e/streams/transformations/"
args: ["streams_tests.py"]
args: ["streams/streams_tests.py"]
<<: *template_cluster
- name: "Streams with users"
binary: "tests/e2e/streams/streams_test_runner.sh"
binary: "tests/e2e/pytest_runner.sh"
proc: "tests/e2e/streams/transformations/"
args: ["streams_owner_tests.py"]
args: ["streams/streams_owner_tests.py"]
<<: *template_cluster

View File

@ -0,0 +1,9 @@
function(copy_write_procedures_e2e_python_files FILE_NAME)
copy_e2e_python_files(write_procedures ${FILE_NAME})
endfunction()
copy_write_procedures_e2e_python_files(common.py)
copy_write_procedures_e2e_python_files(conftest.py)
copy_write_procedures_e2e_python_files(simple_write.py)
add_subdirectory(procedures)

View File

@ -0,0 +1,23 @@
import mgclient
import typing
def execute_and_fetch_all(cursor: mgclient.Cursor, query: str,
params: dict = {}) -> typing.List[tuple]:
cursor.execute(query, params)
return cursor.fetchall()
def connect(**kwargs) -> mgclient.Connection:
connection = mgclient.connect(host="localhost", port=7687, **kwargs)
connection.autocommit = True
return connection
def has_n_result_row(cursor: mgclient.Cursor, query: str, n: int):
results = execute_and_fetch_all(cursor, query)
return len(results) == n
def has_one_result_row(cursor: mgclient.Cursor, query: str):
return has_n_result_row(cursor, query, 1)

View File

@ -0,0 +1,11 @@
import pytest
from common import execute_and_fetch_all, connect
@pytest.fixture(autouse=True)
def connection():
connection = connect()
yield connection
cursor = connection.cursor()
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n")

View File

@ -0,0 +1,2 @@
copy_write_procedures_e2e_python_files(write.py)
copy_write_procedures_e2e_python_files(read.py)

View File

@ -0,0 +1,12 @@
import mgp
@mgp.read_proc
def underlying_graph_is_mutable(ctx: mgp.ProcCtx,
object: mgp.Any) -> mgp.Record(mutable=bool):
return mgp.Record(mutable=object.underlying_graph_is_mutable())
@mgp.read_proc
def graph_is_mutable(ctx: mgp.ProcCtx) -> mgp.Record(mutable=bool):
return mgp.Record(mutable=ctx.graph.is_mutable())

View File

@ -0,0 +1,74 @@
import mgp
@mgp.write_proc
def create_vertex(ctx: mgp.ProcCtx) -> mgp.Record(v=mgp.Any):
v = None
try:
v = ctx.graph.create_vertex()
except RuntimeError as e:
return mgp.Record(v=str(e))
return mgp.Record(v=v)
@mgp.write_proc
def delete_vertex(ctx: mgp.ProcCtx, v: mgp.Any) -> mgp.Record():
ctx.graph.delete_vertex(v)
return mgp.Record()
@mgp.write_proc
def detach_delete_vertex(ctx: mgp.ProcCtx, v: mgp.Any) -> mgp.Record():
ctx.graph.detach_delete_vertex(v)
return mgp.Record()
@mgp.write_proc
def create_edge(ctx: mgp.ProcCtx, from_vertex: mgp.Vertex,
to_vertex: mgp.Vertex,
edge_type: str) -> mgp.Record(e=mgp.Any):
e = None
try:
e = ctx.graph.create_edge(
from_vertex, to_vertex, mgp.EdgeType(edge_type))
except RuntimeError as ex:
return mgp.Record(e=str(ex))
return mgp.Record(e=e)
@mgp.write_proc
def delete_edge(ctx: mgp.ProcCtx, edge: mgp.Edge) -> mgp.Record():
ctx.graph.delete_edge(edge)
return mgp.Record()
@mgp.write_proc
def set_property(ctx: mgp.ProcCtx, object: mgp.Any,
name: str, value: mgp.Nullable[mgp.Any]) -> mgp.Record():
object.properties.set(name, value)
return mgp.Record()
@mgp.write_proc
def add_label(ctx: mgp.ProcCtx, object: mgp.Any,
name: str) -> mgp.Record(o=mgp.Any):
object.add_label(name)
return mgp.Record(o=object)
@mgp.write_proc
def remove_label(ctx: mgp.ProcCtx, object: mgp.Any,
name: str) -> mgp.Record(o=mgp.Any):
object.remove_label(name)
return mgp.Record(o=object)
@mgp.write_proc
def underlying_graph_is_mutable(ctx: mgp.ProcCtx,
object: mgp.Any) -> mgp.Record(mutable=bool):
return mgp.Record(mutable=object.underlying_graph_is_mutable())
@mgp.write_proc
def graph_is_mutable(ctx: mgp.ProcCtx) -> mgp.Record(mutable=bool):
return mgp.Record(mutable=ctx.graph.is_mutable())

View File

@ -0,0 +1,182 @@
import typing
import mgclient
import sys
import pytest
from common import (execute_and_fetch_all,
has_one_result_row, has_n_result_row)
def test_is_write(connection):
is_write = 2
result_order = "name, signature, is_write"
cursor = connection.cursor()
for proc in execute_and_fetch_all(
cursor, "CALL mg.procedures() YIELD * WITH name, signature, "
"is_write WHERE name STARTS WITH 'write' "
f"RETURN {result_order}"):
assert proc[is_write] is True
for proc in execute_and_fetch_all(
cursor, "CALL mg.procedures() YIELD * WITH name, signature, "
"is_write WHERE NOT name STARTS WITH 'write' "
f"RETURN {result_order}"):
assert proc[is_write] is False
assert cursor.description[0].name == "name"
assert cursor.description[1].name == "signature"
assert cursor.description[2].name == "is_write"
def test_single_vertex(connection):
cursor = connection.cursor()
assert has_n_result_row(cursor, "MATCH (n) RETURN n", 0)
result = execute_and_fetch_all(
cursor, "CALL write.create_vertex() YIELD v RETURN v")
vertex = result[0][0]
assert isinstance(vertex, mgclient.Node)
assert has_one_result_row(cursor, "MATCH (n) RETURN n")
assert vertex.labels == set()
assert vertex.properties == {}
def add_label(label: str):
execute_and_fetch_all(
cursor, f"MATCH (n) CALL write.add_label(n, '{label}') "
"YIELD * RETURN *")
def remove_label(label: str):
execute_and_fetch_all(
cursor, f"MATCH (n) CALL write.remove_label(n, '{label}') "
"YIELD * RETURN *")
def get_vertex() -> mgclient.Node:
return execute_and_fetch_all(cursor, "MATCH (n) RETURN n")[0][0]
def set_property(property_name: str, property: typing.Any):
nonlocal cursor
execute_and_fetch_all(
cursor, f"MATCH (n) CALL write.set_property(n, '{property_name}', "
"$property) YIELD * RETURN *", {"property": property})
label_1 = "LABEL1"
label_2 = "LABEL2"
add_label(label_1)
assert get_vertex().labels == {label_1}
add_label(label_1)
assert get_vertex().labels == {label_1}
add_label(label_2)
assert get_vertex().labels == {label_1, label_2}
remove_label(label_1)
assert get_vertex().labels == {label_2}
property_name = "prop"
property_value_1 = 1
property_value_2 = [42, 24]
set_property(property_name, property_value_1)
assert get_vertex().properties == {property_name: property_value_1}
set_property(property_name, property_value_2)
assert get_vertex().properties == {property_name: property_value_2}
set_property(property_name, None)
assert get_vertex().properties == {}
execute_and_fetch_all(
cursor, "MATCH (n) CALL write.delete_vertex(n) YIELD * RETURN 1")
assert has_n_result_row(cursor, "MATCH (n) RETURN n", 0)
def test_single_edge(connection):
cursor = connection.cursor()
assert has_n_result_row(cursor, "MATCH (n) RETURN n", 0)
v1_id = execute_and_fetch_all(
cursor, "CALL write.create_vertex() YIELD v RETURN v")[0][0].id
v2_id = execute_and_fetch_all(
cursor, "CALL write.create_vertex() YIELD v RETURN v")[0][0].id
edge_type = "EDGE"
edge = execute_and_fetch_all(
cursor, f"MATCH (n) WHERE id(n) = {v1_id} "
f"MATCH (m) WHERE id(m) = {v2_id} "
f"CALL write.create_edge(n, m, '{edge_type}') "
"YIELD e RETURN e")[0][0]
assert edge.type == edge_type
assert edge.properties == {}
property_name = "very_looong_prooooperty_naaame"
property_value_1 = {"a": 1, "b": 3.4, "c": [666]}
property_value_2 = 64
def get_edge() -> mgclient.Node:
return execute_and_fetch_all(cursor, "MATCH ()-[e]->() RETURN e")[0][0]
def set_property(property_name: str, property: typing.Any):
nonlocal cursor
execute_and_fetch_all(
cursor, "MATCH ()-[e]->() "
f"CALL write.set_property(e, '{property_name}', "
"$property) YIELD * RETURN *", {"property": property})
set_property(property_name, property_value_1)
assert get_edge().properties == {property_name: property_value_1}
set_property(property_name, property_value_2)
assert get_edge().properties == {property_name: property_value_2}
set_property(property_name, None)
assert get_edge().properties == {}
execute_and_fetch_all(
cursor, "MATCH ()-[e]->() CALL write.delete_edge(e) YIELD * RETURN 1")
assert has_n_result_row(cursor, "MATCH ()-[e]->() RETURN e", 0)
def test_detach_delete_vertex(connection):
cursor = connection.cursor()
assert has_n_result_row(cursor, "MATCH (n) RETURN n", 0)
v1_id = execute_and_fetch_all(
cursor, "CALL write.create_vertex() YIELD v RETURN v")[0][0].id
v2_id = execute_and_fetch_all(
cursor, "CALL write.create_vertex() YIELD v RETURN v")[0][0].id
execute_and_fetch_all(
cursor, f"MATCH (n) WHERE id(n) = {v1_id} "
f"MATCH (m) WHERE id(m) = {v2_id} "
f"CALL write.create_edge(n, m, 'EDGE') "
"YIELD e RETURN e")
assert has_one_result_row(cursor, "MATCH (n)-[e]->(m) RETURN n, e, m")
execute_and_fetch_all(
cursor, f"MATCH (n) WHERE id(n) = {v1_id} "
"CALL write.detach_delete_vertex(n) YIELD * RETURN 1")
assert has_n_result_row(cursor, "MATCH (n)-[e]->(m) RETURN n, e, m", 0)
assert has_n_result_row(cursor, "MATCH ()-[e]->() RETURN e", 0)
assert has_one_result_row(
cursor, f"MATCH (n) WHERE id(n) = {v2_id} RETURN n")
def test_graph_mutability(connection):
cursor = connection.cursor()
assert has_n_result_row(cursor, "MATCH (n) RETURN n", 0)
v1_id = execute_and_fetch_all(
cursor, "CALL write.create_vertex() YIELD v RETURN v")[0][0].id
v2_id = execute_and_fetch_all(
cursor, "CALL write.create_vertex() YIELD v RETURN v")[0][0].id
execute_and_fetch_all(
cursor, f"MATCH (n) WHERE id(n) = {v1_id} "
f"MATCH (m) WHERE id(m) = {v2_id} "
f"CALL write.create_edge(n, m, 'EDGE') "
"YIELD e RETURN e")
def test_mutability(is_write: bool):
module = "write" if is_write else "read"
assert execute_and_fetch_all(
cursor, f"CALL {module}.graph_is_mutable() "
"YIELD mutable RETURN mutable")[0][0] is is_write
assert execute_and_fetch_all(
cursor, "MATCH (n) "
f"CALL {module}.underlying_graph_is_mutable(n) "
"YIELD mutable RETURN mutable")[0][0] is is_write
assert execute_and_fetch_all(
cursor, "MATCH (n)-[e]->(m) "
f"CALL {module}.underlying_graph_is_mutable(e) "
"YIELD mutable RETURN mutable")[0][0] is is_write
test_mutability(True)
test_mutability(False)
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-rA"]))

View File

@ -0,0 +1,14 @@
template_cluster: &template_cluster
cluster:
main:
args: ["--bolt-port", "7687", "--log-level=TRACE"]
log_file: "write-procedures-e2e.log"
setup_queries: []
validation_queries: []
workloads:
- name: "Write procedures simple"
binary: "tests/e2e/pytest_runner.sh"
proc: "tests/e2e/write_procedures/procedures/"
args: ["write_procedures/simple_write.py"]
<<: *template_cluster

View File

@ -2698,8 +2698,7 @@ TEST_P(CypherMainVisitorTest, CallYieldAsterisk) {
ASSERT_TRUE(identifier->user_declared_);
identifier_names.push_back(identifier->name_);
}
std::vector<std::string> expected_names{"name", "signature"};
ASSERT_EQ(identifier_names, expected_names);
ASSERT_THAT(identifier_names, UnorderedElementsAre("name", "signature", "is_write"));
ASSERT_EQ(identifier_names, call_proc->result_fields_);
CheckCallProcedureDefaultMemoryLimit(ast_generator, *call_proc);
}
@ -2724,8 +2723,7 @@ TEST_P(CypherMainVisitorTest, CallYieldAsteriskReturnAsterisk) {
ASSERT_TRUE(identifier->user_declared_);
identifier_names.push_back(identifier->name_);
}
std::vector<std::string> expected_names{"name", "signature"};
ASSERT_EQ(identifier_names, expected_names);
ASSERT_THAT(identifier_names, UnorderedElementsAre("name", "signature", "is_write"));
ASSERT_EQ(identifier_names, call_proc->result_fields_);
CheckCallProcedureDefaultMemoryLimit(ast_generator, *call_proc);
}

View File

@ -154,7 +154,7 @@ TEST_F(MgpGraphTest, CreateVertex) {
read_uncommited_accessor.FindVertex(storage::Gid::FromInt(vertex_id.as_int), storage::View::NEW).has_value());
}
TEST_F(MgpGraphTest, RemoveVertex) {
TEST_F(MgpGraphTest, DeleteVertex) {
storage::Gid vertex_id{};
{
auto accessor = CreateDbAccessor(storage::IsolationLevel::SNAPSHOT_ISOLATION);
@ -168,11 +168,24 @@ TEST_F(MgpGraphTest, RemoveVertex) {
MgpVertexPtr vertex{
EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, mgp_vertex_id{vertex_id.AsInt()}, &memory)};
EXPECT_NE(vertex, nullptr);
EXPECT_SUCCESS(mgp_graph_remove_vertex(&graph, vertex.get()));
EXPECT_SUCCESS(mgp_graph_delete_vertex(&graph, vertex.get()));
EXPECT_EQ(CountVertices(read_uncommited_accessor, storage::View::NEW), 0);
}
TEST_F(MgpGraphTest, CreateRemoveWithImmutableGraph) {
TEST_F(MgpGraphTest, DetachDeleteVertex) {
const auto vertex_ids = CreateEdge();
auto graph = CreateGraph();
auto read_uncommited_accessor = storage.Access(storage::IsolationLevel::READ_UNCOMMITTED);
EXPECT_EQ(CountVertices(read_uncommited_accessor, storage::View::NEW), 2);
MgpVertexPtr vertex{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph,
mgp_vertex_id{vertex_ids.front().AsInt()}, &memory)};
EXPECT_EQ(mgp_graph_delete_vertex(&graph, vertex.get()), MGP_ERROR_LOGIC_ERROR);
EXPECT_EQ(CountVertices(read_uncommited_accessor, storage::View::NEW), 2);
EXPECT_SUCCESS(mgp_graph_detach_delete_vertex(&graph, vertex.get()));
EXPECT_EQ(CountVertices(read_uncommited_accessor, storage::View::NEW), 1);
}
TEST_F(MgpGraphTest, CreateDeleteWithImmutableGraph) {
storage::Gid vertex_id{};
{
auto accessor = CreateDbAccessor(storage::IsolationLevel::SNAPSHOT_ISOLATION);
@ -189,10 +202,10 @@ TEST_F(MgpGraphTest, CreateRemoveWithImmutableGraph) {
MgpVertexPtr created_vertex{raw_vertex};
EXPECT_EQ(created_vertex, nullptr);
EXPECT_EQ(CountVertices(read_uncommited_accessor, storage::View::NEW), 1);
MgpVertexPtr vertex_to_remove{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &immutable_graph,
MgpVertexPtr vertex_to_delete{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &immutable_graph,
mgp_vertex_id{vertex_id.AsInt()}, &memory)};
ASSERT_NE(vertex_to_remove, nullptr);
EXPECT_EQ(mgp_graph_remove_vertex(&immutable_graph, vertex_to_remove.get()), MGP_ERROR_IMMUTABLE_OBJECT);
ASSERT_NE(vertex_to_delete, nullptr);
EXPECT_EQ(mgp_graph_delete_vertex(&immutable_graph, vertex_to_delete.get()), MGP_ERROR_IMMUTABLE_OBJECT);
EXPECT_EQ(CountVertices(read_uncommited_accessor, storage::View::NEW), 1);
}
@ -376,7 +389,7 @@ TEST_F(MgpGraphTest, ModifyImmutableVertex) {
EXPECT_EQ(mgp_vertex_set_property(vertex.get(), "property", value.get()), MGP_ERROR_IMMUTABLE_OBJECT);
}
TEST_F(MgpGraphTest, CreateRemoveEdge) {
TEST_F(MgpGraphTest, CreateDeleteEdge) {
std::array<storage::Gid, 2> vertex_ids{};
{
auto accessor = CreateDbAccessor(storage::IsolationLevel::SNAPSHOT_ISOLATION);
@ -397,11 +410,11 @@ TEST_F(MgpGraphTest, CreateRemoveEdge) {
mgp_edge_type{"EDGE"}, &memory)};
CheckEdgeCountBetween(from, to, 1);
ASSERT_NE(edge, nullptr);
EXPECT_SUCCESS(mgp_graph_remove_edge(&graph, edge.get()));
EXPECT_SUCCESS(mgp_graph_delete_edge(&graph, edge.get()));
CheckEdgeCountBetween(from, to, 0);
}
TEST_F(MgpGraphTest, CreateRemoveEdgeWithImmutableGraph) {
TEST_F(MgpGraphTest, CreateDeleteEdgeWithImmutableGraph) {
storage::Gid from_id;
storage::Gid to_id;
{
@ -431,9 +444,9 @@ TEST_F(MgpGraphTest, CreateRemoveEdgeWithImmutableGraph) {
EXPECT_MGP_NO_ERROR(mgp_edges_iterator *, mgp_vertex_iter_out_edges, from.get(), &memory)};
auto *edge_from_it = EXPECT_MGP_NO_ERROR(mgp_edge *, mgp_edges_iterator_get, edges_it.get());
ASSERT_NE(edge_from_it, nullptr);
EXPECT_EQ(mgp_graph_remove_edge(&graph, edge_from_it), MGP_ERROR_IMMUTABLE_OBJECT);
EXPECT_EQ(mgp_graph_delete_edge(&graph, edge_from_it), MGP_ERROR_IMMUTABLE_OBJECT);
MgpEdgePtr edge_copy_of_immutable{EXPECT_MGP_NO_ERROR(mgp_edge *, mgp_edge_copy, edge_from_it, &memory)};
EXPECT_EQ(mgp_graph_remove_edge(&graph, edge_copy_of_immutable.get()), MGP_ERROR_IMMUTABLE_OBJECT);
EXPECT_EQ(mgp_graph_delete_edge(&graph, edge_copy_of_immutable.get()), MGP_ERROR_IMMUTABLE_OBJECT);
CheckEdgeCountBetween(from, to, 1);
}