From e33fa5f9c076acbb45d5dc2d4adf46b27deca264 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= Date: Fri, 1 Oct 2021 19:33:46 +0200 Subject: [PATCH] Add example write procedure (#244) --- include/mg_procedure.h | 3 +- query_modules/example.c | 106 +++++++++++++++++++++++++++++++++++---- query_modules/example.py | 41 ++++++++++++++- 3 files changed, 138 insertions(+), 12 deletions(-) diff --git a/include/mg_procedure.h b/include/mg_procedure.h index a1df895c1..85074b511 100644 --- a/include/mg_procedure.h +++ b/include/mg_procedure.h @@ -720,6 +720,7 @@ enum mgp_error mgp_graph_get_vertex_by_id(struct mgp_graph *g, struct mgp_vertex enum mgp_error mgp_graph_is_mutable(struct mgp_graph *graph, int *result); /// Add a new vertex to the graph. +/// Resulting vertex must be freed using mgp_vertex_destroy. /// Return MGP_ERROR_IMMUTABLE_OBJECT if `graph` is immutable. /// 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); @@ -736,7 +737,7 @@ enum mgp_error mgp_graph_delete_vertex(struct mgp_graph *graph, struct mgp_verte 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. +/// Resulting edge must be freed using mgp_edge_destroy. /// 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 deleted. diff --git a/query_modules/example.c b/query_modules/example.c index 1a4dfb8c0..6770a013b 100644 --- a/query_modules/example.c +++ b/query_modules/example.c @@ -65,18 +65,11 @@ static void procedure(struct mgp_list *args, struct mgp_graph *graph, struct mgp error_free_list: mgp_list_destroy(args_copy); error_something_went_wrong: + // Best effort. If it fails, there is nothing we can do. mgp_result_set_error_msg(result, "Something went wrong!"); - return; } -static void write_procedure(struct mgp_list *args, struct mgp_graph *graph, struct mgp_result *result, - struct mgp_memory *memory) { - // TODO(antaljanosbenjamin): Finish this example -} - -// Each module needs to define mgp_init_module function. -// Here you can register multiple procedures your module supports. -int mgp_init_module(struct mgp_module *module, struct mgp_memory *memory) { +int add_read_procedure(struct mgp_module *module, struct mgp_memory *memory) { struct mgp_proc *proc = NULL; if (mgp_module_add_read_procedure(module, "procedure", procedure, &proc) != MGP_ERROR_NO_ERROR) { return 1; @@ -119,6 +112,101 @@ int mgp_init_module(struct mgp_module *module, struct mgp_memory *memory) { return 0; } +// This example procedure returns one field called `created_vertex` +// which contains the newly created vertex. +// In case of memory errors, this function will report them and finish +// executing. +// +// The procedure can be invoked in openCypher using the following call: +// CALL example.write_procedure("property value") YIELD created_vertex; +static void write_procedure(struct mgp_list *args, struct mgp_graph *graph, struct mgp_result *result, + struct mgp_memory *memory) { + size_t args_size = 0; + if (mgp_list_size(args, &args_size) != MGP_ERROR_NO_ERROR) { + goto error_something_went_wrong; + } + if (args_size != 1) { + mgp_result_set_error_msg(result, "The procedure requires exactly one argument!"); + return; + } + + struct mgp_value *arg = NULL; + if (mgp_list_at(args, 0, &arg) != MGP_ERROR_NO_ERROR) { + goto error_something_went_wrong; + } + + struct mgp_vertex *vertex = NULL; + if (mgp_graph_create_vertex(graph, memory, &vertex) != MGP_ERROR_NO_ERROR) { + goto error_something_went_wrong; + } + + if (mgp_vertex_set_property(vertex, "new_property", arg) != MGP_ERROR_NO_ERROR) { + goto error_destroy_vertex; + } + + struct mgp_value *vertex_value = NULL; + if (mgp_value_make_vertex(vertex, &vertex_value) != MGP_ERROR_NO_ERROR) { + goto error_destroy_vertex; + } + + struct mgp_result_record *record = NULL; + if (mgp_result_new_record(result, &record) != MGP_ERROR_NO_ERROR) { + goto error_destroy_vertex_value; + } + + if (mgp_result_record_insert(record, "created_vertex", vertex_value) != MGP_ERROR_NO_ERROR) { + goto error_destroy_vertex_value; + } + mgp_value_destroy(vertex_value); + + return; + +error_destroy_vertex: + mgp_vertex_destroy(vertex); + goto error_something_went_wrong; +error_destroy_vertex_value: + mgp_value_destroy(vertex_value); +error_something_went_wrong: + // Best effort. If it fails, there is nothing we can do. + mgp_result_set_error_msg(result, "Something went wrong!"); +} + +int add_write_procedure(struct mgp_module *module, struct mgp_memory *memory) { + struct mgp_proc *proc = NULL; + if (mgp_module_add_write_procedure(module, "write_procedure", write_procedure, &proc) != MGP_ERROR_NO_ERROR) { + return 1; + } + struct mgp_type *string_type = NULL; + if (mgp_type_string(&string_type) != MGP_ERROR_NO_ERROR) { + return 1; + } + + if (mgp_proc_add_arg(proc, "required_arg", string_type) != MGP_ERROR_NO_ERROR) { + return 1; + } + + struct mgp_type *node_type = NULL; + if (mgp_type_node(&node_type) != MGP_ERROR_NO_ERROR) { + return 1; + } + if (mgp_proc_add_result(proc, "created_vertex", node_type) != MGP_ERROR_NO_ERROR) { + return 1; + } + return 0; +} + +// Each module needs to define mgp_init_module function. +// Here you can register multiple procedures your module supports. +int mgp_init_module(struct mgp_module *module, struct mgp_memory *memory) { + if (add_read_procedure(module, memory) != 0) { + return -1; + } + if (add_write_procedure(module, memory) != 0) { + return -1; + } + return 0; +} + // This is an optional function if you need to release any resources before the // module is unloaded. You will probably need this if you acquired some // resources in mgp_init_module. diff --git a/query_modules/example.py b/query_modules/example.py index 4b9830de8..54fd52982 100644 --- a/query_modules/example.py +++ b/query_modules/example.py @@ -14,7 +14,7 @@ def procedure(context: mgp.ProcCtx, vertex_count=int, avg_degree=mgp.Number, props=mgp.Nullable[mgp.Map]): - ''' + """ This example procedure returns 4 fields. * `args` is a copy of arguments passed to the procedure. @@ -31,7 +31,7 @@ def procedure(context: mgp.ProcCtx, MATCH (n) CALL example.procedure(n, 1) YIELD * RETURN *; Naturally, you may pass in different arguments or yield different fields. - ''' + """ # Create a properties map if we received an Edge, Vertex, or Path instance. props = None if isinstance(required_arg, (mgp.Edge, mgp.Vertex)): @@ -53,3 +53,40 @@ def procedure(context: mgp.ProcCtx, # Multiple rows can be produced by returning an iterable of mgp.Record. return mgp.Record(args=args_copy, vertex_count=vertex_count, avg_degree=avg_degree, props=props) + + +@mgp.write_proc +def write_procedure(context: mgp.ProcCtx, property_name: str, property_value: mgp.Nullable[mgp.Any]) -> mgp.Record(created_vertex=mgp.Vertex): + """ + This example procedure creates a new vertex with the specified property + and connects it to all existing vertex which has the same property with + the same name. It returns one field called `created_vertex` which + contains the newly created vertex. + + Any errors can be reported by raising an Exception. + + The procedure can be invoked in openCypher using the following calls: + - CALL example.write_procedure("property_name", "property_value") + YIELD created_vertex; + - MATCH (n) WHERE n.my_property IS NOT NULL + WITH n.my_property as property_value + CALL example.write_procedure("my_property", property_value) + YIELD created_vertex; + + Naturally, you may pass in different arguments. + """ + # Collect all the vertices that has the required property with the same + # value + vertices_to_connect = [] + for v in context.graph.vertices: + if v.properties[property_name] == property_value: + vertices_to_connect.append(v) + # Create the new vertex and set its property + vertex = context.graph.create_vertex() + vertex.properties.set(property_name, property_value) + # Connect the new vertex to the other vertices + for v in vertices_to_connect: + print("ALMA") + context.graph.create_edge(vertex, v, mgp.EdgeType("HAS_SAME_VALUE")) + + return mgp.Record(created_vertex=vertex)