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:
parent
482798295e
commit
be9ed7e879
@ -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;
|
||||
|
702
include/mgp.py
702
include/mgp.py
File diff suppressed because it is too large
Load Diff
@ -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.
|
||||
|
@ -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:
|
||||
|
@ -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
@ -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 "")
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
9
tests/e2e/write_procedures/CMakeLists.txt
Normal file
9
tests/e2e/write_procedures/CMakeLists.txt
Normal 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)
|
23
tests/e2e/write_procedures/common.py
Normal file
23
tests/e2e/write_procedures/common.py
Normal 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)
|
11
tests/e2e/write_procedures/conftest.py
Normal file
11
tests/e2e/write_procedures/conftest.py
Normal 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")
|
2
tests/e2e/write_procedures/procedures/CMakeLists.txt
Normal file
2
tests/e2e/write_procedures/procedures/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
||||
copy_write_procedures_e2e_python_files(write.py)
|
||||
copy_write_procedures_e2e_python_files(read.py)
|
12
tests/e2e/write_procedures/procedures/read.py
Normal file
12
tests/e2e/write_procedures/procedures/read.py
Normal 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())
|
74
tests/e2e/write_procedures/procedures/write.py
Normal file
74
tests/e2e/write_procedures/procedures/write.py
Normal 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())
|
182
tests/e2e/write_procedures/simple_write.py
Normal file
182
tests/e2e/write_procedures/simple_write.py
Normal 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"]))
|
14
tests/e2e/write_procedures/workloads.yaml
Normal file
14
tests/e2e/write_procedures/workloads.yaml
Normal 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
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user