From 2afc1b99f669da8330df7f8fad8392d2597e6807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= Date: Thu, 9 Sep 2021 10:44:47 +0200 Subject: [PATCH] New MGP API (#219) --- .clang-tidy | 2 +- include/mg_procedure.h | 582 +++--- query_modules/example.c | 104 +- src/integrations/kafka/consumer.cpp | 8 +- src/query/context.hpp | 2 +- src/query/db_accessor.hpp | 17 +- src/query/plan/operator.cpp | 2 +- src/query/procedure/cypher_types.hpp | 37 +- src/query/procedure/mg_procedure_helpers.hpp | 37 + src/query/procedure/mg_procedure_impl.cpp | 1843 +++++++++-------- src/query/procedure/mg_procedure_impl.hpp | 9 +- src/query/procedure/module.cpp | 119 +- src/query/procedure/module.hpp | 2 +- src/query/procedure/py_module.cpp | 458 ++-- src/storage/v2/edge_accessor.hpp | 6 +- src/storage/v2/edge_ref.hpp | 4 +- src/storage/v2/vertex_accessor.hpp | 6 +- src/utils/async_timer.cpp | 2 +- src/utils/async_timer.hpp | 2 +- src/utils/on_scope_exit.hpp | 4 + tests/.clang-tidy | 1 - .../memory/procedures/global_memory_limit.c | 28 +- .../procedures/global_memory_limit_proc.c | 56 +- tests/unit/mgp_kafka_c_api.cpp | 16 +- tests/unit/mgp_trans_c_api.cpp | 8 +- tests/unit/query_procedure_mgp_module.cpp | 89 +- tests/unit/query_procedure_mgp_type.cpp | 330 ++- tests/unit/query_procedure_py_module.cpp | 154 +- tests/unit/test_utils.hpp | 17 + 29 files changed, 2347 insertions(+), 1598 deletions(-) create mode 100644 src/query/procedure/mg_procedure_helpers.hpp diff --git a/.clang-tidy b/.clang-tidy index 46103a7a9..a60de817b 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,5 +1,6 @@ --- Checks: '*, + -altera-struct-pack-align, -android-*, -cert-err58-cpp, -cppcoreguidelines-avoid-c-arrays, @@ -43,7 +44,6 @@ Checks: '*, -llvmlibc-implementation-in-namespace, -llvmlibc-restrict-system-libc-headers, -misc-non-private-member-variables-in-classes, - -misc-unused-parameters, -modernize-avoid-c-arrays, -modernize-concat-nested-namespaces, -modernize-pass-by-value, diff --git a/include/mg_procedure.h b/include/mg_procedure.h index db99408c9..93b9b865f 100644 --- a/include/mg_procedure.h +++ b/include/mg_procedure.h @@ -7,9 +7,34 @@ extern "C" { #endif +#if __cplusplus >= 201703L +#define MGP_NODISCARD [[nodiscard]] +#else +#define MGP_NODISCARD +#endif + #include #include +/// @name Error Codes +/// +///@{ + +/// All functions return an error code that can be used to figure out whether the API call was successful or not. In +/// case of failure, the specific error code can be used to identify the reason of the failure. +enum MGP_NODISCARD mgp_error { + MGP_ERROR_NO_ERROR = 0, + MGP_ERROR_UNKNOWN_ERROR, + MGP_ERROR_UNABLE_TO_ALLOCATE, + MGP_ERROR_INSUFFICIENT_BUFFER, + MGP_ERROR_OUT_OF_RANGE, + MGP_ERROR_LOGIC_ERROR, + MGP_ERROR_NON_EXISTENT_OBJECT, + MGP_ERROR_INVALID_ARGUMENT, + MGP_ERROR_KEY_ALREADY_EXISTS, +}; +///@} + /// @name Memory Allocation /// /// These should be preferred compared to plain malloc calls as Memgraph's @@ -30,17 +55,17 @@ struct mgp_memory; /// Allocate a block of memory with given size in bytes. /// Unlike malloc, this function is not thread-safe. /// `size_in_bytes` must be greater than 0. -/// The returned pointer must be freed with mgp_free. -/// NULL is returned if unable to serve the requested allocation. -void *mgp_alloc(struct mgp_memory *memory, size_t size_in_bytes); +/// The resulting pointer must be freed with mgp_free. +/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to serve the requested allocation. +enum mgp_error mgp_alloc(struct mgp_memory *memory, size_t size_in_bytes, void **result); /// Allocate an aligned block of memory with given size in bytes. /// Unlike malloc and aligned_alloc, this function is not thread-safe. /// `size_in_bytes` must be greater than 0. /// `alignment` must be a power of 2 value. -/// The returned pointer must be freed with mgp_free. -/// NULL is returned if unable to serve the requested allocation. -void *mgp_aligned_alloc(struct mgp_memory *memory, size_t size_in_bytes, size_t alignment); +/// The resulting pointer must be freed with mgp_free. +/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to serve the requested allocation. +enum mgp_error mgp_aligned_alloc(struct mgp_memory *memory, size_t size_in_bytes, size_t alignment, void **result); /// Deallocate an allocation from mgp_alloc or mgp_aligned_alloc. /// Unlike free, this function is not thread-safe. @@ -52,16 +77,16 @@ void mgp_free(struct mgp_memory *memory, void *ptr); /// Allocate a global block of memory with given size in bytes. /// This function can be used to allocate global memory that persists /// beyond a single invocation of mgp_main. -/// The returned pointer must be freed with mgp_global_free. -/// NULL is returned if unable to serve the requested allocation. -void *mgp_global_alloc(size_t size_in_bytes); +/// The resulting pointer must be freed with mgp_global_free. +/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to serve the requested allocation. +enum mgp_error mgp_global_alloc(size_t size_in_bytes, void **result); /// Allocate an aligned global block of memory with given size in bytes. /// This function can be used to allocate global memory that persists /// beyond a single invocation of mgp_main. -/// The returned pointer must be freed with mgp_global_free. -/// NULL is returned if unable to serve the requested allocation. -void *mgp_global_aligned_alloc(size_t size_in_bytes, size_t alignment); +/// The resulting pointer must be freed with mgp_global_free. +/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to serve the requested allocation. +enum mgp_error mgp_global_aligned_alloc(size_t size_in_bytes, size_t alignment, void **result); /// Deallocate an allocation from mgp_global_alloc or mgp_global_aligned_alloc. /// If `ptr` is NULL, this function does nothing. @@ -117,147 +142,167 @@ void mgp_value_destroy(struct mgp_value *val); /// Construct a value representing `null` in openCypher. /// You need to free the instance through mgp_value_destroy. -/// NULL is returned if unable to allocate a mgp_value. -struct mgp_value *mgp_value_make_null(struct mgp_memory *memory); +/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. +enum mgp_error mgp_value_make_null(struct mgp_memory *memory, struct mgp_value **result); /// Construct a boolean value. /// Non-zero values represent `true`, while zero represents `false`. /// You need to free the instance through mgp_value_destroy. -/// NULL is returned if unable to allocate a mgp_value. -struct mgp_value *mgp_value_make_bool(int val, struct mgp_memory *memory); +/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. +enum mgp_error mgp_value_make_bool(int val, struct mgp_memory *memory, struct mgp_value **result); /// Construct an integer value. /// You need to free the instance through mgp_value_destroy. -/// NULL is returned if unable to allocate a mgp_value. -struct mgp_value *mgp_value_make_int(int64_t val, struct mgp_memory *memory); +/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. +enum mgp_error mgp_value_make_int(int64_t val, struct mgp_memory *memory, struct mgp_value **result); /// Construct a double floating point value. /// You need to free the instance through mgp_value_destroy. -/// NULL is returned if unable to allocate a mgp_value. -struct mgp_value *mgp_value_make_double(double val, struct mgp_memory *memory); +/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. +enum mgp_error mgp_value_make_double(double val, struct mgp_memory *memory, struct mgp_value **result); /// Construct a character string value from a NULL terminated string. /// You need to free the instance through mgp_value_destroy. -/// NULL is returned if unable to allocate a mgp_value. -struct mgp_value *mgp_value_make_string(const char *val, struct mgp_memory *memory); +/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. +enum mgp_error mgp_value_make_string(const char *val, struct mgp_memory *memory, struct mgp_value **result); /// Create a mgp_value storing a mgp_list. /// You need to free the instance through mgp_value_destroy. The ownership of /// the list is given to the created mgp_value and destroying the mgp_value will /// destroy the mgp_list. Therefore, if a mgp_value is successfully created /// you must not call mgp_list_destroy on the given list. -/// NULL is returned if unable to allocate a mgp_value. -struct mgp_value *mgp_value_make_list(struct mgp_list *val); +/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. +enum mgp_error mgp_value_make_list(struct mgp_list *val, struct mgp_value **result); /// Create a mgp_value storing a mgp_map. /// You need to free the instance through mgp_value_destroy. The ownership of /// the map is given to the created mgp_value and destroying the mgp_value will /// destroy the mgp_map. Therefore, if a mgp_value is successfully created /// you must not call mgp_map_destroy on the given map. -/// NULL is returned if unable to allocate a mgp_value. -struct mgp_value *mgp_value_make_map(struct mgp_map *val); +/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. +enum mgp_error mgp_value_make_map(struct mgp_map *val, struct mgp_value **result); /// Create a mgp_value storing a mgp_vertex. /// You need to free the instance through mgp_value_destroy. The ownership of /// the vertex is given to the created mgp_value and destroying the mgp_value /// will destroy the mgp_vertex. Therefore, if a mgp_value is successfully /// created you must not call mgp_vertex_destroy on the given vertex. -/// NULL is returned if unable to allocate a mgp_value. -struct mgp_value *mgp_value_make_vertex(struct mgp_vertex *val); +/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. +enum mgp_error mgp_value_make_vertex(struct mgp_vertex *val, struct mgp_value **result); /// Create a mgp_value storing a mgp_edge. /// You need to free the instance through mgp_value_destroy. The ownership of /// the edge is given to the created mgp_value and destroying the mgp_value will /// destroy the mgp_edge. Therefore, if a mgp_value is successfully created you /// must not call mgp_edge_destroy on the given edge. -/// NULL is returned if unable to allocate a mgp_value. -struct mgp_value *mgp_value_make_edge(struct mgp_edge *val); +/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. +enum mgp_error mgp_value_make_edge(struct mgp_edge *val, struct mgp_value **result); /// Create a mgp_value storing a mgp_path. /// You need to free the instance through mgp_value_destroy. The ownership of /// the path is given to the created mgp_value and destroying the mgp_value will /// destroy the mgp_path. Therefore, if a mgp_value is successfully created you /// must not call mgp_path_destroy on the given path. -/// NULL is returned if unable to allocate a mgp_value. -struct mgp_value *mgp_value_make_path(struct mgp_path *val); +/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. +enum mgp_error mgp_value_make_path(struct mgp_path *val, struct mgp_value **result); -/// Return the type of the value contained in mgp_value. -enum mgp_value_type mgp_value_get_type(const struct mgp_value *val); +/// Get the type of the value contained in mgp_value. +/// Current implementation always returns without errors. +enum mgp_error mgp_value_get_type(const struct mgp_value *val, enum mgp_value_type *result); -/// Return non-zero if the given mgp_value represents `null`. -int mgp_value_is_null(const struct mgp_value *val); +/// Result is non-zero if the given mgp_value represents `null`. +/// Current implementation always returns without errors. +enum mgp_error mgp_value_is_null(const struct mgp_value *val, int *result); -/// Return non-zero if the given mgp_value stores a boolean. -int mgp_value_is_bool(const struct mgp_value *val); +/// Result is non-zero if the given mgp_value stores a boolean. +/// Current implementation always returns without errors. +enum mgp_error mgp_value_is_bool(const struct mgp_value *val, int *result); -/// Return non-zero if the given mgp_value stores an integer. -int mgp_value_is_int(const struct mgp_value *val); +/// Result is non-zero if the given mgp_value stores an integer. +/// Current implementation always returns without errors. +enum mgp_error mgp_value_is_int(const struct mgp_value *val, int *result); -/// Return non-zero if the given mgp_value stores a double floating-point. -int mgp_value_is_double(const struct mgp_value *val); +/// Result is non-zero if the given mgp_value stores a double floating-point. +/// Current implementation always returns without errors. +enum mgp_error mgp_value_is_double(const struct mgp_value *val, int *result); -/// Return non-zero if the given mgp_value stores a character string. -int mgp_value_is_string(const struct mgp_value *val); +/// Result is non-zero if the given mgp_value stores a character string. +/// Current implementation always returns without errors. +enum mgp_error mgp_value_is_string(const struct mgp_value *val, int *result); -/// Return non-zero if the given mgp_value stores a list of values. -int mgp_value_is_list(const struct mgp_value *val); +/// Result is non-zero if the given mgp_value stores a list of values. +/// Current implementation always returns without errors. +enum mgp_error mgp_value_is_list(const struct mgp_value *val, int *result); -/// Return non-zero if the given mgp_value stores a map of values. -int mgp_value_is_map(const struct mgp_value *val); +/// Result is non-zero if the given mgp_value stores a map of values. +/// Current implementation always returns without errors. +enum mgp_error mgp_value_is_map(const struct mgp_value *val, int *result); -/// Return non-zero if the given mgp_value stores a vertex. -int mgp_value_is_vertex(const struct mgp_value *val); +/// Result is non-zero if the given mgp_value stores a vertex. +/// Current implementation always returns without errors. +enum mgp_error mgp_value_is_vertex(const struct mgp_value *val, int *result); -/// Return non-zero if the given mgp_value stores an edge. -int mgp_value_is_edge(const struct mgp_value *val); +/// Result is non-zero if the given mgp_value stores an edge. +/// Current implementation always returns without errors. +enum mgp_error mgp_value_is_edge(const struct mgp_value *val, int *result); -/// Return non-zero if the given mgp_value stores a path. -int mgp_value_is_path(const struct mgp_value *val); +/// Result is non-zero if the given mgp_value stores a path. +/// Current implementation always returns without errors. +enum mgp_error mgp_value_is_path(const struct mgp_value *val, int *result); -/// Return the contained boolean value. +/// Get the contained boolean value. /// Non-zero values represent `true`, while zero represents `false`. -/// The result is undefined if mgp_value does not contain the expected type. -int mgp_value_get_bool(const struct mgp_value *val); +/// Result is undefined if mgp_value does not contain the expected type. +/// Current implementation always returns without errors. +enum mgp_error mgp_value_get_bool(const struct mgp_value *val, int *result); -/// Return the contained integer. -/// The result is undefined if mgp_value does not contain the expected type. -int64_t mgp_value_get_int(const struct mgp_value *val); +/// Get the contained integer. +/// Result is undefined if mgp_value does not contain the expected type. +/// Current implementation always returns without errors. +enum mgp_error mgp_value_get_int(const struct mgp_value *val, int64_t *result); -/// Return the contained double floating-point. -/// The result is undefined if mgp_value does not contain the expected type. -double mgp_value_get_double(const struct mgp_value *val); +/// Get the contained double floating-point. +/// Result is undefined if mgp_value does not contain the expected type. +/// Current implementation always returns without errors. +enum mgp_error mgp_value_get_double(const struct mgp_value *val, double *result); -/// Return the contained character string. -/// The result is undefined if mgp_value does not contain the expected type. -const char *mgp_value_get_string(const struct mgp_value *val); +/// Get the contained character string. +/// Result is undefined if mgp_value does not contain the expected type. +/// Current implementation always returns without errors. +enum mgp_error mgp_value_get_string(const struct mgp_value *val, const char **result); -/// Return the contained list of values. -/// The result is undefined if mgp_value does not contain the expected type. -const struct mgp_list *mgp_value_get_list(const struct mgp_value *val); +/// Get the contained list of values. +/// Result is undefined if mgp_value does not contain the expected type. +/// Current implementation always returns without errors. +enum mgp_error mgp_value_get_list(const struct mgp_value *val, const struct mgp_list **result); /// Return the contained map of values. -/// The result is undefined if mgp_value does not contain the expected type. -const struct mgp_map *mgp_value_get_map(const struct mgp_value *val); +/// Result is undefined if mgp_value does not contain the expected type. +/// Current implementation always returns without errors. +enum mgp_error mgp_value_get_map(const struct mgp_value *val, const struct mgp_map **result); -/// Return the contained vertex. -/// The result is undefined if mgp_value does not contain the expected type. -const struct mgp_vertex *mgp_value_get_vertex(const struct mgp_value *val); +/// Get the contained vertex. +/// Result is undefined if mgp_value does not contain the expected type. +/// Current implementation always returns without errors. +enum mgp_error mgp_value_get_vertex(const struct mgp_value *val, const struct mgp_vertex **result); -/// Return the contained edge. -/// The result is undefined if mgp_value does not contain the expected type. -const struct mgp_edge *mgp_value_get_edge(const struct mgp_value *val); +/// Get the contained edge. +/// Result is undefined if mgp_value does not contain the expected type. +/// Current implementation always returns without errors. +enum mgp_error mgp_value_get_edge(const struct mgp_value *val, const struct mgp_edge **result); -/// Return the contained path. -/// The result is undefined if mgp_value does not contain the expected type. -const struct mgp_path *mgp_value_get_path(const struct mgp_value *val); +/// Get the contained path. +/// Result is undefined if mgp_value does not contain the expected type. +/// Current implementation always returns without errors. +enum mgp_error mgp_value_get_path(const struct mgp_value *val, const struct mgp_path **result); /// Create an empty list with given capacity. /// You need to free the created instance with mgp_list_destroy. /// The created list will have allocated enough memory for `capacity` elements /// of mgp_value, but it will not contain any elements. Therefore, /// mgp_list_size will return 0. -/// NULL is returned if unable to allocate a new list. -struct mgp_list *mgp_list_make_empty(size_t capacity, struct mgp_memory *memory); +/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_list. +enum mgp_error mgp_list_make_empty(size_t capacity, struct mgp_memory *memory, struct mgp_list **result); /// Free the memory used by the given mgp_list and contained elements. void mgp_list_destroy(struct mgp_list *list); @@ -266,9 +311,9 @@ void mgp_list_destroy(struct mgp_list *list); /// The list copies the given value and therefore does not take ownership of the /// original value. You still need to call mgp_value_destroy to free the /// original value. -/// Return non-zero on success, or 0 if there's no capacity or memory to append -/// the mgp_value to mgp_list. -int mgp_list_append(struct mgp_list *list, const struct mgp_value *val); +/// 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(struct mgp_list *list, const struct mgp_value *val); /// Append a copy of mgp_value to mgp_list increasing capacity if needed. /// The list copies the given value and therefore does not take ownership of the @@ -276,25 +321,27 @@ int mgp_list_append(struct mgp_list *list, const 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 non-zero on success, or 0 if there's no memory to append the -/// mgp_value to mgp_list. -int mgp_list_append_extend(struct mgp_list *list, const struct mgp_value *val); +/// 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, const struct mgp_value *val); -/// Return the number of elements stored in mgp_list. -size_t mgp_list_size(const struct mgp_list *list); +/// Get the number of elements stored in mgp_list. +/// Current implementation always returns without errors. +enum mgp_error mgp_list_size(const struct mgp_list *list, size_t *result); -/// Return the total number of elements for which there's already allocated +/// Get the total number of elements for which there's already allocated /// memory in mgp_list. -size_t mgp_list_capacity(const struct mgp_list *list); +/// Current implementation always returns without errors. +enum mgp_error mgp_list_capacity(const struct mgp_list *list, size_t *result); -/// Return the element in mgp_list at given position. -/// NULL is returned if the index is not within mgp_list_size. -const struct mgp_value *mgp_list_at(const struct mgp_list *list, size_t index); +/// Get the element in mgp_list at given position. +/// MGP_ERROR_OUT_OF_RANGE is returned if the index is not within mgp_list_size. +enum mgp_error mgp_list_at(const struct mgp_list *list, size_t index, const struct mgp_value **result); /// Create an empty map of character strings to mgp_value instances. /// You need to free the created instance with mgp_map_destroy. -/// NULL is returned if unable to allocate a new map. -struct mgp_map *mgp_map_make_empty(struct mgp_memory *memory); +/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_map. +enum mgp_error mgp_map_make_empty(struct mgp_memory *memory, struct mgp_map **result); /// Free the memory used by the given mgp_map and contained items. void mgp_map_destroy(struct mgp_map *map); @@ -304,34 +351,36 @@ void mgp_map_destroy(struct mgp_map *map); /// In case of insertion, both the string and the value are copied into the map. /// Therefore, the map does not take ownership of the original key nor value, so /// you still need to free their memory explicitly. -/// Return non-zero on success, or 0 if there's no memory to insert a new -/// mapping or a previous mapping already exists. -int mgp_map_insert(struct mgp_map *map, const char *key, const struct mgp_value *value); +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate for insertion. +/// Return MGP_ERROR_KEY_ALREADY_EXISTS if a previous mapping already exists. +enum mgp_error mgp_map_insert(struct mgp_map *map, const char *key, const struct mgp_value *value); -/// Return the number of items stored in mgp_map. -size_t mgp_map_size(const struct mgp_map *map); +/// Get the number of items stored in mgp_map. +/// Current implementation always returns without errors. +enum mgp_error mgp_map_size(const struct mgp_map *map, size_t *result); -/// Return the mapped mgp_value to the given character string. -/// NULL is returned if no mapping exists. -const struct mgp_value *mgp_map_at(const struct mgp_map *map, const char *key); +/// Get the mapped mgp_value to the given character string. +/// Result is NULL if no mapping exists. +enum mgp_error mgp_map_at(const struct mgp_map *map, const char *key, const struct mgp_value **result); /// An item in the mgp_map. struct mgp_map_item; /// Get the key of the mapped item. -const char *mgp_map_item_key(const struct mgp_map_item *item); +enum mgp_error mgp_map_item_key(const struct mgp_map_item *item, const char **result); /// Get the value of the mapped item. -const struct mgp_value *mgp_map_item_value(const struct mgp_map_item *item); +enum mgp_error mgp_map_item_value(const struct mgp_map_item *item, const struct mgp_value **result); /// An iterator over the items in mgp_map. struct mgp_map_items_iterator; /// Start iterating over items contained in the given map. -/// The returned mgp_map_items_iterator needs to be deallocated with +/// The resulting mgp_map_items_iterator needs to be deallocated with /// mgp_map_items_iterator_destroy. -/// NULL is returned if unable to allocate a new iterator. -struct mgp_map_items_iterator *mgp_map_iter_items(const struct mgp_map *map, struct mgp_memory *memory); +/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_map_items_iterator. +enum mgp_error mgp_map_iter_items(const struct mgp_map *map, struct mgp_memory *memory, + struct mgp_map_items_iterator **result); /// Deallocate memory used by mgp_map_items_iterator. void mgp_map_items_iterator_destroy(struct mgp_map_items_iterator *it); @@ -343,24 +392,25 @@ void mgp_map_items_iterator_destroy(struct mgp_map_items_iterator *it); /// throughout the lifetime of a map. Therefore, you can store the key as well /// as the value before, and use them after invoking /// mgp_map_items_iterator_next. -/// NULL is returned if the end of the iteration has been reached. -const struct mgp_map_item *mgp_map_items_iterator_get(const struct mgp_map_items_iterator *it); +/// Result is NULL if the end of the iteration has been reached. +enum mgp_error mgp_map_items_iterator_get(const struct mgp_map_items_iterator *it, const struct mgp_map_item **result); /// Advance the iterator to the next item stored in map and return it. /// The previous pointer obtained through mgp_map_items_iterator_get will /// be invalidated, but the pointers to key and value will remain valid. -/// NULL is returned if the end of the iteration has been reached. -const struct mgp_map_item *mgp_map_items_iterator_next(struct mgp_map_items_iterator *it); +/// Result is NULL if the end of the iteration has been reached. +enum mgp_error mgp_map_items_iterator_next(struct mgp_map_items_iterator *it, const struct mgp_map_item **result); /// Create a path with the copy of the given starting vertex. /// You need to free the created instance with mgp_path_destroy. -/// NULL is returned if unable to allocate a path. -struct mgp_path *mgp_path_make_with_start(const struct mgp_vertex *vertex, struct mgp_memory *memory); +/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_path. +enum mgp_error mgp_path_make_with_start(const struct mgp_vertex *vertex, struct mgp_memory *memory, + struct mgp_path **result); /// Copy a mgp_path. /// Returned pointer must be freed with mgp_path_destroy. -/// NULL is returned if unable to allocate a mgp_path. -struct mgp_path *mgp_path_copy(const struct mgp_path *path, struct mgp_memory *memory); +/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_path. +enum mgp_error mgp_path_copy(const struct mgp_path *path, struct mgp_memory *memory, struct mgp_path **result); /// Free the memory used by the given mgp_path and contained vertices and edges. void mgp_path_destroy(struct mgp_path *path); @@ -371,26 +421,26 @@ void mgp_path_destroy(struct mgp_path *path); /// explicitly. /// The last vertex on the path will become the other endpoint of the given /// edge, as continued from the current last vertex. -/// Return non-zero on success, or 0 if the current last vertex in the path is -/// not part of the given edge. 0 is also returned if unable to allocate memory -/// for path extension. -int mgp_path_expand(struct mgp_path *path, const struct mgp_edge *edge); +/// Return MGP_ERROR_LOGIC_ERROR if the current last vertex in the path is not part of the given edge. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for path extension. +enum mgp_error mgp_path_expand(struct mgp_path *path, const struct mgp_edge *edge); -/// Return the number of edges in a mgp_path. -size_t mgp_path_size(const struct mgp_path *path); +/// Get the number of edges in a mgp_path. +/// Current implementation always returns without errors. +enum mgp_error mgp_path_size(const struct mgp_path *path, size_t *result); -/// Return the vertex from a path at given index. +/// Get the vertex from a path at given index. /// The valid index range is [0, mgp_path_size]. -/// NULL is returned if index is out of range. -const struct mgp_vertex *mgp_path_vertex_at(const struct mgp_path *path, size_t index); +/// MGP_ERROR_OUT_OF_RANGE is returned if index is out of range. +enum mgp_error mgp_path_vertex_at(const struct mgp_path *path, size_t index, const struct mgp_vertex **result); -/// Return the edge from a path at given index. +/// Get the edge from a path at given index. /// The valid index range is [0, mgp_path_size - 1]. -/// NULL is returned if index is out of range. -const struct mgp_edge *mgp_path_edge_at(const struct mgp_path *path, size_t index); +/// MGP_ERROR_OUT_OF_RANGE is returned if index is out of range. +enum mgp_error mgp_path_edge_at(const struct mgp_path *path, size_t index, const struct mgp_edge **result); -/// Return non-zero if given paths are equal, otherwise 0. -int mgp_path_equal(const struct mgp_path *p1, const struct mgp_path *p2); +/// Result is non-zero if given paths are equal, otherwise 0. +enum mgp_error mgp_path_equal(const struct mgp_path *p1, const struct mgp_path *p2, int *result); ///@} @@ -405,20 +455,20 @@ struct mgp_result; struct mgp_result_record; /// Set the error as the result of the procedure. -/// If there's no memory for copying the error message, 0 is returned. -int mgp_result_set_error_msg(struct mgp_result *res, const char *error_msg); +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE ff there's no memory for copying the error message. +enum mgp_error mgp_result_set_error_msg(struct mgp_result *res, const char *error_msg); /// Create a new record for results. -/// The previously returned pointer to mgp_result_record is no longer valid, and -/// you must not use it. -/// Return NULL if unable to allocate a mgp_result_record. -struct mgp_result_record *mgp_result_new_record(struct mgp_result *res); +/// The previously obtained mgp_result_record pointer is no longer valid, and you must not use it. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_result_record. +enum mgp_error mgp_result_new_record(struct mgp_result *res, struct mgp_result_record **result); /// Assign a value to a field in the given record. -/// Return 0 if there's no memory to copy the mgp_value to mgp_result_record or -/// if the combination of `field_name` and `val` does not satisfy the -/// procedure's result signature. -int mgp_result_record_insert(struct mgp_result_record *record, const char *field_name, const struct mgp_value *val); +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory to copy the mgp_value to mgp_result_record. +/// Return MGP_ERROR_OUT_OF_RANGE if there is no field named `field_name`. +/// Return MGP_ERROR_LOGIC_ERROR `val` does not satisfy the type of the field name `field_name`. +enum mgp_error mgp_result_record_insert(struct mgp_result_record *record, const char *field_name, + const struct mgp_value *val); ///@} /// @name Graph Constructs @@ -453,14 +503,15 @@ struct mgp_property { /// Get the current property pointed to by the iterator. /// When the mgp_properties_iterator_next is invoked, the previous /// mgp_property is invalidated and its value must not be used. -/// NULL is returned if the end of the iteration has been reached. -const struct mgp_property *mgp_properties_iterator_get(const struct mgp_properties_iterator *it); +/// Result is NULL if the end of the iteration has been reached. +enum mgp_error mgp_properties_iterator_get(const struct mgp_properties_iterator *it, + const struct mgp_property **result); /// Advance the iterator to the next property and return it. /// The previous mgp_property obtained through mgp_properties_iterator_get /// will be invalidated, and you must not use its value. -/// NULL is returned if the end of the iteration has been reached. -const struct mgp_property *mgp_properties_iterator_next(struct mgp_properties_iterator *it); +/// Result is NULL if the end of the iteration has been reached. +enum mgp_error mgp_properties_iterator_next(struct mgp_properties_iterator *it, const struct mgp_property **result); /// Iterator over edges of a vertex. struct mgp_edges_iterator; @@ -476,67 +527,70 @@ 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. -struct mgp_vertex_id mgp_vertex_get_id(const struct mgp_vertex *v); +enum mgp_error mgp_vertex_get_id(const struct mgp_vertex *v, struct mgp_vertex_id *result); /// Copy a mgp_vertex. -/// Returned pointer must be freed with mgp_vertex_destroy. -/// NULL is returned if unable to allocate a mgp_vertex. -struct mgp_vertex *mgp_vertex_copy(const struct mgp_vertex *v, struct mgp_memory *memory); +/// Resulting pointer must be freed with mgp_vertex_destroy. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_vertex. +enum mgp_error mgp_vertex_copy(const struct mgp_vertex *v, struct mgp_memory *memory, struct mgp_vertex **result); /// Free the memory used by a mgp_vertex. void mgp_vertex_destroy(struct mgp_vertex *v); -/// Return non-zero if given vertices are equal, otherwise 0. -int mgp_vertex_equal(const struct mgp_vertex *v1, const struct mgp_vertex *v2); +/// Result is non-zero if given vertices are equal, otherwise 0. +enum mgp_error mgp_vertex_equal(const struct mgp_vertex *v1, const struct mgp_vertex *v2, int *result); -/// Return the number of labels a given vertex has. -size_t mgp_vertex_labels_count(const struct mgp_vertex *v); +/// Get the number of labels a given vertex has. +enum mgp_error mgp_vertex_labels_count(const struct mgp_vertex *v, size_t *result); -/// Return mgp_label in mgp_vertex at given index. -/// If the index is out of bounds, mgp_label.name is set to NULL. -struct mgp_label mgp_vertex_label_at(const struct mgp_vertex *v, size_t index); +/// Get mgp_label in mgp_vertex at given index. +/// Return MGP_ERROR_OUT_OF_RANGE if the index is out of range. +enum mgp_error mgp_vertex_label_at(const struct mgp_vertex *v, size_t index, struct mgp_label *result); -/// Return non-zero if the given vertex has the given label. -int mgp_vertex_has_label(const struct mgp_vertex *v, struct mgp_label label); +/// Result is non-zero if the given vertex has the given label. +enum mgp_error mgp_vertex_has_label(const struct mgp_vertex *v, struct mgp_label label, int *result); -/// Return non-zero if the given vertex has a label with given name. -int mgp_vertex_has_label_named(const struct mgp_vertex *v, const char *label_name); +/// Result is non-zero if the given vertex has a label with given name. +enum mgp_error mgp_vertex_has_label_named(const struct mgp_vertex *v, const char *label_name, int *result); /// Get a copy of a vertex property mapped to a given name. -/// Returned value must be freed with mgp_value_destroy. -/// NULL is returned if unable to allocate a mgp_value. -struct mgp_value *mgp_vertex_get_property(const struct mgp_vertex *v, const char *property_name, - struct mgp_memory *memory); +/// Resulting value must be freed with mgp_value_destroy. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_value. +enum mgp_error mgp_vertex_get_property(const struct mgp_vertex *v, const char *property_name, struct mgp_memory *memory, + struct mgp_value **result); /// Start iterating over properties stored in the given vertex. -/// The returned mgp_properties_iterator needs to be deallocated with +/// The resulting mgp_properties_iterator needs to be deallocated with /// mgp_properties_iterator_destroy. -/// NULL is returned if unable to allocate a new iterator. -struct mgp_properties_iterator *mgp_vertex_iter_properties(const struct mgp_vertex *v, struct mgp_memory *memory); +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_properties_iterator. +enum mgp_error mgp_vertex_iter_properties(const struct mgp_vertex *v, struct mgp_memory *memory, + struct mgp_properties_iterator **result); /// Start iterating over inbound edges of the given vertex. -/// The returned mgp_edges_iterator needs to be deallocated with +/// The resulting mgp_edges_iterator needs to be deallocated with /// mgp_edges_iterator_destroy. -/// NULL is returned if unable to allocate a new iterator. -struct mgp_edges_iterator *mgp_vertex_iter_in_edges(const struct mgp_vertex *v, struct mgp_memory *memory); +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_edges_iterator. +enum mgp_error mgp_vertex_iter_in_edges(const struct mgp_vertex *v, struct mgp_memory *memory, + struct mgp_edges_iterator **result); /// Start iterating over outbound edges of the given vertex. /// The returned mgp_edges_iterator needs to be deallocated with /// mgp_edges_iterator_destroy. -/// NULL is returned if unable to allocate a new iterator. -struct mgp_edges_iterator *mgp_vertex_iter_out_edges(const struct mgp_vertex *v, struct mgp_memory *memory); +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_edges_iterator. +enum mgp_error mgp_vertex_iter_out_edges(const struct mgp_vertex *v, struct mgp_memory *memory, + struct mgp_edges_iterator **result); /// Get the current edge pointed to by the iterator. /// When the mgp_edges_iterator_next is invoked, the previous /// mgp_edge is invalidated and its value must not be used. -/// NULL is returned if the end of the iteration has been reached. -const struct mgp_edge *mgp_edges_iterator_get(const struct mgp_edges_iterator *it); +/// Result is NULL if the end of the iteration has been reached. +enum mgp_error mgp_edges_iterator_get(const struct mgp_edges_iterator *it, const struct mgp_edge **result); /// Advance the iterator to the next edge and return it. /// The previous mgp_edge obtained through mgp_edges_iterator_get /// will be invalidated, and you must not use its value. -/// NULL is returned if the end of the iteration has been reached. -const struct mgp_edge *mgp_edges_iterator_next(struct mgp_edges_iterator *it); +/// Result is NULL if the end of the iteration has been reached. +enum mgp_error mgp_edges_iterator_next(struct mgp_edges_iterator *it, const struct mgp_edge **result); /// ID of an edge; valid during a single query execution. struct mgp_edge_id { @@ -546,47 +600,51 @@ 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. -struct mgp_edge_id mgp_edge_get_id(const struct mgp_edge *e); +enum mgp_error mgp_edge_get_id(const struct mgp_edge *e, struct mgp_edge_id *result); /// Copy a mgp_edge. -/// Returned pointer must be freed with mgp_edge_destroy. -/// NULL is returned if unable to allocate a mgp_edge. -struct mgp_edge *mgp_edge_copy(const struct mgp_edge *e, struct mgp_memory *memory); +/// Resulting pointer must be freed with mgp_edge_destroy. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_edge. +enum mgp_error mgp_edge_copy(const struct mgp_edge *e, struct mgp_memory *memory, struct mgp_edge **result); /// Free the memory used by a mgp_edge. void mgp_edge_destroy(struct mgp_edge *e); -/// Return non-zero if given edges are equal, otherwise 0. -int mgp_edge_equal(const struct mgp_edge *e1, const struct mgp_edge *e2); +/// Result is non-zero if given edges are equal, otherwise 0. +enum mgp_error mgp_edge_equal(const struct mgp_edge *e1, const struct mgp_edge *e2, int *result); -/// Return the type of the given edge. -struct mgp_edge_type mgp_edge_get_type(const struct mgp_edge *e); +/// Get the type of the given edge. +enum mgp_error mgp_edge_get_type(const struct mgp_edge *e, struct mgp_edge_type *result); -/// Return the source vertex of the given edge. -const struct mgp_vertex *mgp_edge_get_from(const struct mgp_edge *e); +/// Get the source vertex of the given edge. +/// Current implementation always returns without errors. +enum mgp_error mgp_edge_get_from(const struct mgp_edge *e, const struct mgp_vertex **result); -/// Return the destination vertex of the given edge. -const struct mgp_vertex *mgp_edge_get_to(const struct mgp_edge *e); +/// Get the destination vertex of the given edge. +/// Current implementation always returns without errors. +enum mgp_error mgp_edge_get_to(const struct mgp_edge *e, const struct mgp_vertex **result); /// Get a copy of a edge property mapped to a given name. -/// Returned value must be freed with mgp_value_destroy. -/// NULL is returned if unable to allocate a mgp_value. -struct mgp_value *mgp_edge_get_property(const struct mgp_edge *e, const char *property_name, struct mgp_memory *memory); +/// Resulting value must be freed with mgp_value_destroy. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_value. +enum mgp_error mgp_edge_get_property(const struct mgp_edge *e, const char *property_name, struct mgp_memory *memory, + struct mgp_value **result); /// Start iterating over properties stored in the given edge. -/// The returned mgp_properties_iterator needs to be deallocated with +/// Resulting mgp_properties_iterator needs to be deallocated with /// mgp_properties_iterator_destroy. -/// NULL is returned if unable to allocate a new iterator. -struct mgp_properties_iterator *mgp_edge_iter_properties(const struct mgp_edge *e, struct mgp_memory *memory); +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_properties_iterator. +enum mgp_error mgp_edge_iter_properties(const struct mgp_edge *e, struct mgp_memory *memory, + struct mgp_properties_iterator **result); /// State of the graph database. struct mgp_graph; /// Return the vertex corresponding to given ID. -/// The returned vertex must be freed using mgp_vertex_destroy. -/// NULL is returned if unable to allocate the vertex or if ID is not valid. -struct mgp_vertex *mgp_graph_get_vertex_by_id(const struct mgp_graph *g, struct mgp_vertex_id id, - struct mgp_memory *memory); +/// 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. +enum mgp_error mgp_graph_get_vertex_by_id(const struct mgp_graph *g, struct mgp_vertex_id id, struct mgp_memory *memory, + struct mgp_vertex **result); /// Iterator over vertices. struct mgp_vertices_iterator; @@ -595,22 +653,22 @@ struct mgp_vertices_iterator; void mgp_vertices_iterator_destroy(struct mgp_vertices_iterator *it); /// Start iterating over vertices of the given graph. -/// The returned mgp_vertices_iterator needs to be deallocated with -/// mgp_vertices_iterator_destroy. -/// NULL is returned if unable to allocate a new iterator. -struct mgp_vertices_iterator *mgp_graph_iter_vertices(const struct mgp_graph *g, struct mgp_memory *memory); +/// Resulting mgp_vertices_iterator needs to be deallocated with mgp_vertices_iterator_destroy. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_vertices_iterator. +enum mgp_error mgp_graph_iter_vertices(const struct mgp_graph *g, struct mgp_memory *memory, + struct mgp_vertices_iterator **result); /// Get the current vertex pointed to by the iterator. /// When the mgp_vertices_iterator_next is invoked, the previous /// mgp_vertex is invalidated and its value must not be used. -/// NULL is returned if the end of the iteration has been reached. -const struct mgp_vertex *mgp_vertices_iterator_get(const struct mgp_vertices_iterator *it); +/// Result is NULL if the end of the iteration has been reached. +enum mgp_error mgp_vertices_iterator_get(const struct mgp_vertices_iterator *it, const struct mgp_vertex **result); /// Advance the iterator to the next vertex and return it. /// The previous mgp_vertex obtained through mgp_vertices_iterator_get /// will be invalidated, and you must not use its value. -/// NULL is returned if the end of the iteration has been reached. -const struct mgp_vertex *mgp_vertices_iterator_next(struct mgp_vertices_iterator *it); +/// Result is NULL if the end of the iteration has been reached. +enum mgp_error mgp_vertices_iterator_next(struct mgp_vertices_iterator *it, const struct mgp_vertex **result); ///@} /// @name Type System @@ -628,24 +686,30 @@ struct mgp_type; /// Get the type representing any value that isn't `null`. /// /// The ANY type is the parent type of all types. -const struct mgp_type *mgp_type_any(); +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +enum mgp_error mgp_type_any(const struct mgp_type **result); /// Get the type representing boolean values. -const struct mgp_type *mgp_type_bool(); +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +enum mgp_error mgp_type_bool(const struct mgp_type **result); /// Get the type representing character string values. -const struct mgp_type *mgp_type_string(); +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +enum mgp_error mgp_type_string(const struct mgp_type **result); /// Get the type representing integer values. -const struct mgp_type *mgp_type_int(); +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +enum mgp_error mgp_type_int(const struct mgp_type **result); /// Get the type representing floating-point values. -const struct mgp_type *mgp_type_float(); +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +enum mgp_error mgp_type_float(const struct mgp_type **result); /// Get the type representing any number value. /// /// This is the parent type for numeric types, i.e. INTEGER and FLOAT. -const struct mgp_type *mgp_type_number(); +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +enum mgp_error mgp_type_number(const struct mgp_type **result); /// Get the type representing map values. /// @@ -656,32 +720,36 @@ const struct mgp_type *mgp_type_number(); /// /// @sa mgp_type_node /// @sa mgp_type_relationship -const struct mgp_type *mgp_type_map(); +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +enum mgp_error mgp_type_map(const struct mgp_type **result); /// Get the type representing graph node values. /// /// Since a node contains a map of properties, the node itself is also of MAP /// type. -const struct mgp_type *mgp_type_node(); +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +enum mgp_error mgp_type_node(const struct mgp_type **result); /// Get the type representing graph relationship values. /// /// Since a relationship contains a map of properties, the relationship itself /// is also of MAP type. -const struct mgp_type *mgp_type_relationship(); +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +enum mgp_error mgp_type_relationship(const struct mgp_type **result); /// Get the type representing a graph path (walk) from one node to another. -const struct mgp_type *mgp_type_path(); +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +enum mgp_error mgp_type_path(const struct mgp_type **result); /// Build a type representing a list of values of given `element_type`. /// -/// NULL is returned if unable to allocate the new type. -const struct mgp_type *mgp_type_list(const struct mgp_type *element_type); +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +enum mgp_error mgp_type_list(const struct mgp_type *element_type, const struct mgp_type **result); /// Build a type representing either a `null` value or a value of given `type`. /// -/// NULL is returned if unable to allocate the new type. -const struct mgp_type *mgp_type_nullable(const struct mgp_type *type); +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +enum mgp_error mgp_type_nullable(const struct mgp_type *type, const struct mgp_type **result); ///@} /// @name Query Module & Procedures @@ -719,9 +787,11 @@ typedef void (*mgp_proc_cb)(const struct mgp_list *, const struct mgp_graph *, s /// Note that Unicode characters are not allowed. Additionally, names are /// case-sensitive. /// -/// NULL is returned if unable to allocate memory for mgp_proc; if `name` is -/// not valid or a procedure with the same name was already registered. -struct mgp_proc *mgp_module_add_read_procedure(struct mgp_module *module, const char *name, mgp_proc_cb cb); +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for mgp_proc. +/// Return MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid procedure name. +/// RETURN MGP_ERROR_LOGIC_ERROR if a procedure with the same name was already registered. +enum mgp_error mgp_module_add_read_procedure(struct mgp_module *module, const char *name, mgp_proc_cb cb, + struct mgp_proc **result); /// Add a required argument to a procedure. /// @@ -734,10 +804,10 @@ struct mgp_proc *mgp_module_add_read_procedure(struct mgp_module *module, const /// /// Passed in `type` describes what kind of values can be used as the argument. /// -/// 0 is returned if unable to allocate memory for an argument; if invoking this -/// function after setting an optional argument or if `name` is not valid. -/// Non-zero is returned on success. -int mgp_proc_add_arg(struct mgp_proc *proc, const char *name, const struct mgp_type *type); +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for an argument. +/// Return MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid argument name. +/// RETURN MGP_ERROR_LOGIC_ERROR if the procedure already has any optional argument. +enum mgp_error mgp_proc_add_arg(struct mgp_proc *proc, const char *name, const struct mgp_type *type); /// Add an optional argument with a default value to a procedure. /// @@ -757,11 +827,12 @@ int mgp_proc_add_arg(struct mgp_proc *proc, const char *name, const struct mgp_t /// a graph element (node, relationship, path) and it must satisfy the given /// `type`. /// -/// 0 is returned if unable to allocate memory for an argument; if `name` is -/// not valid or `default_value` does not satisfy `type`. Non-zero is returned -/// on success. -int mgp_proc_add_opt_arg(struct mgp_proc *proc, const char *name, const struct mgp_type *type, - const struct mgp_value *default_value); +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for an argument. +/// Return MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid argument name. +/// RETURN MGP_ERROR_OUT_OF_RANGE if `default_value` is a graph element (vertex, edge or path). +/// RETURN MGP_ERROR_LOGIC_ERROR if `default_value` does not satisfy `type`. +enum mgp_error mgp_proc_add_opt_arg(struct mgp_proc *proc, const char *name, const struct mgp_type *type, + const struct mgp_value *default_value); /// Add a result field to a procedure. /// @@ -771,16 +842,20 @@ int mgp_proc_add_opt_arg(struct mgp_proc *proc, const char *name, const struct m /// Passed in `type` describes what kind of values can be returned through the /// result field. /// -/// 0 is returned if unable to allocate memory for a result field; if -/// `name` is not valid or if a result field with the same name was already -/// added. Non-zero is returned on success. -int mgp_proc_add_result(struct mgp_proc *proc, const char *name, const struct mgp_type *type); +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for an argument. +/// Return MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid result name. +/// RETURN MGP_ERROR_LOGIC_ERROR if a result field with the same name was already added. +enum mgp_error mgp_proc_add_result(struct mgp_proc *proc, const char *name, const struct mgp_type *type); /// Add a result field to a procedure and mark it as deprecated. /// /// This is the same as mgp_proc_add_result, but the result field will be marked /// as deprecated. -int mgp_proc_add_deprecated_result(struct mgp_proc *proc, const char *name, const struct mgp_type *type); +/// +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for an argument. +/// Return MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid result name. +/// RETURN MGP_ERROR_LOGIC_ERROR if a result field with the same name was already added. +enum mgp_error mgp_proc_add_deprecated_result(struct mgp_proc *proc, const char *name, const struct mgp_type *type); ///@} /// @name Execution @@ -817,28 +892,29 @@ struct mgp_messages; /// Payload is not null terminated and not a string but rather a byte array. /// You need to call mgp_message_payload_size() first, to read the size of /// the payload. -const char *mgp_message_payload(const struct mgp_message *); +enum mgp_error mgp_message_payload(const struct mgp_message *message, const char **result); /// Return the payload size -size_t mgp_message_payload_size(const struct mgp_message *); +enum mgp_error mgp_message_payload_size(const struct mgp_message *message, size_t *result); /// Return the name of topic -const char *mgp_message_topic_name(const struct mgp_message *); +enum mgp_error mgp_message_topic_name(const struct mgp_message *message, const char **result); /// Return the key of mgp_message as a byte array -const char *mgp_message_key(const struct mgp_message *); +enum mgp_error mgp_message_key(const struct mgp_message *message, const char **result); /// Return the key size of mgp_message -size_t mgp_message_key_size(const struct mgp_message *); +enum mgp_error mgp_message_key_size(const struct mgp_message *message, size_t *result); /// Return the timestamp of mgp_message as a byte array -int64_t mgp_message_timestamp(const struct mgp_message *); +enum mgp_error mgp_message_timestamp(const struct mgp_message *message, int64_t *result); /// Return the number of messages contained in the mgp_messages list -size_t mgp_messages_size(const struct mgp_messages *); +/// Current implementation always returns without errors. +enum mgp_error mgp_messages_size(const struct mgp_messages *message, size_t *result); /// Return the message from a messages list at given index -const struct mgp_message *mgp_messages_at(const struct mgp_messages *, size_t); +enum mgp_error mgp_messages_at(const struct mgp_messages *message, size_t index, const struct mgp_message **result); /// Entry-point for a module transformation, invoked through a stream transformation. /// @@ -848,9 +924,17 @@ const struct mgp_message *mgp_messages_at(const struct mgp_messages *, size_t); typedef void (*mgp_trans_cb)(const struct mgp_messages *, const struct mgp_graph *, struct mgp_result *, struct mgp_memory *); -/// Adds a transformation cb to the module pointed by mgp_module. -/// Return non-zero if the transformation is added successfully. -int mgp_module_add_transformation(struct mgp_module *module, const char *name, mgp_trans_cb cb); +/// Register a transformation with a module. +/// +/// The `name` must be a sequence of digits, underscores, lowercase and +/// uppercase Latin letters. The name must begin with a non-digit character. +/// Note that Unicode characters are not allowed. Additionally, names are +/// case-sensitive. +/// +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for transformation. +/// Return MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid transformation name. +/// RETURN MGP_ERROR_LOGIC_ERROR if a transformation with the same name was already registered. +enum mgp_error mgp_module_add_transformation(struct mgp_module *module, const char *name, mgp_trans_cb cb); /// @} #ifdef __cplusplus diff --git a/query_modules/example.c b/query_modules/example.c index 463feb6c0..c64a44a5f 100644 --- a/query_modules/example.c +++ b/query_modules/example.c @@ -16,61 +16,101 @@ // CALL example.procedure(1, 2) YIELD args, result; // CALL example.procedure(1) YIELD args, result; // Naturally, you may pass in different arguments or yield less fields. -static void procedure(const struct mgp_list *args, - const struct mgp_graph *graph, struct mgp_result *result, +static void procedure(const struct mgp_list *args, const struct mgp_graph *graph, struct mgp_result *result, struct mgp_memory *memory) { - struct mgp_list *args_copy = mgp_list_make_empty(mgp_list_size(args), memory); - if (args_copy == NULL) goto error_memory; - for (size_t i = 0; i < mgp_list_size(args); ++i) { - int success = mgp_list_append(args_copy, mgp_list_at(args, i)); - if (!success) goto error_free_list; + size_t args_size = 0; + if (mgp_list_size(args, &args_size) != MGP_ERROR_NO_ERROR) { + goto error_something_went_wrong; + } + struct mgp_list *args_copy = NULL; + if (mgp_list_make_empty(args_size, memory, &args_copy) != MGP_ERROR_NO_ERROR) { + goto error_something_went_wrong; + } + for (size_t i = 0; i < args_size; ++i) { + const struct mgp_value *value = NULL; + if (mgp_list_at(args, i, &value) != MGP_ERROR_NO_ERROR) { + goto error_free_list; + } + if (mgp_list_append(args_copy, value) != MGP_ERROR_NO_ERROR) { + goto error_free_list; + } + } + struct mgp_result_record *record = NULL; + if (mgp_result_new_record(result, &record) != MGP_ERROR_NO_ERROR) { + goto error_free_list; } - struct mgp_result_record *record = mgp_result_new_record(result); - if (record == NULL) goto error_free_list; // Transfer ownership of args_copy to mgp_value. - struct mgp_value *args_value = mgp_value_make_list(args_copy); - if (args_value == NULL) goto error_free_list; - int args_inserted = mgp_result_record_insert(record, "args", args_value); + struct mgp_value *args_value = NULL; + if (mgp_value_make_list(args_copy, &args_value) != MGP_ERROR_NO_ERROR) { + goto error_free_list; + } // Release `args_value` and contained `args_copy`. + if (mgp_result_record_insert(record, "args", args_value) != MGP_ERROR_NO_ERROR) { + mgp_value_destroy(args_value); + goto error_something_went_wrong; + } mgp_value_destroy(args_value); - if (!args_inserted) goto error_memory; - struct mgp_value *hello_world_value = - mgp_value_make_string("Hello World!", memory); - if (hello_world_value == NULL) goto error_memory; - int result_inserted = - mgp_result_record_insert(record, "result", hello_world_value); + struct mgp_value *hello_world_value = NULL; + if (mgp_value_make_string("Hello World!", memory, &hello_world_value) != MGP_ERROR_NO_ERROR) { + goto error_something_went_wrong; + } + enum mgp_error insert_result = mgp_result_record_insert(record, "result", hello_world_value); mgp_value_destroy(hello_world_value); - if (!result_inserted) goto error_memory; + if (insert_result != MGP_ERROR_NO_ERROR) { + goto error_something_went_wrong; + } // We have successfully finished, so return without error reporting. return; error_free_list: mgp_list_destroy(args_copy); -error_memory: - mgp_result_set_error_msg(result, "Not enough memory!"); +error_something_went_wrong: + mgp_result_set_error_msg(result, "Something went wrong!"); return; } // 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) { - struct mgp_proc *proc = - mgp_module_add_read_procedure(module, "procedure", procedure); - if (!proc) return 1; - if (!mgp_proc_add_arg(proc, "required_arg", - mgp_type_nullable(mgp_type_any()))) + struct mgp_proc *proc = NULL; + if (mgp_module_add_read_procedure(module, "procedure", procedure, &proc) != MGP_ERROR_NO_ERROR) { return 1; - struct mgp_value *null_value = mgp_value_make_null(memory); - if (!mgp_proc_add_opt_arg(proc, "optional_arg", - mgp_type_nullable(mgp_type_any()), null_value)) { + } + const struct mgp_type *any_type = NULL; + if (mgp_type_any(&any_type) != MGP_ERROR_NO_ERROR) { + return 1; + } + const struct mgp_type *nullable_any_type = NULL; + if (mgp_type_nullable(any_type, &nullable_any_type) != MGP_ERROR_NO_ERROR) { + return 1; + } + if (mgp_proc_add_arg(proc, "required_arg", nullable_any_type) != MGP_ERROR_NO_ERROR) { + return 1; + } + + struct mgp_value *null_value = NULL; + if (mgp_value_make_null(memory, &null_value) != MGP_ERROR_NO_ERROR) { + return 1; + } + if (mgp_proc_add_opt_arg(proc, "optional_arg", nullable_any_type, null_value) != MGP_ERROR_NO_ERROR) { mgp_value_destroy(null_value); return 1; } mgp_value_destroy(null_value); - if (!mgp_proc_add_result(proc, "result", mgp_type_string())) return 1; - if (!mgp_proc_add_result(proc, "args", - mgp_type_list(mgp_type_nullable(mgp_type_any())))) + const struct mgp_type *string = NULL; + if (mgp_type_string(&string) != MGP_ERROR_NO_ERROR) { return 1; + } + if (mgp_proc_add_result(proc, "result", string) != MGP_ERROR_NO_ERROR) { + return 1; + } + const struct mgp_type *list_of_anything = NULL; + if (mgp_type_list(nullable_any_type, &list_of_anything) != MGP_ERROR_NO_ERROR) { + return 1; + } + if (mgp_proc_add_result(proc, "args", list_of_anything)) { + return 1; + } return 0; } diff --git a/src/integrations/kafka/consumer.cpp b/src/integrations/kafka/consumer.cpp index af073633b..08c4f90c2 100644 --- a/src/integrations/kafka/consumer.cpp +++ b/src/integrations/kafka/consumer.cpp @@ -229,13 +229,13 @@ void Consumer::Check(std::optional timeout, std::opti utils::OnScopeExit restore_is_running([this] { is_running_.store(false); }); if (last_assignment_.empty()) { - if (auto err = consumer_->assignment(last_assignment_); err != RdKafka::ERR_NO_ERROR) { + if (const auto err = consumer_->assignment(last_assignment_); err != RdKafka::ERR_NO_ERROR) { spdlog::warn("Saving the commited offset of consumer {} failed: {}", info_.consumer_name, RdKafka::err2str(err)); throw ConsumerCheckFailedException(info_.consumer_name, fmt::format("Couldn't save commited offsets: '{}'", RdKafka::err2str(err))); } } else { - if (auto err = consumer_->assign(last_assignment_); err != RdKafka::ERR_NO_ERROR) { + if (const auto err = consumer_->assign(last_assignment_); err != RdKafka::ERR_NO_ERROR) { throw ConsumerCheckFailedException(info_.consumer_name, fmt::format("Couldn't restore commited offsets: '{}'", RdKafka::err2str(err))); } @@ -300,7 +300,7 @@ void Consumer::StartConsuming() { is_running_.store(true); if (!last_assignment_.empty()) { - if (auto err = consumer_->assign(last_assignment_); err != RdKafka::ERR_NO_ERROR) { + if (const auto err = consumer_->assign(last_assignment_); err != RdKafka::ERR_NO_ERROR) { throw ConsumerStartFailedException(info_.consumer_name, fmt::format("Couldn't restore commited offsets: '{}'", RdKafka::err2str(err))); } @@ -328,7 +328,7 @@ void Consumer::StartConsuming() { try { consumer_function_(batch); - if (auto err = consumer_->commitSync(); err != RdKafka::ERR_NO_ERROR) { + if (const auto err = consumer_->commitSync(); err != RdKafka::ERR_NO_ERROR) { spdlog::warn("Committing offset of consumer {} failed: {}", info_.consumer_name, RdKafka::err2str(err)); break; } diff --git a/src/query/context.hpp b/src/query/context.hpp index afdd83f5d..5245bdbaf 100644 --- a/src/query/context.hpp +++ b/src/query/context.hpp @@ -64,7 +64,7 @@ struct ExecutionContext { static_assert(std::is_move_assignable_v, "ExecutionContext must be move assignable!"); static_assert(std::is_move_constructible_v, "ExecutionContext must be move constructible!"); -inline bool MustAbort(const ExecutionContext &context) { +inline bool MustAbort(const ExecutionContext &context) noexcept { return (context.is_shutting_down != nullptr && context.is_shutting_down->load(std::memory_order_acquire)) || context.timer.IsExpired(); } diff --git a/src/query/db_accessor.hpp b/src/query/db_accessor.hpp index 26b7b6a19..078abd4ec 100644 --- a/src/query/db_accessor.hpp +++ b/src/query/db_accessor.hpp @@ -73,11 +73,11 @@ class EdgeAccessor final { int64_t CypherId() const { return impl_.Gid().AsInt(); } - auto Gid() const { return impl_.Gid(); } + storage::Gid Gid() const noexcept { return impl_.Gid(); } - bool operator==(const EdgeAccessor &e) const { return impl_ == e.impl_; } + bool operator==(const EdgeAccessor &e) const noexcept { return impl_ == e.impl_; } - bool operator!=(const EdgeAccessor &e) const { return !(*this == e); } + bool operator!=(const EdgeAccessor &e) const noexcept { return !(*this == e); } }; class VertexAccessor final { @@ -87,7 +87,7 @@ class VertexAccessor final { static EdgeAccessor MakeEdgeAccessor(const storage::EdgeAccessor impl) { return EdgeAccessor(impl); } public: - explicit VertexAccessor(storage::VertexAccessor impl) : impl_(std::move(impl)) {} + explicit VertexAccessor(storage::VertexAccessor impl) : impl_(impl) {} bool IsVisible(storage::View view) const { return impl_.IsVisible(view); } @@ -158,11 +158,14 @@ class VertexAccessor final { int64_t CypherId() const { return impl_.Gid().AsInt(); } - auto Gid() const { return impl_.Gid(); } + storage::Gid Gid() const noexcept { return impl_.Gid(); } - bool operator==(const VertexAccessor &v) const { return impl_ == v.impl_; } + bool operator==(const VertexAccessor &v) const noexcept { + static_assert(noexcept(impl_ == v.impl_)); + return impl_ == v.impl_; + } - bool operator!=(const VertexAccessor &v) const { return !(*this == v); } + bool operator!=(const VertexAccessor &v) const noexcept { return !(*this == v); } }; inline VertexAccessor EdgeAccessor::To() const { return VertexAccessor(impl_.ToVertex()); } diff --git a/src/query/plan/operator.cpp b/src/query/plan/operator.cpp index 8cf25ec77..b2c13c27b 100644 --- a/src/query/plan/operator.cpp +++ b/src/query/plan/operator.cpp @@ -3601,7 +3601,7 @@ std::unordered_map CallProcedure::GetAndResetCounters() { namespace { void CallCustomProcedure(const std::string_view &fully_qualified_procedure_name, const mgp_proc &proc, - const std::vector &args, const mgp_graph &graph, ExpressionEvaluator *evaluator, + const std::vector &args, mgp_graph &graph, ExpressionEvaluator *evaluator, utils::MemoryResource *memory, std::optional memory_limit, mgp_result *result) { static_assert(std::uses_allocator_v>, "Expected mgp_value to use custom allocator and makes STL " diff --git a/src/query/procedure/cypher_types.hpp b/src/query/procedure/cypher_types.hpp index e69122205..33ef5cc56 100644 --- a/src/query/procedure/cypher_types.hpp +++ b/src/query/procedure/cypher_types.hpp @@ -7,6 +7,7 @@ #include #include +#include "query/procedure/mg_procedure_helpers.hpp" #include "query/typed_value.hpp" #include "utils/memory.hpp" #include "utils/pmr/string.hpp" @@ -50,7 +51,7 @@ class AnyType : public CypherType { public: std::string_view GetPresentableName() const override { return "ANY"; } - bool SatisfiesType(const mgp_value &value) const override { return !mgp_value_is_null(&value); } + bool SatisfiesType(const mgp_value &value) const override { return !CallBool(mgp_value_is_null, &value); } bool SatisfiesType(const query::TypedValue &value) const override { return !value.IsNull(); } }; @@ -59,7 +60,7 @@ class BoolType : public CypherType { public: std::string_view GetPresentableName() const override { return "BOOLEAN"; } - bool SatisfiesType(const mgp_value &value) const override { return mgp_value_is_bool(&value); } + bool SatisfiesType(const mgp_value &value) const override { return CallBool(mgp_value_is_bool, &value); } bool SatisfiesType(const query::TypedValue &value) const override { return value.IsBool(); } }; @@ -68,7 +69,7 @@ class StringType : public CypherType { public: std::string_view GetPresentableName() const override { return "STRING"; } - bool SatisfiesType(const mgp_value &value) const override { return mgp_value_is_string(&value); } + bool SatisfiesType(const mgp_value &value) const override { return CallBool(mgp_value_is_string, &value); } bool SatisfiesType(const query::TypedValue &value) const override { return value.IsString(); } }; @@ -77,7 +78,7 @@ class IntType : public CypherType { public: std::string_view GetPresentableName() const override { return "INTEGER"; } - bool SatisfiesType(const mgp_value &value) const override { return mgp_value_is_int(&value); } + bool SatisfiesType(const mgp_value &value) const override { return CallBool(mgp_value_is_int, &value); } bool SatisfiesType(const query::TypedValue &value) const override { return value.IsInt(); } }; @@ -86,7 +87,7 @@ class FloatType : public CypherType { public: std::string_view GetPresentableName() const override { return "FLOAT"; } - bool SatisfiesType(const mgp_value &value) const override { return mgp_value_is_double(&value); } + bool SatisfiesType(const mgp_value &value) const override { return CallBool(mgp_value_is_double, &value); } bool SatisfiesType(const query::TypedValue &value) const override { return value.IsDouble(); } }; @@ -96,7 +97,7 @@ class NumberType : public CypherType { std::string_view GetPresentableName() const override { return "NUMBER"; } bool SatisfiesType(const mgp_value &value) const override { - return mgp_value_is_int(&value) || mgp_value_is_double(&value); + return CallBool(mgp_value_is_int, &value) || CallBool(mgp_value_is_double, &value); } bool SatisfiesType(const query::TypedValue &value) const override { return value.IsInt() || value.IsDouble(); } @@ -106,7 +107,7 @@ class NodeType : public CypherType { public: std::string_view GetPresentableName() const override { return "NODE"; } - bool SatisfiesType(const mgp_value &value) const override { return mgp_value_is_vertex(&value); } + bool SatisfiesType(const mgp_value &value) const override { return CallBool(mgp_value_is_vertex, &value); } bool SatisfiesType(const query::TypedValue &value) const override { return value.IsVertex(); } }; @@ -115,7 +116,7 @@ class RelationshipType : public CypherType { public: std::string_view GetPresentableName() const override { return "RELATIONSHIP"; } - bool SatisfiesType(const mgp_value &value) const override { return mgp_value_is_edge(&value); } + bool SatisfiesType(const mgp_value &value) const override { return CallBool(mgp_value_is_edge, &value); } bool SatisfiesType(const query::TypedValue &value) const override { return value.IsEdge(); } }; @@ -124,7 +125,7 @@ class PathType : public CypherType { public: std::string_view GetPresentableName() const override { return "PATH"; } - bool SatisfiesType(const mgp_value &value) const override { return mgp_value_is_path(&value); } + bool SatisfiesType(const mgp_value &value) const override { return CallBool(mgp_value_is_path, &value); } bool SatisfiesType(const query::TypedValue &value) const override { return value.IsPath(); } }; @@ -141,7 +142,8 @@ class MapType : public CypherType { std::string_view GetPresentableName() const override { return "MAP"; } bool SatisfiesType(const mgp_value &value) const override { - return mgp_value_is_map(&value) || mgp_value_is_vertex(&value) || mgp_value_is_edge(&value); + return CallBool(mgp_value_is_map, &value) || CallBool(mgp_value_is_vertex, &value) || + CallBool(mgp_value_is_edge, &value); } bool SatisfiesType(const query::TypedValue &value) const override { @@ -166,10 +168,15 @@ class ListType : public CypherType { std::string_view GetPresentableName() const override { return presentable_name_; } bool SatisfiesType(const mgp_value &value) const override { - if (!mgp_value_is_list(&value)) return false; - const auto *list = mgp_value_get_list(&value); - for (size_t i = 0; i < mgp_list_size(list); ++i) { - if (!element_type_->SatisfiesType(*mgp_list_at(list, i))) return false; + if (!CallBool(mgp_value_is_list, &value)) { + return false; + } + auto *list = Call(mgp_value_get_list, &value); + const auto list_size = Call(mgp_list_size, list); + for (size_t i = 0; i < list_size; ++i) { + if (!element_type_->SatisfiesType(*Call(mgp_list_at, list, i))) { + return false; + }; } return true; } @@ -228,7 +235,7 @@ class NullableType : public CypherType { std::string_view GetPresentableName() const override { return presentable_name_; } bool SatisfiesType(const mgp_value &value) const override { - return mgp_value_is_null(&value) || type_->SatisfiesType(value); + return CallBool(mgp_value_is_null, &value) || type_->SatisfiesType(value); } bool SatisfiesType(const query::TypedValue &value) const override { diff --git a/src/query/procedure/mg_procedure_helpers.hpp b/src/query/procedure/mg_procedure_helpers.hpp new file mode 100644 index 000000000..792745f6a --- /dev/null +++ b/src/query/procedure/mg_procedure_helpers.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include + +#include "mg_procedure.h" + +namespace query::procedure { +template +TResult Call(TFunc func, TArgs... args) { + static_assert(std::is_trivially_copyable_v); + static_assert((std::is_trivially_copyable_v> && ...)); + TResult result{}; + MG_ASSERT(func(args..., &result) == MGP_ERROR_NO_ERROR); + return result; +} + +template +bool CallBool(TFunc func, TArgs... args) { + return Call(func, args...) != 0; +} + +template +using MgpRawObjectDeleter = void (*)(TObj *); + +template +using MgpUniquePtr = std::unique_ptr>; + +template +mgp_error CreateMgpObject(MgpUniquePtr &obj, TFunc func, TArgs &&...args) { + TObj *raw_obj{nullptr}; + const auto err = func(std::forward(args)..., &raw_obj); + obj.reset(raw_obj); + return err; +} +} // namespace query::procedure diff --git a/src/query/procedure/mg_procedure_impl.cpp b/src/query/procedure/mg_procedure_impl.cpp index e674c0a56..efd45be1c 100644 --- a/src/query/procedure/mg_procedure_impl.cpp +++ b/src/query/procedure/mg_procedure_impl.cpp @@ -3,10 +3,15 @@ #include #include #include +#include +#include #include +#include #include +#include #include "module.hpp" +#include "query/procedure/mg_procedure_helpers.hpp" #include "utils/algorithm.hpp" #include "utils/concepts.hpp" #include "utils/logging.hpp" @@ -37,84 +42,170 @@ void *MgpAlignedAllocImpl(utils::MemoryResource &memory, const size_t size_in_by const size_t bytes_for_header = *maybe_bytes_for_header; const size_t alloc_size = bytes_for_header + size_in_bytes; if (alloc_size < size_in_bytes) return nullptr; + + void *ptr = memory.Allocate(alloc_size, alloc_align); + char *data = reinterpret_cast(ptr) + bytes_for_header; + std::memcpy(data - sizeof(size_in_bytes), &size_in_bytes, sizeof(size_in_bytes)); + std::memcpy(data - sizeof(size_in_bytes) - sizeof(alloc_align), &alloc_align, sizeof(alloc_align)); + return data; +} + +void MgpFreeImpl(utils::MemoryResource &memory, void *const p) noexcept { try { - void *ptr = memory.Allocate(alloc_size, alloc_align); - char *data = reinterpret_cast(ptr) + bytes_for_header; - std::memcpy(data - sizeof(size_in_bytes), &size_in_bytes, sizeof(size_in_bytes)); - std::memcpy(data - sizeof(size_in_bytes) - sizeof(alloc_align), &alloc_align, sizeof(alloc_align)); - return data; + if (!p) return; + char *const data = reinterpret_cast(p); + // Read the header containing size & alignment info. + size_t size_in_bytes{}; + std::memcpy(&size_in_bytes, data - sizeof(size_in_bytes), sizeof(size_in_bytes)); + size_t alloc_align{}; + std::memcpy(&alloc_align, data - sizeof(size_in_bytes) - sizeof(alloc_align), sizeof(alloc_align)); + // Reconstruct how many bytes we allocated on top of the original request. + // We need not check allocation request overflow, since we did so already in + // mgp_aligned_alloc. + const size_t header_size = sizeof(size_in_bytes) + sizeof(alloc_align); + const size_t bytes_for_header = *utils::RoundUint64ToMultiple(header_size, alloc_align); + const size_t alloc_size = bytes_for_header + size_in_bytes; + // Get the original ptr we allocated. + void *const original_ptr = data - bytes_for_header; + memory.Deallocate(original_ptr, alloc_size, alloc_align); + } catch (const utils::BasicException &be) { + spdlog::error("BasicException during the release of memory for query modules: {}", be.what()); + } catch (const std::exception &e) { + spdlog::error("std::exception during the release of memory for query modules: {}", e.what()); } catch (...) { - return nullptr; + spdlog::error("Unexpected throw during the release of memory for query modules"); } } +struct NonexistentObjectException : public utils::BasicException { + using utils::BasicException::BasicException; +}; -void MgpFreeImpl(utils::MemoryResource &memory, void *const p) { - if (!p) return; - char *const data = reinterpret_cast(p); - // Read the header containing size & alignment info. - size_t size_in_bytes; - std::memcpy(&size_in_bytes, data - sizeof(size_in_bytes), sizeof(size_in_bytes)); - size_t alloc_align; - std::memcpy(&alloc_align, data - sizeof(size_in_bytes) - sizeof(alloc_align), sizeof(alloc_align)); - // Reconstruct how many bytes we allocated on top of the original request. - // We need not check allocation request overflow, since we did so already in - // mgp_aligned_alloc. - const size_t header_size = sizeof(size_in_bytes) + sizeof(alloc_align); - const size_t bytes_for_header = *utils::RoundUint64ToMultiple(header_size, alloc_align); - const size_t alloc_size = bytes_for_header + size_in_bytes; - // Get the original ptr we allocated. - void *const original_ptr = data - bytes_for_header; - memory.Deallocate(original_ptr, alloc_size, alloc_align); +struct KeyAlreadyExistsException : public utils::BasicException { + using utils::BasicException::BasicException; +}; + +struct InsufficientBufferException : public utils::BasicException { + using utils::BasicException::BasicException; +}; + +template +concept ReturnsType = std::same_as, TReturn>; + +template +concept ReturnsVoid = ReturnsType; + +template +void WrapExceptionsHelper(TFunc &&func) { + std::forward(func)(); } +template > +void WrapExceptionsHelper(TFunc &&func, TReturn *result) { + *result = {}; + *result = std::forward(func)(); +} + +template +[[nodiscard]] mgp_error WrapExceptions(TFunc &&func, Args &&...args) noexcept { + static_assert(sizeof...(args) <= 1, "WrapExceptions should have only one or zero parameter!"); + try { + WrapExceptionsHelper(std::forward(func), std::forward(args)...); + } catch (const NonexistentObjectException &neoe) { + spdlog::error("Nonexistent object error during mg API call: {}", neoe.what()); + return MGP_ERROR_NON_EXISTENT_OBJECT; + } catch (const KeyAlreadyExistsException &kaee) { + spdlog::error("Key already exists error during mg API call: {}", kaee.what()); + return MGP_ERROR_KEY_ALREADY_EXISTS; + } catch (const InsufficientBufferException &ibe) { + spdlog::error("Insufficient buffer error during mg API call: {}", ibe.what()); + return MGP_ERROR_INSUFFICIENT_BUFFER; + } catch (const std::bad_alloc &bae) { + spdlog::error("Memory allocation error during mg API call: {}", bae.what()); + return MGP_ERROR_UNABLE_TO_ALLOCATE; + } catch (const utils::OutOfMemoryException &oome) { + spdlog::error("Memory limit exceeded during mg API call: {}", oome.what()); + return MGP_ERROR_UNABLE_TO_ALLOCATE; + } catch (const std::out_of_range &oore) { + spdlog::error("Out of range error during mg API call: {}", oore.what()); + return MGP_ERROR_OUT_OF_RANGE; + } catch (const std::invalid_argument &iae) { + spdlog::error("Invalid argument error during mg API call: {}", iae.what()); + return MGP_ERROR_INVALID_ARGUMENT; + } catch (const std::logic_error &lee) { + spdlog::error("Logic error during mg API call: {}", lee.what()); + return MGP_ERROR_LOGIC_ERROR; + } catch (const std::exception &e) { + spdlog::error("Unexpected error during mg API call: {}", e.what()); + return MGP_ERROR_UNKNOWN_ERROR; + } catch (...) { + spdlog::error("Unexpected error during mg API call"); + return MGP_ERROR_UNKNOWN_ERROR; + } + return MGP_ERROR_NO_ERROR; +} } // namespace -void *mgp_alloc(mgp_memory *memory, size_t size_in_bytes) { - return mgp_aligned_alloc(memory, size_in_bytes, alignof(std::max_align_t)); +mgp_error mgp_alloc(mgp_memory *memory, size_t size_in_bytes, void **result) { + return mgp_aligned_alloc(memory, size_in_bytes, alignof(std::max_align_t), result); } -void *mgp_aligned_alloc(mgp_memory *memory, const size_t size_in_bytes, const size_t alignment) { - return MgpAlignedAllocImpl(*memory->impl, size_in_bytes, alignment); +mgp_error mgp_aligned_alloc(mgp_memory *memory, const size_t size_in_bytes, const size_t alignment, void **result) { + return WrapExceptions( + [memory, size_in_bytes, alignment] { return MgpAlignedAllocImpl(*memory->impl, size_in_bytes, alignment); }, + result); } -void mgp_free(mgp_memory *memory, void *const p) { MgpFreeImpl(*memory->impl, p); } - -void *mgp_global_alloc(size_t size_in_bytes) { - return mgp_global_aligned_alloc(size_in_bytes, alignof(std::max_align_t)); +void mgp_free(mgp_memory *memory, void *const p) { + static_assert(noexcept(MgpFreeImpl(*memory->impl, p))); + MgpFreeImpl(*memory->impl, p); } -void *mgp_global_aligned_alloc(size_t size_in_bytes, size_t alignment) { - return MgpAlignedAllocImpl(gModuleRegistry.GetSharedMemoryResource(), size_in_bytes, alignment); +mgp_error mgp_global_alloc(size_t size_in_bytes, void **result) { + return mgp_global_aligned_alloc(size_in_bytes, alignof(std::max_align_t), result); } -void mgp_global_free(void *const p) { MgpFreeImpl(gModuleRegistry.GetSharedMemoryResource(), p); } +mgp_error mgp_global_aligned_alloc(size_t size_in_bytes, size_t alignment, void **result) { + return WrapExceptions( + [size_in_bytes, alignment] { + return MgpAlignedAllocImpl(gModuleRegistry.GetSharedMemoryResource(), size_in_bytes, alignment); + }, + result); +} + +void mgp_global_free(void *const p) { + static_assert(noexcept(MgpFreeImpl(gModuleRegistry.GetSharedMemoryResource(), p))); + MgpFreeImpl(gModuleRegistry.GetSharedMemoryResource(), p); +} namespace { -// May throw whatever the constructor of U throws. `std::bad_alloc` is handled -// by returning nullptr. template -U *new_mgp_object(utils::MemoryResource *memory, TArgs &&...args) { +U *NewRawMgpObject(utils::MemoryResource *memory, TArgs &&...args) { utils::Allocator allocator(memory); - try { - return allocator.template new_object(std::forward(args)...); - } catch (const std::bad_alloc &) { - return nullptr; - } + return allocator.template new_object(std::forward(args)...); } template -U *new_mgp_object(mgp_memory *memory, TArgs &&...args) { - return new_mgp_object(memory->impl, std::forward(args)...); +U *NewRawMgpObject(mgp_memory *memory, TArgs &&...args) { + return NewRawMgpObject(memory->impl, std::forward(args)...); } // Assume that deallocation and object destruction never throws. If it does, // we are in big trouble. template -void delete_mgp_object(T *ptr) noexcept { - if (!ptr) return; - utils::Allocator allocator(ptr->GetMemoryResource()); - allocator.delete_object(ptr); +void DeleteRawMgpObject(T *ptr) noexcept { + try { + if (!ptr) return; + utils::Allocator allocator(ptr->GetMemoryResource()); + allocator.delete_object(ptr); + } catch (...) { + LOG_FATAL("Cannot deallocate mgp object"); + } +} + +template +MgpUniquePtr NewMgpObject(mgp_memory *memory, TArgs &&...args) { + return MgpUniquePtr(NewRawMgpObject(memory->impl, std::forward(args)...), &DeleteRawMgpObject); } mgp_value_type FromTypedValueType(query::TypedValue::Type type) { @@ -143,19 +234,19 @@ mgp_value_type FromTypedValueType(query::TypedValue::Type type) { } query::TypedValue ToTypedValue(const mgp_value &val, utils::MemoryResource *memory) { - switch (mgp_value_get_type(&val)) { + switch (Call(mgp_value_get_type, &val)) { case MGP_VALUE_TYPE_NULL: return query::TypedValue(memory); case MGP_VALUE_TYPE_BOOL: - return query::TypedValue(static_cast(mgp_value_get_bool(&val)), memory); + return query::TypedValue(CallBool(mgp_value_get_bool, &val), memory); case MGP_VALUE_TYPE_INT: - return query::TypedValue(mgp_value_get_int(&val), memory); + return query::TypedValue(Call(mgp_value_get_int, &val), memory); case MGP_VALUE_TYPE_DOUBLE: - return query::TypedValue(mgp_value_get_double(&val), memory); + return query::TypedValue(Call(mgp_value_get_double, &val), memory); case MGP_VALUE_TYPE_STRING: - return query::TypedValue(mgp_value_get_string(&val), memory); + return query::TypedValue(Call(mgp_value_get_string, &val), memory); case MGP_VALUE_TYPE_LIST: { - const auto *list = mgp_value_get_list(&val); + const auto *list = Call(mgp_value_get_list, &val); query::TypedValue::TVector tv_list(memory); tv_list.reserve(list->elems.size()); for (const auto &elem : list->elems) { @@ -164,7 +255,7 @@ query::TypedValue ToTypedValue(const mgp_value &val, utils::MemoryResource *memo return query::TypedValue(std::move(tv_list)); } case MGP_VALUE_TYPE_MAP: { - const auto *map = mgp_value_get_map(&val); + const auto *map = Call(mgp_value_get_map, &val); query::TypedValue::TMap tv_map(memory); for (const auto &item : map->items) { tv_map.emplace(item.first, ToTypedValue(item.second, memory)); @@ -172,11 +263,11 @@ query::TypedValue ToTypedValue(const mgp_value &val, utils::MemoryResource *memo return query::TypedValue(std::move(tv_map)); } case MGP_VALUE_TYPE_VERTEX: - return query::TypedValue(mgp_value_get_vertex(&val)->impl, memory); + return query::TypedValue(Call(mgp_value_get_vertex, &val)->impl, memory); case MGP_VALUE_TYPE_EDGE: - return query::TypedValue(mgp_value_get_edge(&val)->impl, memory); + return query::TypedValue(Call(mgp_value_get_edge, &val)->impl, memory); case MGP_VALUE_TYPE_PATH: { - const auto *path = mgp_value_get_path(&val); + const auto *path = Call(mgp_value_get_path, &val); MG_ASSERT(!path->vertices.empty()); MG_ASSERT(path->vertices.size() == path->edges.size() + 1); query::Path tv_path(path->vertices[0].impl, memory); @@ -406,7 +497,7 @@ namespace { void DeleteValueMember(mgp_value *value) noexcept { MG_ASSERT(value); utils::Allocator allocator(value->GetMemoryResource()); - switch (mgp_value_get_type(value)) { + switch (Call(mgp_value_get_type, value)) { case MGP_VALUE_TYPE_NULL: case MGP_VALUE_TYPE_BOOL: case MGP_VALUE_TYPE_INT: @@ -509,293 +600,393 @@ mgp_value::mgp_value(mgp_value &&other, utils::MemoryResource *m) : type(other.t mgp_value::~mgp_value() noexcept { DeleteValueMember(this); } -void mgp_value_destroy(mgp_value *val) { delete_mgp_object(val); } +void mgp_value_destroy(mgp_value *val) { DeleteRawMgpObject(val); } -mgp_value *mgp_value_make_null(mgp_memory *memory) { return new_mgp_object(memory); } +mgp_error mgp_value_make_null(mgp_memory *memory, mgp_value **result) { + return WrapExceptions([memory] { return NewRawMgpObject(memory); }, result); +} -mgp_value *mgp_value_make_bool(int val, mgp_memory *memory) { return new_mgp_object(memory, val != 0); } +mgp_error mgp_value_make_bool(int val, mgp_memory *memory, mgp_value **result) { + return WrapExceptions([val, memory] { return NewRawMgpObject(memory, val != 0); }, result); +} -mgp_value *mgp_value_make_int(int64_t val, mgp_memory *memory) { return new_mgp_object(memory, val); } - -mgp_value *mgp_value_make_double(double val, mgp_memory *memory) { return new_mgp_object(memory, val); } - -mgp_value *mgp_value_make_string(const char *val, mgp_memory *memory) { - try { - // This may throw something from std::string constructor, it could be - // std::length_error, but it's not really well defined, so catch all. - return new_mgp_object(memory, val); - } catch (...) { - return nullptr; +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define DEFINE_MGP_VALUE_MAKE_WITH_MEMORY(type, param) \ + mgp_error mgp_value_make_##type(param val, mgp_memory *memory, mgp_value **result) { \ + return WrapExceptions([val, memory] { return NewRawMgpObject(memory, val); }, result); \ } -} -mgp_value *mgp_value_make_list(mgp_list *val) { return new_mgp_object(val->GetMemoryResource(), val); } +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +DEFINE_MGP_VALUE_MAKE_WITH_MEMORY(int, int64_t); +DEFINE_MGP_VALUE_MAKE_WITH_MEMORY(double, double); +DEFINE_MGP_VALUE_MAKE_WITH_MEMORY(string, const char *); -mgp_value *mgp_value_make_map(mgp_map *val) { return new_mgp_object(val->GetMemoryResource(), val); } - -mgp_value *mgp_value_make_vertex(mgp_vertex *val) { return new_mgp_object(val->GetMemoryResource(), val); } - -mgp_value *mgp_value_make_edge(mgp_edge *val) { return new_mgp_object(val->GetMemoryResource(), val); } - -mgp_value *mgp_value_make_path(mgp_path *val) { return new_mgp_object(val->GetMemoryResource(), val); } - -mgp_value_type mgp_value_get_type(const mgp_value *val) { return val->type; } - -int mgp_value_is_null(const mgp_value *val) { return mgp_value_get_type(val) == MGP_VALUE_TYPE_NULL; } - -int mgp_value_is_bool(const mgp_value *val) { return mgp_value_get_type(val) == MGP_VALUE_TYPE_BOOL; } - -int mgp_value_is_int(const mgp_value *val) { return mgp_value_get_type(val) == MGP_VALUE_TYPE_INT; } - -int mgp_value_is_double(const mgp_value *val) { return mgp_value_get_type(val) == MGP_VALUE_TYPE_DOUBLE; } - -int mgp_value_is_string(const mgp_value *val) { return mgp_value_get_type(val) == MGP_VALUE_TYPE_STRING; } - -int mgp_value_is_list(const mgp_value *val) { return mgp_value_get_type(val) == MGP_VALUE_TYPE_LIST; } - -int mgp_value_is_map(const mgp_value *val) { return mgp_value_get_type(val) == MGP_VALUE_TYPE_MAP; } - -int mgp_value_is_vertex(const mgp_value *val) { return mgp_value_get_type(val) == MGP_VALUE_TYPE_VERTEX; } - -int mgp_value_is_edge(const mgp_value *val) { return mgp_value_get_type(val) == MGP_VALUE_TYPE_EDGE; } - -int mgp_value_is_path(const mgp_value *val) { return mgp_value_get_type(val) == MGP_VALUE_TYPE_PATH; } - -int mgp_value_get_bool(const mgp_value *val) { return val->bool_v ? 1 : 0; } - -int64_t mgp_value_get_int(const mgp_value *val) { return val->int_v; } - -double mgp_value_get_double(const mgp_value *val) { return val->double_v; } - -const char *mgp_value_get_string(const mgp_value *val) { return val->string_v.c_str(); } - -const mgp_list *mgp_value_get_list(const mgp_value *val) { return val->list_v; } - -const mgp_map *mgp_value_get_map(const mgp_value *val) { return val->map_v; } - -const mgp_vertex *mgp_value_get_vertex(const mgp_value *val) { return val->vertex_v; } - -const mgp_edge *mgp_value_get_edge(const mgp_value *val) { return val->edge_v; } - -const mgp_path *mgp_value_get_path(const mgp_value *val) { return val->path_v; } - -mgp_list *mgp_list_make_empty(size_t capacity, mgp_memory *memory) { - auto *list = new_mgp_object(memory); - if (!list) return nullptr; - try { - // May throw std::bad_alloc or std::length_error. - list->elems.reserve(capacity); - } catch (...) { - mgp_list_destroy(list); - return nullptr; +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define DEFINE_MGP_VALUE_MAKE(type) \ + mgp_error mgp_value_make_##type(mgp_##type *val, mgp_value **result) { \ + return WrapExceptions([val] { return NewRawMgpObject(val->GetMemoryResource(), val); }, result); \ } - return list; + +DEFINE_MGP_VALUE_MAKE(list) +DEFINE_MGP_VALUE_MAKE(map) +DEFINE_MGP_VALUE_MAKE(vertex) +DEFINE_MGP_VALUE_MAKE(edge) +DEFINE_MGP_VALUE_MAKE(path) + +namespace { +mgp_value_type MgpValueGetType(const mgp_value &val) noexcept { return val.type; } +} // namespace + +mgp_error mgp_value_get_type(const mgp_value *val, mgp_value_type *result) { + static_assert(noexcept(MgpValueGetType(*val))); + *result = MgpValueGetType(*val); + return MGP_ERROR_NO_ERROR; } -void mgp_list_destroy(mgp_list *list) { delete_mgp_object(list); } - -int mgp_list_append(mgp_list *list, const mgp_value *val) { - if (mgp_list_size(list) >= mgp_list_capacity(list)) return 0; - return mgp_list_append_extend(list, val); -} - -int mgp_list_append_extend(mgp_list *list, const mgp_value *val) { - try { - // May throw std::bad_alloc or std::length_error. - list->elems.push_back(*val); - } catch (...) { - return 0; +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define DEFINE_MGP_VALUE_IS(type_lowercase, type_uppercase) \ + mgp_error mgp_value_is_##type_lowercase(const mgp_value *val, int *result) { \ + static_assert(noexcept(MgpValueGetType(*val))); \ + *result = MgpValueGetType(*val) == MGP_VALUE_TYPE_##type_uppercase; \ + return MGP_ERROR_NO_ERROR; \ } - return 1; + +DEFINE_MGP_VALUE_IS(null, NULL) +DEFINE_MGP_VALUE_IS(bool, BOOL) +DEFINE_MGP_VALUE_IS(int, INT) +DEFINE_MGP_VALUE_IS(double, DOUBLE) +DEFINE_MGP_VALUE_IS(string, STRING) +DEFINE_MGP_VALUE_IS(list, LIST) +DEFINE_MGP_VALUE_IS(map, MAP) +DEFINE_MGP_VALUE_IS(vertex, VERTEX) +DEFINE_MGP_VALUE_IS(edge, EDGE) +DEFINE_MGP_VALUE_IS(path, PATH) + +mgp_error mgp_value_get_bool(const mgp_value *val, int *result) { + *result = val->bool_v ? 1 : 0; + return MGP_ERROR_NO_ERROR; +} +mgp_error mgp_value_get_int(const mgp_value *val, int64_t *result) { + *result = val->int_v; + return MGP_ERROR_NO_ERROR; +} +mgp_error mgp_value_get_double(const mgp_value *val, double *result) { + *result = val->double_v; + return MGP_ERROR_NO_ERROR; +} +mgp_error mgp_value_get_string(const mgp_value *val, const char **result) { + static_assert(noexcept(val->string_v.c_str())); + *result = val->string_v.c_str(); + return MGP_ERROR_NO_ERROR; } -size_t mgp_list_size(const mgp_list *list) { return list->elems.size(); } - -size_t mgp_list_capacity(const mgp_list *list) { return list->elems.capacity(); } - -const mgp_value *mgp_list_at(const mgp_list *list, size_t i) { - if (i >= mgp_list_size(list)) return nullptr; - return &list->elems[i]; -} - -mgp_map *mgp_map_make_empty(mgp_memory *memory) { return new_mgp_object(memory); } - -void mgp_map_destroy(mgp_map *map) { delete_mgp_object(map); } - -int mgp_map_insert(mgp_map *map, const char *key, const mgp_value *value) { - try { - // Unfortunately, cppreference.com does not say what exceptions are thrown, - // so catch all of them. It's probably `std::bad_alloc` and - // `std::length_error`. - map->items.emplace(key, *value); - return 1; - } catch (...) { - return 0; +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define DEFINE_MGP_VALUE_GET(type) \ + mgp_error mgp_value_get_##type(const mgp_value *val, const mgp_##type **result) { \ + *result = val->type##_v; \ + return MGP_ERROR_NO_ERROR; \ } + +DEFINE_MGP_VALUE_GET(list) +DEFINE_MGP_VALUE_GET(map) +DEFINE_MGP_VALUE_GET(vertex) +DEFINE_MGP_VALUE_GET(edge) +DEFINE_MGP_VALUE_GET(path) + +mgp_error mgp_list_make_empty(size_t capacity, mgp_memory *memory, mgp_list **result) { + return WrapExceptions( + [capacity, memory] { + auto list = NewMgpObject(memory); + list->elems.reserve(capacity); + return list.release(); + }, + result); } -size_t mgp_map_size(const mgp_map *map) { return map->items.size(); } +void mgp_list_destroy(mgp_list *list) { DeleteRawMgpObject(list); } -const mgp_value *mgp_map_at(const mgp_map *map, const char *key) { - auto found_it = map->items.find(key); - if (found_it == map->items.end()) return nullptr; - return &found_it->second; +namespace { +void MgpListAppendExtend(mgp_list &list, const mgp_value &value) { list.elems.push_back(value); } +} // namespace + +mgp_error mgp_list_append(mgp_list *list, const mgp_value *val) { + return WrapExceptions([list, val] { + if (Call(mgp_list_size, list) >= Call(mgp_list_capacity, list)) { + throw InsufficientBufferException{ + "Cannot append a new value to the mgp_list without extending it, because its size reached its capacity!"}; + } + MgpListAppendExtend(*list, *val); + }); } -const char *mgp_map_item_key(const mgp_map_item *item) { return item->key; } - -const mgp_value *mgp_map_item_value(const mgp_map_item *item) { return item->value; } - -mgp_map_items_iterator *mgp_map_iter_items(const mgp_map *map, mgp_memory *memory) { - return new_mgp_object(memory, map); +mgp_error mgp_list_append_extend(mgp_list *list, const mgp_value *val) { + return WrapExceptions([list, val] { MgpListAppendExtend(*list, *val); }); } -void mgp_map_items_iterator_destroy(mgp_map_items_iterator *it) { delete_mgp_object(it); } - -const mgp_map_item *mgp_map_items_iterator_get(const mgp_map_items_iterator *it) { - if (it->current_it == it->map->items.end()) return nullptr; - return &it->current; +mgp_error mgp_list_size(const mgp_list *list, size_t *result) { + static_assert(noexcept(list->elems.size())); + *result = list->elems.size(); + return MGP_ERROR_NO_ERROR; } -const mgp_map_item *mgp_map_items_iterator_next(mgp_map_items_iterator *it) { - if (it->current_it == it->map->items.end()) return nullptr; - if (++it->current_it == it->map->items.end()) return nullptr; - it->current.key = it->current_it->first.c_str(); - it->current.value = &it->current_it->second; - return &it->current; +mgp_error mgp_list_capacity(const mgp_list *list, size_t *result) { + static_assert(noexcept(list->elems.capacity())); + *result = list->elems.capacity(); + return MGP_ERROR_NO_ERROR; } -mgp_path *mgp_path_make_with_start(const mgp_vertex *vertex, mgp_memory *memory) { - auto *path = new_mgp_object(memory); - if (!path) return nullptr; - try { - path->vertices.push_back(*vertex); - } catch (...) { - delete_mgp_object(path); - return nullptr; - } - return path; +mgp_error mgp_list_at(const mgp_list *list, size_t i, const mgp_value **result) { + return WrapExceptions( + [list, i] { + if (i >= Call(mgp_list_size, list)) { + throw std::out_of_range("Element cannot be retrieved, because index exceeds list's size!"); + } + return &list->elems[i]; + }, + result); } -mgp_path *mgp_path_copy(const mgp_path *path, mgp_memory *memory) { - MG_ASSERT(mgp_path_size(path) == path->vertices.size() - 1, "Invalid mgp_path"); - return new_mgp_object(memory, *path); +mgp_error mgp_map_make_empty(mgp_memory *memory, mgp_map **result) { + return WrapExceptions([&memory] { return NewRawMgpObject(memory); }, result); } -void mgp_path_destroy(mgp_path *path) { delete_mgp_object(path); } +void mgp_map_destroy(mgp_map *map) { DeleteRawMgpObject(map); } + +mgp_error mgp_map_insert(mgp_map *map, const char *key, const mgp_value *value) { + return WrapExceptions([&] { + auto emplace_result = map->items.emplace(key, *value); + if (!emplace_result.second) { + throw KeyAlreadyExistsException{"Map already contains mapping for {}", key}; + } + }); +} + +mgp_error mgp_map_size(const mgp_map *map, size_t *result) { + static_assert(noexcept(map->items.size())); + *result = map->items.size(); + return MGP_ERROR_NO_ERROR; +} + +mgp_error mgp_map_at(const mgp_map *map, const char *key, const mgp_value **result) { + return WrapExceptions( + [&map, &key]() -> const mgp_value * { + auto found_it = map->items.find(key); + if (found_it == map->items.end()) { + return nullptr; + }; + return &found_it->second; + }, + result); +} + +mgp_error mgp_map_item_key(mgp_map_item *item, const char **result) { + return WrapExceptions([&item] { return item->key; }, result); +} + +mgp_error mgp_map_item_value(const mgp_map_item *item, const mgp_value **result) { + return WrapExceptions([item] { return item->value; }, result); +} + +mgp_error mgp_map_iter_items(mgp_map *map, mgp_memory *memory, mgp_map_items_iterator **result) { + return WrapExceptions([map, memory] { return NewRawMgpObject(memory, map); }, result); +} + +void mgp_map_items_iterator_destroy(mgp_map_items_iterator *it) { DeleteRawMgpObject(it); } + +mgp_error mgp_map_items_iterator_get(const mgp_map_items_iterator *it, const mgp_map_item **result) { + return WrapExceptions( + [it]() -> const mgp_map_item * { + if (it->current_it == it->map->items.end()) { + return nullptr; + }; + return &it->current; + }, + result); +} + +mgp_error mgp_map_items_iterator_next(mgp_map_items_iterator *it, const mgp_map_item **result) { + return WrapExceptions( + [it]() -> const mgp_map_item * { + if (it->current_it == it->map->items.end()) { + return nullptr; + } + if (++it->current_it == it->map->items.end()) { + return nullptr; + } + it->current.key = it->current_it->first.c_str(); + it->current.value = &it->current_it->second; + return &it->current; + }, + result); +} + +mgp_error mgp_path_make_with_start(const mgp_vertex *vertex, mgp_memory *memory, mgp_path **result) { + return WrapExceptions( + [vertex, memory]() -> mgp_path * { + auto path = NewMgpObject(memory); + if (path == nullptr) { + return nullptr; + } + path->vertices.push_back(*vertex); + return path.release(); + }, + result); +} + +mgp_error mgp_path_copy(const mgp_path *path, mgp_memory *memory, mgp_path **result) { + return WrapExceptions( + [path, memory] { + MG_ASSERT(Call(mgp_path_size, path) == path->vertices.size() - 1, "Invalid mgp_path"); + return NewRawMgpObject(memory, *path); + }, + result); +} + +void mgp_path_destroy(mgp_path *path) { DeleteRawMgpObject(path); } + +mgp_error mgp_path_expand(mgp_path *path, const mgp_edge *edge) { + return WrapExceptions([path, edge] { + MG_ASSERT(Call(mgp_path_size, path) == path->vertices.size() - 1, "Invalid mgp_path"); + // Check that the both the last vertex on path and dst_vertex are endpoints of + // the given edge. + const auto *src_vertex = &path->vertices.back(); + const mgp_vertex *dst_vertex = nullptr; + if (CallBool(mgp_vertex_equal, Call(mgp_edge_get_to, edge), src_vertex) != 0) { + dst_vertex = Call(mgp_edge_get_from, edge); + } else if (CallBool(mgp_vertex_equal, Call(mgp_edge_get_from, edge), src_vertex)) { + dst_vertex = Call(mgp_edge_get_to, edge); + } else { + // edge is not a continuation on src_vertex + throw std::logic_error{"The current last vertex in the path is not part of the given edge."}; + } + // Try appending edge and dst_vertex to path, preserving the original mgp_path + // instance if anything fails. + utils::OnScopeExit scope_guard( + [path] { MG_ASSERT(Call(mgp_path_size, path) == path->vertices.size() - 1); }); -int mgp_path_expand(mgp_path *path, const mgp_edge *edge) { - MG_ASSERT(mgp_path_size(path) == path->vertices.size() - 1, "Invalid mgp_path"); - // Check that the both the last vertex on path and dst_vertex are endpoints of - // the given edge. - const auto *src_vertex = &path->vertices.back(); - const mgp_vertex *dst_vertex = nullptr; - if (mgp_vertex_equal(mgp_edge_get_to(edge), src_vertex)) { - dst_vertex = mgp_edge_get_from(edge); - } else if (mgp_vertex_equal(mgp_edge_get_from(edge), src_vertex)) { - dst_vertex = mgp_edge_get_to(edge); - } else { - // edge is not a continuation on src_vertex - return 0; - } - // Try appending edge and dst_vertex to path, preserving the original mgp_path - // instance if anything fails. - try { path->edges.push_back(*edge); - } catch (...) { - MG_ASSERT(mgp_path_size(path) == path->vertices.size() - 1); - return 0; - } - try { path->vertices.push_back(*dst_vertex); - } catch (...) { - path->edges.pop_back(); - MG_ASSERT(mgp_path_size(path) == path->vertices.size() - 1); - return 0; - } - MG_ASSERT(mgp_path_size(path) == path->vertices.size() - 1); - return 1; + }); } -size_t mgp_path_size(const mgp_path *path) { return path->edges.size(); } +namespace { +size_t MgpPathSize(const mgp_path &path) noexcept { return path.edges.size(); } +} // namespace -const mgp_vertex *mgp_path_vertex_at(const mgp_path *path, size_t i) { - MG_ASSERT(mgp_path_size(path) == path->vertices.size() - 1); - if (i > mgp_path_size(path)) return nullptr; - return &path->vertices[i]; +mgp_error mgp_path_size(const mgp_path *path, size_t *result) { + *result = MgpPathSize(*path); + return MGP_ERROR_NO_ERROR; } -const mgp_edge *mgp_path_edge_at(const mgp_path *path, size_t i) { - MG_ASSERT(mgp_path_size(path) == path->vertices.size() - 1); - if (i >= mgp_path_size(path)) return nullptr; - return &path->edges[i]; +mgp_error mgp_path_vertex_at(const mgp_path *path, size_t i, const mgp_vertex **result) { + return WrapExceptions( + [path, i] { + const auto path_size = Call(mgp_path_size, path); + MG_ASSERT(path_size == path->vertices.size() - 1); + if (i > path_size) { + throw std::out_of_range("Vertex cannot be retrieved, because index exceeds path's size!"); + } + return &path->vertices[i]; + }, + result); } -int mgp_path_equal(const struct mgp_path *p1, const struct mgp_path *p2) { - MG_ASSERT(mgp_path_size(p1) == p1->vertices.size() - 1); - MG_ASSERT(mgp_path_size(p2) == p2->vertices.size() - 1); - if (mgp_path_size(p1) != mgp_path_size(p2)) return 0; - const auto *start1 = mgp_path_vertex_at(p1, 0); - const auto *start2 = mgp_path_vertex_at(p2, 0); - if (!mgp_vertex_equal(start1, start2)) return 0; - for (size_t i = 0; i < mgp_path_size(p1); ++i) { - const auto *e1 = mgp_path_edge_at(p1, i); - const auto *e2 = mgp_path_edge_at(p2, i); - if (!mgp_edge_equal(e1, e2)) return 0; - } - return 1; +mgp_error mgp_path_edge_at(const mgp_path *path, size_t i, const mgp_edge **result) { + return WrapExceptions( + [path, i] { + const auto path_size = Call(mgp_path_size, path); + MG_ASSERT(path_size == path->vertices.size() - 1); + if (i > path_size) { + throw std::out_of_range("Edge cannot be retrieved, because index exceeds path's size!"); + } + return &path->edges[i]; + }, + result); +} + +mgp_error mgp_path_equal(const struct mgp_path *p1, const struct mgp_path *p2, int *result) { + return WrapExceptions( + [p1, p2] { + const auto p1_size = MgpPathSize(*p1); + const auto p2_size = MgpPathSize(*p2); + MG_ASSERT(p1_size == p1->vertices.size() - 1); + MG_ASSERT(p2_size == p2->vertices.size() - 1); + if (p1_size != p2_size) { + return 0; + } + const auto *start1 = Call(mgp_path_vertex_at, p1, 0); + const auto *start2 = Call(mgp_path_vertex_at, p2, 0); + static_assert(noexcept(start1->impl == start2->impl)); + if (*start1 != *start2) { + return 0; + } + for (size_t i = 0; i < p1_size; ++i) { + const auto *e1 = Call(mgp_path_edge_at, p1, i); + const auto *e2 = Call(mgp_path_edge_at, p2, i); + if (*e1 != *e2) { + return 0; + } + } + return 1; + }, + result); } /// Plugin Result -int mgp_result_set_error_msg(mgp_result *res, const char *msg) { - auto *memory = res->rows.get_allocator().GetMemoryResource(); - try { +mgp_error mgp_result_set_error_msg(mgp_result *res, const char *msg) { + return WrapExceptions([=] { + auto *memory = res->rows.get_allocator().GetMemoryResource(); res->error_msg.emplace(msg, memory); - } catch (...) { - return 0; - } - return 1; + }); } -mgp_result_record *mgp_result_new_record(mgp_result *res) { - auto *memory = res->rows.get_allocator().GetMemoryResource(); - MG_ASSERT(res->signature, "Expected to have a valid signature"); - try { - res->rows.push_back( - mgp_result_record{res->signature, utils::pmr::map(memory)}); - } catch (...) { - return nullptr; - } - return &res->rows.back(); +mgp_error mgp_result_new_record(mgp_result *res, mgp_result_record **result) { + return WrapExceptions( + [res] { + auto *memory = res->rows.get_allocator().GetMemoryResource(); + MG_ASSERT(res->signature, "Expected to have a valid signature"); + res->rows.push_back( + mgp_result_record{res->signature, utils::pmr::map(memory)}); + return &res->rows.back(); + }, + result); } -int mgp_result_record_insert(mgp_result_record *record, const char *field_name, const mgp_value *val) { - auto *memory = record->values.get_allocator().GetMemoryResource(); - // Validate field_name & val satisfy the procedure's result signature. - MG_ASSERT(record->signature, "Expected to have a valid signature"); - auto find_it = record->signature->find(field_name); - if (find_it == record->signature->end()) return 0; - const auto *type = find_it->second.first; - if (!type->SatisfiesType(*val)) return 0; - try { +mgp_error mgp_result_record_insert(mgp_result_record *record, const char *field_name, const mgp_value *val) { + return WrapExceptions([=] { + auto *memory = record->values.get_allocator().GetMemoryResource(); + // Validate field_name & val satisfy the procedure's result signature. + MG_ASSERT(record->signature, "Expected to have a valid signature"); + auto find_it = record->signature->find(field_name); + if (find_it == record->signature->end()) { + throw std::out_of_range{fmt::format("The result doesn't have any field named '{}'.", field_name)}; + } + const auto *type = find_it->second.first; + if (!type->SatisfiesType(*val)) { + throw std::logic_error{ + fmt::format("The type of value doesn't satisfies the type '{}'!", type->GetPresentableName())}; + } record->values.emplace(field_name, ToTypedValue(*val, memory)); - } catch (...) { - return 0; - } - return 1; + }); } /// Graph Constructs -void mgp_properties_iterator_destroy(mgp_properties_iterator *it) { delete_mgp_object(it); } +void mgp_properties_iterator_destroy(mgp_properties_iterator *it) { DeleteRawMgpObject(it); } -const mgp_property *mgp_properties_iterator_get(const mgp_properties_iterator *it) { - if (it->current) return &it->property; - return nullptr; +mgp_error mgp_properties_iterator_get(const mgp_properties_iterator *it, const mgp_property **result) { + return WrapExceptions( + [it]() -> const mgp_property * { + if (it->current) { + return &it->property; + }; + return nullptr; + }, + result); } -const mgp_property *mgp_properties_iterator_next(mgp_properties_iterator *it) { +mgp_error mgp_properties_iterator_next(mgp_properties_iterator *it, const mgp_property **result) { // Incrementing the iterator either for on-disk or in-memory // storage, so perhaps the underlying thing can throw. // Both copying TypedValue and/or string from PropertyName may fail to @@ -803,382 +994,400 @@ const mgp_property *mgp_properties_iterator_next(mgp_properties_iterator *it) { // either way return nullptr and leave `it` in undefined state. // Hopefully iterator comparison doesn't throw, but wrap the whole thing in // try ... catch just to be sure. - try { - if (it->current_it == it->pvs.end()) { - MG_ASSERT(!it->current, - "Iteration is already done, so it->current should " - "have been set to std::nullopt"); - return nullptr; - } - if (++it->current_it == it->pvs.end()) { - it->current = std::nullopt; - return nullptr; - } - it->current.emplace( - utils::pmr::string(it->graph->impl->PropertyToName(it->current_it->first), it->GetMemoryResource()), - mgp_value(it->current_it->second, it->GetMemoryResource())); - it->property.name = it->current->first.c_str(); - it->property.value = &it->current->second; - return &it->property; - } catch (...) { - it->current = std::nullopt; - return nullptr; - } -} - -mgp_vertex_id mgp_vertex_get_id(const mgp_vertex *v) { return mgp_vertex_id{.as_int = v->impl.Gid().AsInt()}; } - -mgp_vertex *mgp_vertex_copy(const mgp_vertex *v, mgp_memory *memory) { return new_mgp_object(memory, *v); } - -void mgp_vertex_destroy(mgp_vertex *v) { delete_mgp_object(v); } - -int mgp_vertex_equal(const mgp_vertex *a, const mgp_vertex *b) { return a->impl == b->impl ? 1 : 0; } - -size_t mgp_vertex_labels_count(const mgp_vertex *v) { - auto maybe_labels = v->impl.Labels(v->graph->view); - if (maybe_labels.HasError()) { - switch (maybe_labels.GetError()) { - case storage::Error::DELETED_OBJECT: - case storage::Error::NONEXISTENT_OBJECT: - // Treat deleted/nonexistent vertex as having no labels. - return 0; - case storage::Error::PROPERTIES_DISABLED: - case storage::Error::VERTEX_HAS_EDGES: - case storage::Error::SERIALIZATION_ERROR: - spdlog::error("Unexpected error when getting vertex labels."); - return 0; - } - } - return maybe_labels->size(); -} - -mgp_label mgp_vertex_label_at(const mgp_vertex *v, size_t i) { - // TODO: Maybe it's worth caching this in mgp_vertex. - auto maybe_labels = v->impl.Labels(v->graph->view); - if (maybe_labels.HasError()) { - switch (maybe_labels.GetError()) { - case storage::Error::DELETED_OBJECT: - case storage::Error::NONEXISTENT_OBJECT: - return mgp_label{nullptr}; - case storage::Error::PROPERTIES_DISABLED: - case storage::Error::VERTEX_HAS_EDGES: - case storage::Error::SERIALIZATION_ERROR: - spdlog::error("Unexpected error when getting vertex labels."); - return mgp_label{nullptr}; - } - } - if (i >= maybe_labels->size()) return mgp_label{nullptr}; - const auto &label = (*maybe_labels)[i]; - static_assert(std::is_lvalue_reference_vgraph->impl->LabelToName(label))>, - "Expected LabelToName to return a pointer or reference, so we " - "don't have to take a copy and manage memory."); - const auto &name = v->graph->impl->LabelToName(label); - return mgp_label{name.c_str()}; -} - -int mgp_vertex_has_label_named(const mgp_vertex *v, const char *name) { - storage::LabelId label; - try { - // This will allocate a std::string from `name`, which may throw - // std::bad_alloc or std::length_error. This could be avoided with a - // std::string_view. Although storage API may be updated with - // std::string_view, NameToLabel itself may still throw std::bad_alloc when - // creating a new LabelId mapping and we need to handle that. - label = v->graph->impl->NameToLabel(name); - } catch (...) { - spdlog::error("Unable to allocate a LabelId mapping"); - // If we need to allocate a new mapping, then the vertex does not have such - // a label, so return 0. - return 0; - } - auto maybe_has_label = v->impl.HasLabel(v->graph->view, label); - if (maybe_has_label.HasError()) { - switch (maybe_has_label.GetError()) { - case storage::Error::DELETED_OBJECT: - case storage::Error::NONEXISTENT_OBJECT: - return 0; - case storage::Error::PROPERTIES_DISABLED: - case storage::Error::VERTEX_HAS_EDGES: - case storage::Error::SERIALIZATION_ERROR: - spdlog::error("Unexpected error when checking vertex has label."); - return 0; - } - } - return *maybe_has_label; -} - -int mgp_vertex_has_label(const mgp_vertex *v, mgp_label label) { return mgp_vertex_has_label_named(v, label.name); } - -mgp_value *mgp_vertex_get_property(const mgp_vertex *v, const char *name, mgp_memory *memory) { - try { - const auto &key = v->graph->impl->NameToProperty(name); - auto maybe_prop = v->impl.GetProperty(v->graph->view, key); - if (maybe_prop.HasError()) { - switch (maybe_prop.GetError()) { - case storage::Error::DELETED_OBJECT: - case storage::Error::NONEXISTENT_OBJECT: - // Treat deleted/nonexistent vertex as having no properties. - return new_mgp_object(memory); - case storage::Error::PROPERTIES_DISABLED: - case storage::Error::VERTEX_HAS_EDGES: - case storage::Error::SERIALIZATION_ERROR: - spdlog::error("Unexpected error when getting vertex property"); + return WrapExceptions( + [it]() -> const mgp_property * { + if (it->current_it == it->pvs.end()) { + MG_ASSERT(!it->current, + "Iteration is already done, so it->current should " + "have been set to std::nullopt"); return nullptr; - } - } - return new_mgp_object(memory, std::move(*maybe_prop)); - } catch (...) { - // In case NameToProperty or GetProperty throw an exception, most likely - // std::bad_alloc. - return nullptr; - } + } + if (++it->current_it == it->pvs.end()) { + it->current = std::nullopt; + return nullptr; + } + utils::OnScopeExit clean_up([it] { it->current = std::nullopt; }); + it->current.emplace( + utils::pmr::string(it->graph->impl->PropertyToName(it->current_it->first), it->GetMemoryResource()), + mgp_value(it->current_it->second, it->GetMemoryResource())); + it->property.name = it->current->first.c_str(); + it->property.value = &it->current->second; + clean_up.Disable(); + return &it->property; + }, + result); } -mgp_properties_iterator *mgp_vertex_iter_properties(const mgp_vertex *v, mgp_memory *memory) { +mgp_error mgp_vertex_get_id(const mgp_vertex *v, mgp_vertex_id *result) { + return WrapExceptions([v] { return mgp_vertex_id{.as_int = v->impl.Gid().AsInt()}; }, result); +} + +mgp_error mgp_vertex_copy(const mgp_vertex *v, mgp_memory *memory, mgp_vertex **result) { + return WrapExceptions([v, memory] { return NewRawMgpObject(memory, *v); }, result); +} + +void mgp_vertex_destroy(mgp_vertex *v) { DeleteRawMgpObject(v); } + +mgp_error mgp_vertex_equal(const mgp_vertex *v1, const mgp_vertex *v2, int *result) { + // NOLINTNEXTLINE(clang-diagnostic-unevaluated-expression) + static_assert(noexcept(*result = *v1 == *v2 ? 1 : 0)); + *result = *v1 == *v2 ? 1 : 0; + return MGP_ERROR_NO_ERROR; +} + +mgp_error mgp_vertex_labels_count(const mgp_vertex *v, size_t *result) { + return WrapExceptions( + [v]() -> size_t { + auto maybe_labels = v->impl.Labels(v->graph->view); + if (maybe_labels.HasError()) { + switch (maybe_labels.GetError()) { + case storage::Error::DELETED_OBJECT: + case storage::Error::NONEXISTENT_OBJECT: + throw NonexistentObjectException{"Cannot get the labels of nonexistent vertex"}; + case storage::Error::PROPERTIES_DISABLED: + case storage::Error::VERTEX_HAS_EDGES: + case storage::Error::SERIALIZATION_ERROR: + MG_ASSERT(false, "Unexpected error when getting vertex labels."); + } + } + return maybe_labels->size(); + }, + result); +} + +mgp_error mgp_vertex_label_at(const mgp_vertex *v, size_t i, mgp_label *result) { + return WrapExceptions( + [v, i]() -> const char * { + // TODO: Maybe it's worth caching this in mgp_vertex. + auto maybe_labels = v->impl.Labels(v->graph->view); + if (maybe_labels.HasError()) { + switch (maybe_labels.GetError()) { + case storage::Error::DELETED_OBJECT: + case storage::Error::NONEXISTENT_OBJECT: + throw NonexistentObjectException{"Cannot get a label of nonexistent vertex"}; + case storage::Error::PROPERTIES_DISABLED: + case storage::Error::VERTEX_HAS_EDGES: + case storage::Error::SERIALIZATION_ERROR: + MG_ASSERT(false, "Unexpected error when getting a label of a vertex."); + } + } + if (i >= maybe_labels->size()) { + throw std::out_of_range("Label cannot be retrieved, because index exceeds the number of labels!"); + } + const auto &label = (*maybe_labels)[i]; + static_assert(std::is_lvalue_reference_vgraph->impl->LabelToName(label))>, + "Expected LabelToName to return a pointer or reference, so we " + "don't have to take a copy and manage memory."); + const auto &name = v->graph->impl->LabelToName(label); + return name.c_str(); + }, + &result->name); +} + +mgp_error mgp_vertex_has_label_named(const mgp_vertex *v, const char *name, int *result) { + return WrapExceptions( + [v, name] { + storage::LabelId label; + label = v->graph->impl->NameToLabel(name); + + auto maybe_has_label = v->impl.HasLabel(v->graph->view, label); + if (maybe_has_label.HasError()) { + switch (maybe_has_label.GetError()) { + case storage::Error::DELETED_OBJECT: + case storage::Error::NONEXISTENT_OBJECT: + throw NonexistentObjectException{"Cannot check the existence of a label on nonexistent vertex"}; + case storage::Error::PROPERTIES_DISABLED: + case storage::Error::VERTEX_HAS_EDGES: + case storage::Error::SERIALIZATION_ERROR: + MG_ASSERT(false, "Unexpected error when checking the existence of a label on a vertex."); + } + } + return *maybe_has_label; + }, + result); +} + +mgp_error mgp_vertex_has_label(const mgp_vertex *v, mgp_label label, int *result) { + return mgp_vertex_has_label_named(v, label.name, result); +} + +mgp_error mgp_vertex_get_property(const mgp_vertex *v, const char *name, mgp_memory *memory, mgp_value **result) { + return WrapExceptions( + [v, name, memory]() -> mgp_value * { + const auto &key = v->graph->impl->NameToProperty(name); + auto maybe_prop = v->impl.GetProperty(v->graph->view, key); + if (maybe_prop.HasError()) { + switch (maybe_prop.GetError()) { + case storage::Error::DELETED_OBJECT: + case storage::Error::NONEXISTENT_OBJECT: + throw NonexistentObjectException{"Cannot get a property of nonexistent vertex"}; + case storage::Error::PROPERTIES_DISABLED: + case storage::Error::VERTEX_HAS_EDGES: + case storage::Error::SERIALIZATION_ERROR: + MG_ASSERT(false, "Unexpected error when getting a property of a vertex."); + } + } + return NewRawMgpObject(memory, std::move(*maybe_prop)); + }, + result); +} + +mgp_error mgp_vertex_iter_properties(const mgp_vertex *v, mgp_memory *memory, mgp_properties_iterator **result) { // NOTE: This copies the whole properties into the iterator. // TODO: Think of a good way to avoid the copy which doesn't just rely on some // assumption that storage may return a pointer to the property store. This // will probably require a different API in storage. - try { - auto maybe_props = v->impl.Properties(v->graph->view); - if (maybe_props.HasError()) { - switch (maybe_props.GetError()) { - case storage::Error::DELETED_OBJECT: - case storage::Error::NONEXISTENT_OBJECT: - // Treat deleted/nonexistent vertex as having no properties. - return new_mgp_object(memory, v->graph); - case storage::Error::PROPERTIES_DISABLED: - case storage::Error::VERTEX_HAS_EDGES: - case storage::Error::SERIALIZATION_ERROR: - spdlog::error("Unexpected error when getting vertex properties"); - return nullptr; - } - } - return new_mgp_object(memory, v->graph, std::move(*maybe_props)); - } catch (...) { - // Since we are copying stuff, we may get std::bad_alloc. Hopefully, no - // other exceptions are possible, but catch them all just in case. - return nullptr; - } + return WrapExceptions( + [v, memory] { + auto maybe_props = v->impl.Properties(v->graph->view); + if (maybe_props.HasError()) { + switch (maybe_props.GetError()) { + case storage::Error::DELETED_OBJECT: + case storage::Error::NONEXISTENT_OBJECT: + throw NonexistentObjectException{"Cannot get the properties of nonexistent vertex"}; + case storage::Error::PROPERTIES_DISABLED: + case storage::Error::VERTEX_HAS_EDGES: + case storage::Error::SERIALIZATION_ERROR: + MG_ASSERT(false, "Unexpected error when getting the properties of a vertex."); + } + } + return NewRawMgpObject(memory, v->graph, std::move(*maybe_props)); + }, + result); } -void mgp_edges_iterator_destroy(mgp_edges_iterator *it) { delete_mgp_object(it); } +void mgp_edges_iterator_destroy(mgp_edges_iterator *it) { DeleteRawMgpObject(it); } -mgp_edges_iterator *mgp_vertex_iter_in_edges(const mgp_vertex *v, mgp_memory *memory) { - auto *it = new_mgp_object(memory, *v); - if (!it) return nullptr; - try { - auto maybe_edges = v->impl.InEdges(v->graph->view); - if (maybe_edges.HasError()) { - switch (maybe_edges.GetError()) { - case storage::Error::DELETED_OBJECT: - case storage::Error::NONEXISTENT_OBJECT: - // Treat deleted/nonexistent vertex as having no edges. - return it; - case storage::Error::PROPERTIES_DISABLED: - case storage::Error::VERTEX_HAS_EDGES: - case storage::Error::SERIALIZATION_ERROR: - spdlog::error("Unexpected error when getting in edges"); - mgp_edges_iterator_destroy(it); - return nullptr; - } - } - it->in.emplace(std::move(*maybe_edges)); - it->in_it.emplace(it->in->begin()); - if (*it->in_it != it->in->end()) { - it->current_e.emplace(**it->in_it, v->graph, it->GetMemoryResource()); - } - } catch (...) { - // We are probably copying edges, and that may throw std::bad_alloc. - mgp_edges_iterator_destroy(it); - return nullptr; - } - return it; +mgp_error mgp_vertex_iter_in_edges(const mgp_vertex *v, mgp_memory *memory, mgp_edges_iterator **result) { + return WrapExceptions( + [v, memory] { + auto it = NewMgpObject(memory, *v); + MG_ASSERT(it != nullptr); + + auto maybe_edges = v->impl.InEdges(v->graph->view); + if (maybe_edges.HasError()) { + switch (maybe_edges.GetError()) { + case storage::Error::DELETED_OBJECT: + case storage::Error::NONEXISTENT_OBJECT: + throw NonexistentObjectException{"Cannot get the inbound edges of nonexistent vertex"}; + case storage::Error::PROPERTIES_DISABLED: + case storage::Error::VERTEX_HAS_EDGES: + case storage::Error::SERIALIZATION_ERROR: + MG_ASSERT(false, "Unexpected error when getting the inbound edges of a vertex."); + } + } + it->in.emplace(std::move(*maybe_edges)); + it->in_it.emplace(it->in->begin()); + if (*it->in_it != it->in->end()) { + it->current_e.emplace(**it->in_it, v->graph, it->GetMemoryResource()); + } + + return it.release(); + }, + result); } -mgp_edges_iterator *mgp_vertex_iter_out_edges(const mgp_vertex *v, mgp_memory *memory) { - auto *it = new_mgp_object(memory, *v); - if (!it) return nullptr; - try { - auto maybe_edges = v->impl.OutEdges(v->graph->view); - if (maybe_edges.HasError()) { - switch (maybe_edges.GetError()) { - case storage::Error::DELETED_OBJECT: - case storage::Error::NONEXISTENT_OBJECT: - // Treat deleted/nonexistent vertex as having no edges. - return it; - case storage::Error::PROPERTIES_DISABLED: - case storage::Error::VERTEX_HAS_EDGES: - case storage::Error::SERIALIZATION_ERROR: - spdlog::error("Unexpected error when getting out edges"); - mgp_edges_iterator_destroy(it); - return nullptr; - } - } - it->out.emplace(std::move(*maybe_edges)); - it->out_it.emplace(it->out->begin()); - if (*it->out_it != it->out->end()) { - it->current_e.emplace(**it->out_it, v->graph, it->GetMemoryResource()); - } - } catch (...) { - // We are probably copying edges, and that may throw std::bad_alloc. - mgp_edges_iterator_destroy(it); - return nullptr; - } - return it; +mgp_error mgp_vertex_iter_out_edges(const mgp_vertex *v, mgp_memory *memory, mgp_edges_iterator **result) { + return WrapExceptions( + [v, memory] { + auto it = NewMgpObject(memory, *v); + MG_ASSERT(it != nullptr); + + auto maybe_edges = v->impl.OutEdges(v->graph->view); + if (maybe_edges.HasError()) { + switch (maybe_edges.GetError()) { + case storage::Error::DELETED_OBJECT: + case storage::Error::NONEXISTENT_OBJECT: + throw NonexistentObjectException{"Cannot get the outbound edges of nonexistent vertex"}; + case storage::Error::PROPERTIES_DISABLED: + case storage::Error::VERTEX_HAS_EDGES: + case storage::Error::SERIALIZATION_ERROR: + MG_ASSERT(false, "Unexpected error when getting the outbound edges of a vertex."); + } + } + it->out.emplace(std::move(*maybe_edges)); + it->out_it.emplace(it->out->begin()); + if (*it->out_it != it->out->end()) { + it->current_e.emplace(**it->out_it, v->graph, it->GetMemoryResource()); + } + + return it.release(); + }, + result); } -const mgp_edge *mgp_edges_iterator_get(const mgp_edges_iterator *it) { - if (it->current_e) return &*it->current_e; - return nullptr; +mgp_error mgp_edges_iterator_get(const mgp_edges_iterator *it, const mgp_edge **result) { + return WrapExceptions( + [it]() -> const mgp_edge * { + if (it->current_e.has_value()) { + return &*it->current_e; + } + return nullptr; + }, + result); } -const mgp_edge *mgp_edges_iterator_next(mgp_edges_iterator *it) { - if (!it->in && !it->out) return nullptr; - auto next = [&](auto *impl_it, const auto &end) -> const mgp_edge * { - if (*impl_it == end) { - MG_ASSERT(!it->current_e, - "Iteration is already done, so it->current_e " - "should have been set to std::nullopt"); - return nullptr; - } - if (++(*impl_it) == end) { - it->current_e = std::nullopt; - return nullptr; - } - it->current_e.emplace(**impl_it, it->source_vertex.graph, it->GetMemoryResource()); - return &*it->current_e; - }; - try { - if (it->in_it) { - return next(&*it->in_it, it->in->end()); - } else { - return next(&*it->out_it, it->out->end()); - } - } catch (...) { - // Just to be sure that operator++ or anything else has thrown something. - it->current_e = std::nullopt; - return nullptr; - } +mgp_error mgp_edges_iterator_next(mgp_edges_iterator *it, const mgp_edge **result) { + return WrapExceptions( + [it] { + MG_ASSERT(it->in || it->out); + auto next = [&](auto *impl_it, const auto &end) -> const mgp_edge * { + if (*impl_it == end) { + MG_ASSERT(!it->current_e, + "Iteration is already done, so it->current_e " + "should have been set to std::nullopt"); + return nullptr; + } + if (++(*impl_it) == end) { + it->current_e = std::nullopt; + return nullptr; + } + it->current_e.emplace(**impl_it, it->source_vertex.graph, it->GetMemoryResource()); + return &*it->current_e; + }; + if (it->in_it) { + return next(&*it->in_it, it->in->end()); + } + return next(&*it->out_it, it->out->end()); + }, + result); } -mgp_edge_id mgp_edge_get_id(const mgp_edge *e) { return mgp_edge_id{.as_int = e->impl.Gid().AsInt()}; } - -mgp_edge *mgp_edge_copy(const mgp_edge *v, mgp_memory *memory) { - return new_mgp_object(memory, v->impl, v->from.graph); +mgp_error mgp_edge_get_id(const mgp_edge *e, mgp_edge_id *result) { + return WrapExceptions([e] { return mgp_edge_id{.as_int = e->impl.Gid().AsInt()}; }, result); } -void mgp_edge_destroy(mgp_edge *e) { delete_mgp_object(e); } - -int mgp_edge_equal(const struct mgp_edge *e1, const struct mgp_edge *e2) { return e1->impl == e2->impl ? 1 : 0; } - -mgp_edge_type mgp_edge_get_type(const mgp_edge *e) { - const auto &name = e->from.graph->impl->EdgeTypeToName(e->impl.EdgeType()); - static_assert(std::is_lvalue_reference_vfrom.graph->impl->EdgeTypeToName(e->impl.EdgeType()))>, - "Expected EdgeTypeToName to return a pointer or reference, so we " - "don't have to take a copy and manage memory."); - return mgp_edge_type{name.c_str()}; +mgp_error mgp_edge_copy(const mgp_edge *e, mgp_memory *memory, mgp_edge **result) { + return WrapExceptions([e, memory] { return NewRawMgpObject(memory, e->impl, e->from.graph); }, result); } -const mgp_vertex *mgp_edge_get_from(const mgp_edge *e) { return &e->from; } +void mgp_edge_destroy(mgp_edge *e) { DeleteRawMgpObject(e); } -const mgp_vertex *mgp_edge_get_to(const mgp_edge *e) { return &e->to; } - -mgp_value *mgp_edge_get_property(const mgp_edge *e, const char *name, mgp_memory *memory) { - try { - const auto &key = e->from.graph->impl->NameToProperty(name); - auto view = e->from.graph->view; - auto maybe_prop = e->impl.GetProperty(view, key); - if (maybe_prop.HasError()) { - switch (maybe_prop.GetError()) { - case storage::Error::DELETED_OBJECT: - case storage::Error::NONEXISTENT_OBJECT: - // Treat deleted/nonexistent edge as having no properties. - return new_mgp_object(memory); - case storage::Error::PROPERTIES_DISABLED: - case storage::Error::VERTEX_HAS_EDGES: - case storage::Error::SERIALIZATION_ERROR: - spdlog::error("Unexpected error when getting edge property"); - return nullptr; - } - } - return new_mgp_object(memory, std::move(*maybe_prop)); - } catch (...) { - // In case NameToProperty or GetProperty throw an exception, most likely - // std::bad_alloc. - return nullptr; - } +mgp_error mgp_edge_equal(const struct mgp_edge *e1, const struct mgp_edge *e2, int *result) { + // NOLINTNEXTLINE(clang-diagnostic-unevaluated-expression) + static_assert(noexcept(*result = *e1 == *e2 ? 1 : 0)); + *result = *e1 == *e2 ? 1 : 0; + return MGP_ERROR_NO_ERROR; } -mgp_properties_iterator *mgp_edge_iter_properties(const mgp_edge *e, mgp_memory *memory) { +mgp_error mgp_edge_get_type(const mgp_edge *e, mgp_edge_type *result) { + return WrapExceptions( + [e] { + const auto &name = e->from.graph->impl->EdgeTypeToName(e->impl.EdgeType()); + static_assert(std::is_lvalue_reference_vfrom.graph->impl->EdgeTypeToName(e->impl.EdgeType()))>, + "Expected EdgeTypeToName to return a pointer or reference, so we " + "don't have to take a copy and manage memory."); + return name.c_str(); + }, + &result->name); +} + +mgp_error mgp_edge_get_from(const mgp_edge *e, const mgp_vertex **result) { + *result = &e->from; + return MGP_ERROR_NO_ERROR; +} + +mgp_error mgp_edge_get_to(const mgp_edge *e, const mgp_vertex **result) { + *result = &e->to; + return MGP_ERROR_NO_ERROR; +} + +mgp_error mgp_edge_get_property(const mgp_edge *e, const char *name, mgp_memory *memory, mgp_value **result) { + return WrapExceptions( + [e, name, memory] { + const auto &key = e->from.graph->impl->NameToProperty(name); + auto view = e->from.graph->view; + auto maybe_prop = e->impl.GetProperty(view, key); + if (maybe_prop.HasError()) { + switch (maybe_prop.GetError()) { + case storage::Error::DELETED_OBJECT: + case storage::Error::NONEXISTENT_OBJECT: + throw NonexistentObjectException{"Cannot get a property of nonexistent edge"}; + case storage::Error::PROPERTIES_DISABLED: + case storage::Error::VERTEX_HAS_EDGES: + case storage::Error::SERIALIZATION_ERROR: + MG_ASSERT(false, "Unexpected error when getting a property of an edge."); + } + } + return NewRawMgpObject(memory, std::move(*maybe_prop)); + }, + result); +} + +mgp_error mgp_edge_iter_properties(const mgp_edge *e, mgp_memory *memory, mgp_properties_iterator **result) { // NOTE: This copies the whole properties into iterator. // TODO: Think of a good way to avoid the copy which doesn't just rely on some // assumption that storage may return a pointer to the property store. This // will probably require a different API in storage. - try { - auto view = e->from.graph->view; - auto maybe_props = e->impl.Properties(view); - if (maybe_props.HasError()) { - switch (maybe_props.GetError()) { - case storage::Error::DELETED_OBJECT: - case storage::Error::NONEXISTENT_OBJECT: - // Treat deleted/nonexistent edge as having no properties. - return new_mgp_object(memory, e->from.graph); - case storage::Error::PROPERTIES_DISABLED: - case storage::Error::VERTEX_HAS_EDGES: - case storage::Error::SERIALIZATION_ERROR: - spdlog::error("Unexpected error when getting edge properties"); + return WrapExceptions( + [e, memory] { + auto view = e->from.graph->view; + auto maybe_props = e->impl.Properties(view); + if (maybe_props.HasError()) { + switch (maybe_props.GetError()) { + case storage::Error::DELETED_OBJECT: + case storage::Error::NONEXISTENT_OBJECT: + throw NonexistentObjectException{"Cannot get the properties of nonexistent edge"}; + case storage::Error::PROPERTIES_DISABLED: + case storage::Error::VERTEX_HAS_EDGES: + case storage::Error::SERIALIZATION_ERROR: + MG_ASSERT(false, "Unexpected error when getting the properties of an edge."); + } + } + return NewRawMgpObject(memory, e->from.graph, std::move(*maybe_props)); + }, + result); +} + +mgp_error mgp_graph_get_vertex_by_id(const mgp_graph *graph, mgp_vertex_id id, mgp_memory *memory, + mgp_vertex **result) { + return WrapExceptions( + [graph, id, memory]() -> mgp_vertex * { + auto maybe_vertex = graph->impl->FindVertex(storage::Gid::FromInt(id.as_int), graph->view); + if (maybe_vertex) { + return NewRawMgpObject(memory, *maybe_vertex, graph); + } + return nullptr; + }, + result); +} + +void mgp_vertices_iterator_destroy(mgp_vertices_iterator *it) { DeleteRawMgpObject(it); } + +mgp_error mgp_graph_iter_vertices(const mgp_graph *graph, mgp_memory *memory, mgp_vertices_iterator **result) { + return WrapExceptions([graph, memory] { return NewRawMgpObject(memory, graph); }, result); +} + +mgp_error mgp_vertices_iterator_get(const mgp_vertices_iterator *it, const mgp_vertex **result) { + return WrapExceptions( + [it]() -> const mgp_vertex * { + if (it->current_v.has_value()) { + return &*it->current_v; + } + return nullptr; + }, + result); +} + +mgp_error mgp_vertices_iterator_next(mgp_vertices_iterator *it, const mgp_vertex **result) { + return WrapExceptions( + [it]() -> const mgp_vertex * { + if (it->current_it == it->vertices.end()) { + MG_ASSERT(!it->current_v, + "Iteration is already done, so it->current_v " + "should have been set to std::nullopt"); return nullptr; - } - } - return new_mgp_object(memory, e->from.graph, std::move(*maybe_props)); - } catch (...) { - // Since we are copying stuff, we may get std::bad_alloc. Hopefully, no - // other exceptions are possible, but catch them all just in case. - return nullptr; - } -} - -mgp_vertex *mgp_graph_get_vertex_by_id(const mgp_graph *graph, mgp_vertex_id id, mgp_memory *memory) { - auto maybe_vertex = graph->impl->FindVertex(storage::Gid::FromInt(id.as_int), graph->view); - if (maybe_vertex) return new_mgp_object(memory, *maybe_vertex, graph); - return nullptr; -} - -void mgp_vertices_iterator_destroy(mgp_vertices_iterator *it) { delete_mgp_object(it); } - -mgp_vertices_iterator *mgp_graph_iter_vertices(const mgp_graph *graph, mgp_memory *memory) { - try { - return new_mgp_object(memory, graph); - } catch (...) { - return nullptr; - } -} - -const mgp_vertex *mgp_vertices_iterator_get(const mgp_vertices_iterator *it) { - if (it->current_v) return &*it->current_v; - return nullptr; -} - -const mgp_vertex *mgp_vertices_iterator_next(mgp_vertices_iterator *it) { - try { - if (it->current_it == it->vertices.end()) { - MG_ASSERT(!it->current_v, - "Iteration is already done, so it->current_v " - "should have been set to std::nullopt"); - return nullptr; - } - if (++it->current_it == it->vertices.end()) { - it->current_v = std::nullopt; - return nullptr; - } - it->current_v.emplace(*it->current_it, it->graph, it->GetMemoryResource()); - return &*it->current_v; - } catch (...) { - // VerticesIterable::Iterator::operator++ may throw - it->current_v = std::nullopt; - return nullptr; - } + } + if (++it->current_it == it->vertices.end()) { + it->current_v = std::nullopt; + return nullptr; + } + utils::OnScopeExit clean_up([it] { it->current_v = std::nullopt; }); + it->current_v.emplace(*it->current_it, it->graph, it->GetMemoryResource()); + clean_up.Disable(); + return &*it->current_v; + }, + result); } /// Type System @@ -1187,160 +1396,133 @@ const mgp_vertex *mgp_vertices_iterator_next(mgp_vertices_iterator *it) { /// allocations done for types. namespace { -void NoOpCypherTypeDeleter(CypherType *) {} +void NoOpCypherTypeDeleter(CypherType * /*type*/) {} } // namespace -const mgp_type *mgp_type_any() { - static AnyType impl; - static mgp_type any_type{CypherTypePtr(&impl, NoOpCypherTypeDeleter)}; - return &any_type; -} - -const mgp_type *mgp_type_bool() { - static BoolType impl; - static mgp_type bool_type{CypherTypePtr(&impl, NoOpCypherTypeDeleter)}; - return &bool_type; -} - -const mgp_type *mgp_type_string() { - static StringType impl; - static mgp_type string_type{CypherTypePtr(&impl, NoOpCypherTypeDeleter)}; - return &string_type; -} - -const mgp_type *mgp_type_int() { - static IntType impl; - static mgp_type int_type{CypherTypePtr(&impl, NoOpCypherTypeDeleter)}; - return &int_type; -} - -const mgp_type *mgp_type_float() { - static FloatType impl; - static mgp_type float_type{CypherTypePtr(&impl, NoOpCypherTypeDeleter)}; - return &float_type; -} - -const mgp_type *mgp_type_number() { - static NumberType impl; - static mgp_type number_type{CypherTypePtr(&impl, NoOpCypherTypeDeleter)}; - return &number_type; -} - -const mgp_type *mgp_type_map() { - static MapType impl; - static mgp_type map_type{CypherTypePtr(&impl, NoOpCypherTypeDeleter)}; - return &map_type; -} - -const mgp_type *mgp_type_node() { - static NodeType impl; - static mgp_type node_type{CypherTypePtr(&impl, NoOpCypherTypeDeleter)}; - return &node_type; -} - -const mgp_type *mgp_type_relationship() { - static RelationshipType impl; - static mgp_type relationship_type{CypherTypePtr(&impl, NoOpCypherTypeDeleter)}; - return &relationship_type; -} - -const mgp_type *mgp_type_path() { - static PathType impl; - static mgp_type path_type{CypherTypePtr(&impl, NoOpCypherTypeDeleter)}; - return &path_type; -} - -const mgp_type *mgp_type_list(const mgp_type *type) { - if (!type) return nullptr; - // Maps `type` to corresponding instance of ListType. - static utils::pmr::map list_types(utils::NewDeleteResource()); - static utils::SpinLock lock; - std::lock_guard guard(lock); - auto found_it = list_types.find(type); - if (found_it != list_types.end()) return &found_it->second; - try { - auto alloc = list_types.get_allocator(); - CypherTypePtr impl( - alloc.new_object( - // Just obtain the pointer to original impl, don't own it. - CypherTypePtr(type->impl.get(), NoOpCypherTypeDeleter), alloc.GetMemoryResource()), - [alloc](CypherType *base_ptr) mutable { alloc.delete_object(static_cast(base_ptr)); }); - return &list_types.emplace(type, mgp_type{std::move(impl)}).first->second; - } catch (const std::bad_alloc &) { - return nullptr; +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define DEFINE_MGP_TYPE_GETTER(cypher_type_name, mgp_type_name) \ + mgp_error mgp_type_##mgp_type_name(const mgp_type **result) { \ + return WrapExceptions( \ + [] { \ + static cypher_type_name##Type impl; \ + static mgp_type mgp_type_name_type{CypherTypePtr(&impl, NoOpCypherTypeDeleter)}; \ + return &mgp_type_name_type; \ + }, \ + result); \ } + +DEFINE_MGP_TYPE_GETTER(Any, any); +DEFINE_MGP_TYPE_GETTER(Bool, bool); +DEFINE_MGP_TYPE_GETTER(String, string); +DEFINE_MGP_TYPE_GETTER(Int, int); +DEFINE_MGP_TYPE_GETTER(Float, float); +DEFINE_MGP_TYPE_GETTER(Number, number); +DEFINE_MGP_TYPE_GETTER(Map, map); +DEFINE_MGP_TYPE_GETTER(Node, node); +DEFINE_MGP_TYPE_GETTER(Relationship, relationship); +DEFINE_MGP_TYPE_GETTER(Path, path); + +mgp_error mgp_type_list(const mgp_type *type, const mgp_type **result) { + return WrapExceptions( + [type] { + // Maps `type` to corresponding instance of ListType. + static utils::pmr::map gListTypes(utils::NewDeleteResource()); + static utils::SpinLock lock; + std::lock_guard guard(lock); + auto found_it = gListTypes.find(type); + if (found_it != gListTypes.end()) { + return &found_it->second; + } + auto alloc = gListTypes.get_allocator(); + CypherTypePtr impl( + alloc.new_object( + // Just obtain the pointer to original impl, don't own it. + CypherTypePtr(type->impl.get(), NoOpCypherTypeDeleter), alloc.GetMemoryResource()), + [alloc](CypherType *base_ptr) mutable { alloc.delete_object(static_cast(base_ptr)); }); + return &gListTypes.emplace(type, mgp_type{std::move(impl)}).first->second; + }, + result); } -const mgp_type *mgp_type_nullable(const mgp_type *type) { - if (!type) return nullptr; - // Maps `type` to corresponding instance of NullableType. - static utils::pmr::map gNullableTypes(utils::NewDeleteResource()); - static utils::SpinLock lock; - std::lock_guard guard(lock); - auto found_it = gNullableTypes.find(type); - if (found_it != gNullableTypes.end()) return &found_it->second; - try { - auto alloc = gNullableTypes.get_allocator(); - auto impl = NullableType::Create(CypherTypePtr(type->impl.get(), NoOpCypherTypeDeleter), alloc.GetMemoryResource()); - return &gNullableTypes.emplace(type, mgp_type{std::move(impl)}).first->second; - } catch (const std::bad_alloc &) { - return nullptr; - } +mgp_error mgp_type_nullable(const mgp_type *type, const mgp_type **result) { + return WrapExceptions( + [type] { + // Maps `type` to corresponding instance of NullableType. + static utils::pmr::map gNullableTypes(utils::NewDeleteResource()); + static utils::SpinLock lock; + std::lock_guard guard(lock); + auto found_it = gNullableTypes.find(type); + if (found_it != gNullableTypes.end()) return &found_it->second; + + auto alloc = gNullableTypes.get_allocator(); + auto impl = + NullableType::Create(CypherTypePtr(type->impl.get(), NoOpCypherTypeDeleter), alloc.GetMemoryResource()); + return &gNullableTypes.emplace(type, mgp_type{std::move(impl)}).first->second; + }, + result); } -mgp_proc *mgp_module_add_read_procedure(mgp_module *module, const char *name, mgp_proc_cb cb) { - if (!module || !cb) return nullptr; - if (!IsValidIdentifierName(name)) return nullptr; - if (module->procedures.find(name) != module->procedures.end()) return nullptr; - try { - auto *memory = module->procedures.get_allocator().GetMemoryResource(); - // May throw std::bad_alloc, std::length_error - return &module->procedures.emplace(name, mgp_proc(name, cb, memory)).first->second; - } catch (...) { - return nullptr; - } +mgp_error mgp_module_add_read_procedure(mgp_module *module, const char *name, mgp_proc_cb cb, mgp_proc **result) { + return WrapExceptions( + [module, name, cb]() -> mgp_proc * { + if (!IsValidIdentifierName(name)) { + throw std::invalid_argument{fmt::format("Invalid procedure name: {}", name)}; + } + if (module->procedures.find(name) != module->procedures.end()) { + throw std::logic_error{fmt::format("Procedure already exists with name '{}'", name)}; + }; + + auto *memory = module->procedures.get_allocator().GetMemoryResource(); + // May throw std::bad_alloc, std::length_error + return &module->procedures.emplace(name, mgp_proc(name, cb, memory)).first->second; + }, + result); } -int mgp_proc_add_arg(mgp_proc *proc, const char *name, const mgp_type *type) { - if (!proc || !type) return 0; - if (!proc->opt_args.empty()) return 0; - if (!IsValidIdentifierName(name)) return 0; - try { +mgp_error mgp_proc_add_arg(mgp_proc *proc, const char *name, const mgp_type *type) { + return WrapExceptions([=] { + if (!IsValidIdentifierName(name)) { + throw std::invalid_argument{fmt::format("Invalid argument name for procedure '{}': {}", proc->name, name)}; + } + if (!proc->opt_args.empty()) { + throw std::logic_error{fmt::format( + "Cannot add required argument '{}' to procedure '{}' after adding any optional one", name, proc->name)}; + } proc->args.emplace_back(name, type->impl.get()); - return 1; - } catch (...) { - return 0; - } + }); } -int mgp_proc_add_opt_arg(mgp_proc *proc, const char *name, const mgp_type *type, const mgp_value *default_value) { - if (!proc || !type || !default_value) return 0; - if (!IsValidIdentifierName(name)) return 0; - switch (mgp_value_get_type(default_value)) { - case MGP_VALUE_TYPE_VERTEX: - case MGP_VALUE_TYPE_EDGE: - case MGP_VALUE_TYPE_PATH: - // default_value must not be a graph element. - return 0; - case MGP_VALUE_TYPE_NULL: - case MGP_VALUE_TYPE_BOOL: - case MGP_VALUE_TYPE_INT: - case MGP_VALUE_TYPE_DOUBLE: - case MGP_VALUE_TYPE_STRING: - case MGP_VALUE_TYPE_LIST: - case MGP_VALUE_TYPE_MAP: - break; - } - // Default value must be of required `type`. - if (!type->impl->SatisfiesType(*default_value)) return 0; - auto *memory = proc->opt_args.get_allocator().GetMemoryResource(); - try { +mgp_error mgp_proc_add_opt_arg(mgp_proc *proc, const char *name, const mgp_type *type, const mgp_value *default_value) { + return WrapExceptions([=] { + if (!IsValidIdentifierName(name)) { + throw std::invalid_argument{fmt::format("Invalid argument name for procedure '{}': {}", proc->name, name)}; + } + switch (MgpValueGetType(*default_value)) { + case MGP_VALUE_TYPE_VERTEX: + case MGP_VALUE_TYPE_EDGE: + case MGP_VALUE_TYPE_PATH: + // default_value must not be a graph element. + throw std::out_of_range{fmt::format( + "Default value of argument '{}' of procedure '{}' name must not be a graph element!", name, proc->name)}; + case MGP_VALUE_TYPE_NULL: + case MGP_VALUE_TYPE_BOOL: + case MGP_VALUE_TYPE_INT: + case MGP_VALUE_TYPE_DOUBLE: + case MGP_VALUE_TYPE_STRING: + case MGP_VALUE_TYPE_LIST: + case MGP_VALUE_TYPE_MAP: + break; + } + // Default value must be of required `type`. + if (!type->impl->SatisfiesType(*default_value)) { + throw std::logic_error{ + fmt::format("The default value of argument '{}' for procedure '{}' doesn't satisfy type '{}'", name, + proc->name, type->impl->GetPresentableName())}; + } + auto *memory = proc->opt_args.get_allocator().GetMemoryResource(); proc->opt_args.emplace_back(utils::pmr::string(name, memory), type->impl.get(), ToTypedValue(*default_value, memory)); - return 1; - } catch (...) { - return 0; - } + }); } namespace { @@ -1349,39 +1531,42 @@ template concept ModuleProperties = utils::SameAsAnyOf; template -bool AddResultToProp(T *prop, const char *name, const mgp_type *type, bool is_deprecated) { - if (!prop || !type) return false; - if (!IsValidIdentifierName(name)) return false; - if (prop->results.find(name) != prop->results.end()) return false; - try { +mgp_error AddResultToProp(T *prop, const char *name, const mgp_type *type, bool is_deprecated) noexcept { + return WrapExceptions([=] { + if (!IsValidIdentifierName(name)) { + throw std::invalid_argument{fmt::format("Invalid result name for procedure '{}': {}", prop->name, name)}; + } + if (prop->results.find(name) != prop->results.end()) { + throw std::logic_error{fmt::format("Result already exists with name '{}' for procedure '{}'", name, prop->name)}; + }; auto *memory = prop->results.get_allocator().GetMemoryResource(); prop->results.emplace(utils::pmr::string(name, memory), std::make_pair(type->impl.get(), is_deprecated)); - return true; - } catch (...) { - return false; - } + }); } } // namespace -int mgp_proc_add_result(mgp_proc *proc, const char *name, const mgp_type *type) { +mgp_error mgp_proc_add_result(mgp_proc *proc, const char *name, const mgp_type *type) { return AddResultToProp(proc, name, type, false); } -bool MgpTransAddFixedResult(mgp_trans *trans) { - if (int err = AddResultToProp(trans, "query", mgp_type_string(), false); err != 1) { +mgp_error MgpTransAddFixedResult(mgp_trans *trans) noexcept { + if (const auto err = AddResultToProp(trans, "query", Call(mgp_type_string), false); + err != MGP_ERROR_NO_ERROR) { return err; } - return AddResultToProp(trans, "parameters", mgp_type_nullable(mgp_type_map()), false); + return AddResultToProp(trans, "parameters", + Call(mgp_type_nullable, Call(mgp_type_map)), false); } -int mgp_proc_add_deprecated_result(mgp_proc *proc, const char *name, const mgp_type *type) { +mgp_error mgp_proc_add_deprecated_result(mgp_proc *proc, const char *name, const mgp_type *type) { return AddResultToProp(proc, name, type, true); } int mgp_must_abort(const mgp_graph *graph) { MG_ASSERT(graph->ctx); - return query::MustAbort(*graph->ctx); + static_assert(noexcept(query::MustAbort(*graph->ctx))); + return query::MustAbort(*graph->ctx) ? 1 : 0; } namespace query::procedure { @@ -1453,34 +1638,56 @@ bool IsValidIdentifierName(const char *name) { } // namespace query::procedure -const char *mgp_message_payload(const mgp_message *message) { return message->msg->Payload().data(); } - -size_t mgp_message_payload_size(const mgp_message *message) { return message->msg->Payload().size(); } - -const char *mgp_message_topic_name(const mgp_message *message) { return message->msg->TopicName().data(); } - -const char *mgp_message_key(const mgp_message *message) { return message->msg->Key().data(); } - -size_t mgp_message_key_size(const struct mgp_message *message) { return message->msg->Key().size(); } - -int64_t mgp_message_timestamp(const mgp_message *message) { return message->msg->Timestamp(); } - -size_t mgp_messages_size(const mgp_messages *messages) { return messages->messages.size(); } - -const mgp_message *mgp_messages_at(const mgp_messages *messages, size_t index) { - return index >= mgp_messages_size(messages) ? nullptr : &messages->messages[index]; +mgp_error mgp_message_payload(const mgp_message *message, const char **result) { + return WrapExceptions([message] { return message->msg->Payload().data(); }, result); } -int mgp_module_add_transformation(mgp_module *module, const char *name, mgp_trans_cb cb) { - if (!module || !cb) return 0; - if (!IsValidIdentifierName(name)) return 0; - if (module->transformations.find(name) != module->transformations.end()) return 0; - try { +mgp_error mgp_message_payload_size(const mgp_message *message, size_t *result) { + return WrapExceptions([message] { return message->msg->Payload().size(); }, result); +} + +mgp_error mgp_message_topic_name(const mgp_message *message, const char **result) { + return WrapExceptions([message] { return message->msg->TopicName().data(); }, result); +} + +mgp_error mgp_message_key(const mgp_message *message, const char **result) { + return WrapExceptions([message] { return message->msg->Key().data(); }, result); +} + +mgp_error mgp_message_key_size(const struct mgp_message *message, size_t *result) { + return WrapExceptions([message] { return message->msg->Key().size(); }, result); +} + +mgp_error mgp_message_timestamp(const mgp_message *message, int64_t *result) { + return WrapExceptions([message] { return message->msg->Timestamp(); }, result); +} + +mgp_error mgp_messages_size(const mgp_messages *messages, size_t *result) { + static_assert(noexcept(messages->messages.size())); + *result = messages->messages.size(); + return MGP_ERROR_NO_ERROR; +} + +mgp_error mgp_messages_at(const mgp_messages *messages, size_t index, const mgp_message **result) { + return WrapExceptions( + [messages, index] { + if (index >= Call(mgp_messages_size, messages)) { + throw std::out_of_range("Message cannot be retrieved, because index exceeds messages' size!"); + } + return &messages->messages[index]; + }, + result); +} + +mgp_error mgp_module_add_transformation(mgp_module *module, const char *name, mgp_trans_cb cb) { + return WrapExceptions([=] { + if (!IsValidIdentifierName(name)) { + throw std::invalid_argument{fmt::format("Invalid transformation name: {}", name)}; + } + if (module->transformations.find(name) != module->transformations.end()) { + throw std::logic_error{fmt::format("Transformation already exists with name '{}'", name)}; + }; auto *memory = module->transformations.get_allocator().GetMemoryResource(); - // May throw std::bad_alloc, std::length_error module->transformations.emplace(name, mgp_trans(name, cb, memory)); - return 1; - } catch (...) { - return 0; - } + }); } diff --git a/src/query/procedure/mg_procedure_impl.hpp b/src/query/procedure/mg_procedure_impl.hpp index 313d581e3..8ea1f7fbd 100644 --- a/src/query/procedure/mg_procedure_impl.hpp +++ b/src/query/procedure/mg_procedure_impl.hpp @@ -221,6 +221,9 @@ struct mgp_vertex { mgp_vertex &operator=(const mgp_vertex &) = delete; mgp_vertex &operator=(mgp_vertex &&) = delete; + bool operator==(const mgp_vertex &other) const noexcept { return this->impl == other.impl; } + bool operator!=(const mgp_vertex &other) const noexcept { return !(*this == other); }; + ~mgp_vertex() = default; utils::MemoryResource *GetMemoryResource() const noexcept { return memory; } @@ -257,9 +260,11 @@ struct mgp_edge { mgp_edge &operator=(const mgp_edge &) = delete; mgp_edge &operator=(mgp_edge &&) = delete; - ~mgp_edge() = default; + bool operator==(const mgp_edge &other) const noexcept { return this->impl == other.impl; } + bool operator!=(const mgp_edge &other) const noexcept { return !(*this == other); }; + utils::MemoryResource *GetMemoryResource() const noexcept { return memory; } utils::MemoryResource *memory; @@ -512,7 +517,7 @@ struct mgp_trans { utils::pmr::map> results; }; -bool MgpTransAddFixedResult(mgp_trans *trans); +mgp_error MgpTransAddFixedResult(mgp_trans *trans) noexcept; struct mgp_module { using allocator_type = utils::Allocator; diff --git a/src/query/procedure/module.cpp b/src/query/procedure/module.cpp index 4d4d291f0..d692157ae 100644 --- a/src/query/procedure/module.cpp +++ b/src/query/procedure/module.cpp @@ -91,30 +91,40 @@ void RegisterMgLoad(ModuleRegistry *module_registry, utils::RWLock *lock, Builti } lock->lock_shared(); }; - auto load_all_cb = [module_registry, with_unlock_shared](const mgp_list *, const mgp_graph *, mgp_result *, - mgp_memory *) { + auto load_all_cb = [module_registry, with_unlock_shared](const mgp_list * /*args*/, const mgp_graph * /*graph*/, + mgp_result * /*result*/, mgp_memory * /*memory*/) { with_unlock_shared([&]() { module_registry->UnloadAndLoadModulesFromDirectories(); }); }; mgp_proc load_all("load_all", load_all_cb, utils::NewDeleteResource()); module->AddProcedure("load_all", std::move(load_all)); - auto load_cb = [module_registry, with_unlock_shared](const mgp_list *args, const mgp_graph *, mgp_result *res, - mgp_memory *) { - MG_ASSERT(mgp_list_size(args) == 1U, "Should have been type checked already"); - const mgp_value *arg = mgp_list_at(args, 0); - MG_ASSERT(mgp_value_is_string(arg), "Should have been type checked already"); + auto load_cb = [module_registry, with_unlock_shared](const mgp_list *args, const mgp_graph * /*graph*/, + mgp_result *result, mgp_memory * /*memory*/) { + MG_ASSERT(Call(mgp_list_size, args) == 1U, "Should have been type checked already"); + const auto *arg = Call(mgp_list_at, args, 0); + MG_ASSERT(CallBool(mgp_value_is_string, arg), "Should have been type checked already"); bool succ = false; - with_unlock_shared([&]() { succ = module_registry->LoadOrReloadModuleFromName(mgp_value_get_string(arg)); }); - if (!succ) mgp_result_set_error_msg(res, "Failed to (re)load the module."); + with_unlock_shared([&]() { + const char *arg_as_string{nullptr}; + if (const auto err = mgp_value_get_string(arg, &arg_as_string); err != MGP_ERROR_NO_ERROR) { + succ = false; + } else { + succ = module_registry->LoadOrReloadModuleFromName(arg_as_string); + } + }); + if (!succ) { + MG_ASSERT(mgp_result_set_error_msg(result, "Failed to (re)load the module.") == MGP_ERROR_NO_ERROR); + } }; mgp_proc load("load", load_cb, utils::NewDeleteResource()); - mgp_proc_add_arg(&load, "module_name", mgp_type_string()); + MG_ASSERT(mgp_proc_add_arg(&load, "module_name", Call(mgp_type_string)) == MGP_ERROR_NO_ERROR); module->AddProcedure("load", std::move(load)); } void RegisterMgProcedures( // We expect modules to be sorted by name. const std::map, std::less<>> *all_modules, BuiltinModule *module) { - auto procedures_cb = [all_modules](const mgp_list *, const mgp_graph *, mgp_result *result, mgp_memory *memory) { + auto procedures_cb = [all_modules](const mgp_list * /*args*/, const mgp_graph * /*graph*/, mgp_result *result, + mgp_memory *memory) { // Iterating over all_modules assumes that the standard mechanism of custom // procedure invocations takes the ModuleRegistry::lock_ with READ access. // For details on how the invocation is done, take a look at the @@ -125,43 +135,53 @@ void RegisterMgProcedures( std::is_same_vProcedures()), const std::map> *>, "Expected module procedures to be sorted by name"); for (const auto &[proc_name, proc] : *module->Procedures()) { - auto *record = mgp_result_new_record(result); - if (!record) { - mgp_result_set_error_msg(result, "Not enough memory!"); + mgp_result_record *record{nullptr}; + if (const auto err = mgp_result_new_record(result, &record); err == MGP_ERROR_UNABLE_TO_ALLOCATE) { + static_cast(mgp_result_set_error_msg(result, "Not enough memory!")); + return; + } else if (err != MGP_ERROR_NO_ERROR) { + static_cast(mgp_result_set_error_msg(result, "Unexpected error")); return; } + utils::pmr::string full_name(module_name, memory->impl); full_name.append(1, '.'); full_name.append(proc_name); - auto *name_value = mgp_value_make_string(full_name.c_str(), memory); - if (!name_value) { - mgp_result_set_error_msg(result, "Not enough memory!"); + MgpUniquePtr name_value{nullptr, mgp_value_destroy}; + if (const auto err = CreateMgpObject(name_value, mgp_value_make_string, full_name.c_str(), memory); + err == MGP_ERROR_UNABLE_TO_ALLOCATE) { + static_cast(mgp_result_set_error_msg(result, "Not enough memory!")); + return; + } else if (err != MGP_ERROR_NO_ERROR) { + static_cast(mgp_result_set_error_msg(result, "Unexpected error")); return; } std::stringstream ss; ss << module_name << "."; PrintProcSignature(proc, &ss); const auto signature = ss.str(); - auto *signature_value = mgp_value_make_string(signature.c_str(), memory); - if (!signature_value) { - mgp_value_destroy(name_value); - mgp_result_set_error_msg(result, "Not enough memory!"); + MgpUniquePtr signature_value{nullptr, mgp_value_destroy}; + if (const auto err = CreateMgpObject(signature_value, mgp_value_make_string, full_name.c_str(), memory); + err == MGP_ERROR_UNABLE_TO_ALLOCATE) { + static_cast(mgp_result_set_error_msg(result, "Not enough memory!")); + return; + } else if (err != MGP_ERROR_NO_ERROR) { + static_cast(mgp_result_set_error_msg(result, "Unexpected error")); return; } - int succ1 = mgp_result_record_insert(record, "name", name_value); - int succ2 = mgp_result_record_insert(record, "signature", signature_value); - mgp_value_destroy(name_value); - mgp_value_destroy(signature_value); - if (!succ1 || !succ2) { - mgp_result_set_error_msg(result, "Unable to set the result!"); + 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) { + static_cast(mgp_result_set_error_msg(result, "Unable to set the result!")); return; } } } }; mgp_proc procedures("procedures", procedures_cb, utils::NewDeleteResource()); - mgp_proc_add_result(&procedures, "name", mgp_type_string()); - mgp_proc_add_result(&procedures, "signature", mgp_type_string()); + MG_ASSERT(mgp_proc_add_result(&procedures, "name", Call(mgp_type_string)) == MGP_ERROR_NO_ERROR); + MG_ASSERT(mgp_proc_add_result(&procedures, "signature", Call(mgp_type_string)) == + MGP_ERROR_NO_ERROR); module->AddProcedure("procedures", std::move(procedures)); } @@ -175,32 +195,41 @@ void RegisterMgTransformations(const std::mapTransformations()), const std::map> *>, "Expected module transformations to be sorted by name"); for (const auto &[trans_name, proc] : *module->Transformations()) { - auto *record = mgp_result_new_record(result); - if (!record) { - mgp_result_set_error_msg(result, "Not enough memory!"); + mgp_result_record *record{nullptr}; + if (const auto err = mgp_result_new_record(result, &record); err == MGP_ERROR_UNABLE_TO_ALLOCATE) { + static_cast(mgp_result_set_error_msg(result, "Not enough memory!")); + return; + } else if (err != MGP_ERROR_NO_ERROR) { + static_cast(mgp_result_set_error_msg(result, "Unexpected error")); return; } + utils::pmr::string full_name(module_name, memory->impl); full_name.append(1, '.'); full_name.append(trans_name); - auto *name_value = mgp_value_make_string(full_name.c_str(), memory); - if (!name_value) { - mgp_result_set_error_msg(result, "Not enough memory!"); + + MgpUniquePtr name_value{nullptr, mgp_value_destroy}; + if (const auto err = CreateMgpObject(name_value, mgp_value_make_string, full_name.c_str(), memory); + err == MGP_ERROR_UNABLE_TO_ALLOCATE) { + static_cast(mgp_result_set_error_msg(result, "Not enough memory!")); + return; + } else if (err != MGP_ERROR_NO_ERROR) { + static_cast(mgp_result_set_error_msg(result, "Unexpected error")); return; } - int succ = mgp_result_record_insert(record, "name", name_value); - mgp_value_destroy(name_value); - if (!succ) { - mgp_result_set_error_msg(result, "Unable to set the result!"); + + if (const auto err = mgp_result_record_insert(record, "name", name_value.get()); err != MGP_ERROR_NO_ERROR) { + static_cast(mgp_result_set_error_msg(result, "Unable to set the result!")); return; } } } }; mgp_proc procedures("transformations", procedures_cb, utils::NewDeleteResource()); - mgp_proc_add_result(&procedures, "name", mgp_type_string()); + MG_ASSERT(mgp_proc_add_result(&procedures, "name", Call(mgp_type_string)) == MGP_ERROR_NO_ERROR); module->AddProcedure("transformations", std::move(procedures)); } + // Run `fun` with `mgp_module *` and `mgp_memory *` arguments. If `fun` returned // a `true` value, store the `mgp_module::procedures` and // `mgp_module::transformations into `proc_map`. The return value of WithModuleRegistration @@ -392,11 +421,13 @@ bool PythonModule::Load(const std::filesystem::path &file_path) { return false; } bool succ = true; - auto module_cb = [&](auto *module_def, auto *memory) { + auto module_cb = [&](auto *module_def, auto * /*memory*/) { auto result = ImportPyModule(file_path.stem().c_str(), module_def); for (auto &trans : module_def->transformations) { - succ = MgpTransAddFixedResult(&trans.second); - if (!succ) return result; + succ = MgpTransAddFixedResult(&trans.second) == MGP_ERROR_NO_ERROR; + if (!succ) { + return result; + } }; return result; }; @@ -578,7 +609,7 @@ void ModuleRegistry::UnloadAllModules() { DoUnloadAllModules(); } -utils::MemoryResource &ModuleRegistry::GetSharedMemoryResource() { return *shared_; } +utils::MemoryResource &ModuleRegistry::GetSharedMemoryResource() noexcept { return *shared_; } namespace { diff --git a/src/query/procedure/module.hpp b/src/query/procedure/module.hpp index 1d05ac160..1542fcda0 100644 --- a/src/query/procedure/module.hpp +++ b/src/query/procedure/module.hpp @@ -100,7 +100,7 @@ class ModuleRegistry final { void UnloadAllModules(); /// Returns the shared memory allocator used by modules - utils::MemoryResource &GetSharedMemoryResource(); + utils::MemoryResource &GetSharedMemoryResource() noexcept; private: std::vector modules_dirs_; diff --git a/src/query/procedure/py_module.cpp b/src/query/procedure/py_module.cpp index 03b4c089f..0562e0083 100644 --- a/src/query/procedure/py_module.cpp +++ b/src/query/procedure/py_module.cpp @@ -4,6 +4,7 @@ #include #include +#include "query/procedure/mg_procedure_helpers.hpp" #include "query/procedure/mg_procedure_impl.hpp" #include "utils/pmr/vector.hpp" @@ -70,7 +71,7 @@ PyObject *PyVerticesIteratorGet(PyVerticesIterator *self, PyObject *Py_UNUSED(ig MG_ASSERT(self->it); MG_ASSERT(self->py_graph); MG_ASSERT(self->py_graph->graph); - const auto *vertex = mgp_vertices_iterator_get(self->it); + const auto *vertex = Call(mgp_vertices_iterator_get, self->it); if (!vertex) Py_RETURN_NONE; return MakePyVertex(*vertex, self->py_graph); } @@ -79,7 +80,7 @@ PyObject *PyVerticesIteratorNext(PyVerticesIterator *self, PyObject *Py_UNUSED(i MG_ASSERT(self->it); MG_ASSERT(self->py_graph); MG_ASSERT(self->py_graph->graph); - const auto *vertex = mgp_vertices_iterator_next(self->it); + const auto *vertex = Call(mgp_vertices_iterator_next, self->it); if (!vertex) Py_RETURN_NONE; return MakePyVertex(*vertex, self->py_graph); } @@ -130,7 +131,7 @@ PyObject *PyEdgesIteratorGet(PyEdgesIterator *self, PyObject *Py_UNUSED(ignored) MG_ASSERT(self->it); MG_ASSERT(self->py_graph); MG_ASSERT(self->py_graph->graph); - const auto *edge = mgp_edges_iterator_get(self->it); + const auto *edge = Call(mgp_edges_iterator_get, self->it); if (!edge) Py_RETURN_NONE; return MakePyEdge(*edge, self->py_graph); } @@ -139,7 +140,7 @@ PyObject *PyEdgesIteratorNext(PyEdgesIterator *self, PyObject *Py_UNUSED(ignored MG_ASSERT(self->it); MG_ASSERT(self->py_graph); MG_ASSERT(self->py_graph->graph); - const auto *edge = mgp_edges_iterator_next(self->it); + const auto *edge = Call(mgp_edges_iterator_next, self->it); if (!edge) Py_RETURN_NONE; return MakePyEdge(*edge, self->py_graph); } @@ -181,7 +182,7 @@ PyObject *PyGraphGetVertexById(PyGraph *self, PyObject *args) { static_assert(std::is_same_v); int64_t id = 0; if (!PyArg_ParseTuple(args, "l", &id)) return nullptr; - auto *vertex = mgp_graph_get_vertex_by_id(self->graph, mgp_vertex_id{id}, self->memory); + auto *vertex = Call(mgp_graph_get_vertex_by_id, self->graph, mgp_vertex_id{id}, self->memory); if (!vertex) { PyErr_SetString(PyExc_IndexError, "Unable to find the vertex with given ID."); return nullptr; @@ -194,7 +195,7 @@ PyObject *PyGraphGetVertexById(PyGraph *self, PyObject *args) { PyObject *PyGraphIterVertices(PyGraph *self, PyObject *Py_UNUSED(ignored)) { MG_ASSERT(self->graph); MG_ASSERT(self->memory); - auto *vertices_it = mgp_graph_iter_vertices(self->graph, self->memory); + auto *vertices_it = Call(mgp_graph_iter_vertices, self->graph, self->memory); if (!vertices_it) { PyErr_SetString(PyExc_MemoryError, "Unable to allocate mgp_vertices_iterator."); return nullptr; @@ -291,7 +292,7 @@ PyObject *PyQueryProcAddArg(PyQueryProc *self, PyObject *args) { return nullptr; } const auto *type = reinterpret_cast(py_type)->type; - if (!mgp_proc_add_arg(self->proc, name, type)) { + if (mgp_proc_add_arg(self->proc, name, type) != MGP_ERROR_NO_ERROR) { PyErr_SetString(PyExc_ValueError, "Invalid call to mgp_proc_add_arg."); return nullptr; } @@ -310,7 +311,7 @@ PyObject *PyQueryProcAddOptArg(PyQueryProc *self, PyObject *args) { } const auto *type = reinterpret_cast(py_type)->type; mgp_memory memory{self->proc->opt_args.get_allocator().GetMemoryResource()}; - mgp_value *value; + mgp_value *value{nullptr}; try { value = PyObjectToMgpValue(py_value, &memory); } catch (const std::bad_alloc &e) { @@ -327,7 +328,7 @@ PyObject *PyQueryProcAddOptArg(PyQueryProc *self, PyObject *args) { return nullptr; } MG_ASSERT(value); - if (!mgp_proc_add_opt_arg(self->proc, name, type, value)) { + if (mgp_proc_add_opt_arg(self->proc, name, type, value) != MGP_ERROR_NO_ERROR) { mgp_value_destroy(value); PyErr_SetString(PyExc_ValueError, "Invalid call to mgp_proc_add_opt_arg."); return nullptr; @@ -346,7 +347,7 @@ PyObject *PyQueryProcAddResult(PyQueryProc *self, PyObject *args) { return nullptr; } const auto *type = reinterpret_cast(py_type)->type; - if (!mgp_proc_add_result(self->proc, name, type)) { + if (mgp_proc_add_result(self->proc, name, type) != MGP_ERROR_NO_ERROR) { PyErr_SetString(PyExc_ValueError, "Invalid call to mgp_proc_add_result."); return nullptr; } @@ -363,7 +364,7 @@ PyObject *PyQueryProcAddDeprecatedResult(PyQueryProc *self, PyObject *args) { return nullptr; } const auto *type = reinterpret_cast(py_type)->type; - if (!mgp_proc_add_deprecated_result(self->proc, name, type)) { + if (const auto err = mgp_proc_add_deprecated_result(self->proc, name, type); err != MGP_ERROR_NO_ERROR) { PyErr_SetString(PyExc_ValueError, "Invalid call to mgp_proc_add_deprecated_result."); return nullptr; } @@ -424,8 +425,8 @@ PyObject *PyMessageIsValid(PyMessage *self, PyObject *Py_UNUSED(ignored)) { PyObject *PyMessageGetPayload(PyMessage *self, PyObject *Py_UNUSED(ignored)) { MG_ASSERT(self->message); - auto payload_size = mgp_message_payload_size(self->message); - const auto *payload = mgp_message_payload(self->message); + auto payload_size = Call(mgp_message_payload_size, self->message); + const auto *payload = Call(mgp_message_payload, self->message); auto *raw_bytes = PyByteArray_FromStringAndSize(payload, payload_size); if (!raw_bytes) { PyErr_SetString(PyExc_RuntimeError, "Unable to get raw bytes from payload"); @@ -437,7 +438,7 @@ PyObject *PyMessageGetPayload(PyMessage *self, PyObject *Py_UNUSED(ignored)) { PyObject *PyMessageGetTopicName(PyMessage *self, PyObject *Py_UNUSED(ignored)) { MG_ASSERT(self->message); MG_ASSERT(self->memory); - const auto *topic_name = mgp_message_topic_name(self->message); + const auto *topic_name = Call(mgp_message_topic_name, self->message); auto *py_topic_name = PyUnicode_FromString(topic_name); if (!py_topic_name) { PyErr_SetString(PyExc_RuntimeError, "Unable to get raw bytes from payload"); @@ -449,8 +450,8 @@ PyObject *PyMessageGetTopicName(PyMessage *self, PyObject *Py_UNUSED(ignored)) { PyObject *PyMessageGetKey(PyMessage *self, PyObject *Py_UNUSED(ignored)) { MG_ASSERT(self->message); MG_ASSERT(self->memory); - auto key_size = mgp_message_key_size(self->message); - const auto *key = mgp_message_key(self->message); + auto key_size = Call(mgp_message_key_size, self->message); + const auto *key = Call(mgp_message_key, self->message); auto *raw_bytes = PyByteArray_FromStringAndSize(key, key_size); if (!raw_bytes) { PyErr_SetString(PyExc_RuntimeError, "Unable to get raw bytes from payload"); @@ -462,7 +463,7 @@ PyObject *PyMessageGetKey(PyMessage *self, PyObject *Py_UNUSED(ignored)) { PyObject *PyMessageGetTimestamp(PyMessage *self, PyObject *Py_UNUSED(ignored)) { MG_ASSERT(self->message); MG_ASSERT(self->memory); - auto timestamp = mgp_message_timestamp(self->message); + auto timestamp = Call(mgp_message_timestamp, self->message); auto *py_int = PyLong_FromUnsignedLong(timestamp); if (!py_int) { PyErr_SetString(PyExc_IndexError, "Unable to get timestamp."); @@ -529,7 +530,7 @@ PyObject *PyMessagesGetMessageAt(PyMessages *self, PyObject *args) { int64_t id = 0; if (!PyArg_ParseTuple(args, "l", &id)) return nullptr; if (id < 0) return nullptr; - const auto *message = mgp_messages_at(self->messages, id); + const auto *message = Call(mgp_messages_at, self->messages, id); // NOLINTNEXTLINE auto *py_message = PyObject_New(PyMessage, &PyMessageType); if (!py_message) { @@ -586,11 +587,11 @@ PyObject *MakePyMessages(const mgp_messages *msgs, mgp_memory *memory) { py::Object MgpListToPyTuple(const mgp_list *list, PyGraph *py_graph) { MG_ASSERT(list); MG_ASSERT(py_graph); - const size_t len = mgp_list_size(list); + const auto len = Call(mgp_list_size, list); py::Object py_tuple(PyTuple_New(len)); if (!py_tuple) return nullptr; for (size_t i = 0; i < len; ++i) { - auto elem = MgpValueToPyObject(*mgp_list_at(list, i), py_graph); + auto elem = MgpValueToPyObject(*Call(mgp_list_at, list, i), py_graph); if (!elem) return nullptr; // Explicitly convert `py_tuple`, which is `py::Object`, via static_cast. // Then the macro will cast it to `PyTuple *`. @@ -629,7 +630,7 @@ std::optional AddRecordFromPython(mgp_result *result, py::Obj } py::Object items(PyDict_Items(fields.Ptr())); if (!items) return py::FetchError(); - auto *record = mgp_result_new_record(result); + auto *record = Call(mgp_result_new_record, result); if (!record) { PyErr_NoMemory(); return py::FetchError(); @@ -662,7 +663,7 @@ std::optional AddRecordFromPython(mgp_result *result, py::Obj return py::FetchError(); } MG_ASSERT(field_val); - if (!mgp_result_record_insert(record, field_name, field_val)) { + if (mgp_result_record_insert(record, field_name, field_val) != MGP_ERROR_NO_ERROR) { std::stringstream ss; ss << "Unable to insert field '" << py::Object::FromBorrow(key) << "' with value: '" << py::Object::FromBorrow(val) << "'; did you set the correct field type?"; @@ -764,7 +765,7 @@ void CallPythonProcedure(const py::Object &py_cb, const mgp_list *args, const mg } if (maybe_msg) { - mgp_result_set_error_msg(result, maybe_msg->c_str()); + static_cast(mgp_result_set_error_msg(result, maybe_msg->c_str())); } } @@ -845,7 +846,7 @@ void CallPythonTransformation(const py::Object &py_cb, const mgp_messages *msgs, } if (maybe_msg) { - mgp_result_set_error_msg(result, maybe_msg->c_str()); + static_cast(mgp_result_set_error_msg(result, maybe_msg->c_str())); } } } // namespace @@ -945,7 +946,7 @@ PyObject *PyMgpModuleTypeNullable(PyObject *mod, PyObject *obj) { return nullptr; } auto *py_type = reinterpret_cast(obj); - return MakePyCypherType(mgp_type_nullable(py_type->type)); + return MakePyCypherType(Call(mgp_type_nullable, py_type->type)); } PyObject *PyMgpModuleTypeList(PyObject *mod, PyObject *obj) { @@ -954,36 +955,48 @@ PyObject *PyMgpModuleTypeList(PyObject *mod, PyObject *obj) { return nullptr; } auto *py_type = reinterpret_cast(obj); - return MakePyCypherType(mgp_type_list(py_type->type)); + return MakePyCypherType(Call(mgp_type_list, py_type->type)); } -PyObject *PyMgpModuleTypeAny(PyObject *mod, PyObject *Py_UNUSED(ignored)) { return MakePyCypherType(mgp_type_any()); } - -PyObject *PyMgpModuleTypeBool(PyObject *mod, PyObject *Py_UNUSED(ignored)) { return MakePyCypherType(mgp_type_bool()); } - -PyObject *PyMgpModuleTypeString(PyObject *mod, PyObject *Py_UNUSED(ignored)) { - return MakePyCypherType(mgp_type_string()); +PyObject *PyMgpModuleTypeAny(PyObject * /*mod*/, PyObject *Py_UNUSED(ignored)) { + return MakePyCypherType(Call(mgp_type_any)); } -PyObject *PyMgpModuleTypeInt(PyObject *mod, PyObject *Py_UNUSED(ignored)) { return MakePyCypherType(mgp_type_int()); } - -PyObject *PyMgpModuleTypeFloat(PyObject *mod, PyObject *Py_UNUSED(ignored)) { - return MakePyCypherType(mgp_type_float()); +PyObject *PyMgpModuleTypeBool(PyObject * /*mod*/, PyObject *Py_UNUSED(ignored)) { + return MakePyCypherType(Call(mgp_type_bool)); } -PyObject *PyMgpModuleTypeNumber(PyObject *mod, PyObject *Py_UNUSED(ignored)) { - return MakePyCypherType(mgp_type_number()); +PyObject *PyMgpModuleTypeString(PyObject * /*mod*/, PyObject *Py_UNUSED(ignored)) { + return MakePyCypherType(Call(mgp_type_string)); } -PyObject *PyMgpModuleTypeMap(PyObject *mod, PyObject *Py_UNUSED(ignored)) { return MakePyCypherType(mgp_type_map()); } - -PyObject *PyMgpModuleTypeNode(PyObject *mod, PyObject *Py_UNUSED(ignored)) { return MakePyCypherType(mgp_type_node()); } - -PyObject *PyMgpModuleTypeRelationship(PyObject *mod, PyObject *Py_UNUSED(ignored)) { - return MakePyCypherType(mgp_type_relationship()); +PyObject *PyMgpModuleTypeInt(PyObject * /*mod*/, PyObject *Py_UNUSED(ignored)) { + return MakePyCypherType(Call(mgp_type_int)); } -PyObject *PyMgpModuleTypePath(PyObject *mod, PyObject *Py_UNUSED(ignored)) { return MakePyCypherType(mgp_type_path()); } +PyObject *PyMgpModuleTypeFloat(PyObject * /*mod*/, PyObject *Py_UNUSED(ignored)) { + return MakePyCypherType(Call(mgp_type_float)); +} + +PyObject *PyMgpModuleTypeNumber(PyObject * /*mod*/, PyObject *Py_UNUSED(ignored)) { + return MakePyCypherType(Call(mgp_type_number)); +} + +PyObject *PyMgpModuleTypeMap(PyObject * /*mod*/, PyObject *Py_UNUSED(ignored)) { + return MakePyCypherType(Call(mgp_type_map)); +} + +PyObject *PyMgpModuleTypeNode(PyObject * /*mod*/, PyObject *Py_UNUSED(ignored)) { + return MakePyCypherType(Call(mgp_type_node)); +} + +PyObject *PyMgpModuleTypeRelationship(PyObject * /*mod*/, PyObject *Py_UNUSED(ignored)) { + return MakePyCypherType(Call(mgp_type_relationship)); +} + +PyObject *PyMgpModuleTypePath(PyObject * /*mod*/, PyObject *Py_UNUSED(ignored)) { + return MakePyCypherType(Call(mgp_type_path)); +} static PyMethodDef PyMgpModuleMethods[] = { {"type_nullable", PyMgpModuleTypeNullable, METH_O, @@ -1038,7 +1051,7 @@ PyObject *PyPropertiesIteratorGet(PyPropertiesIterator *self, PyObject *Py_UNUSE MG_ASSERT(self->it); MG_ASSERT(self->py_graph); MG_ASSERT(self->py_graph->graph); - const auto *property = mgp_properties_iterator_get(self->it); + const auto *property = Call(mgp_properties_iterator_get, self->it); if (!property) Py_RETURN_NONE; py::Object py_name(PyUnicode_FromString(property->name)); if (!py_name) return nullptr; @@ -1051,7 +1064,7 @@ PyObject *PyPropertiesIteratorNext(PyPropertiesIterator *self, PyObject *Py_UNUS MG_ASSERT(self->it); MG_ASSERT(self->py_graph); MG_ASSERT(self->py_graph->graph); - const auto *property = mgp_properties_iterator_next(self->it); + const auto *property = Call(mgp_properties_iterator_next, self->it); if (!property) Py_RETURN_NONE; py::Object py_name(PyUnicode_FromString(property->name)); if (!py_name) return nullptr; @@ -1093,7 +1106,7 @@ PyObject *PyEdgeGetTypeName(PyEdge *self, PyObject *Py_UNUSED(ignored)) { MG_ASSERT(self->edge); MG_ASSERT(self->py_graph); MG_ASSERT(self->py_graph->graph); - return PyUnicode_FromString(mgp_edge_get_type(self->edge).name); + return PyUnicode_FromString(Call(mgp_edge_get_type, self->edge).name); } PyObject *PyEdgeFromVertex(PyEdge *self, PyObject *Py_UNUSED(ignored)) { @@ -1101,7 +1114,7 @@ PyObject *PyEdgeFromVertex(PyEdge *self, PyObject *Py_UNUSED(ignored)) { MG_ASSERT(self->edge); MG_ASSERT(self->py_graph); MG_ASSERT(self->py_graph->graph); - const auto *vertex = mgp_edge_get_from(self->edge); + const auto *vertex = Call(mgp_edge_get_from, self->edge); MG_ASSERT(vertex); return MakePyVertex(*vertex, self->py_graph); } @@ -1111,7 +1124,7 @@ PyObject *PyEdgeToVertex(PyEdge *self, PyObject *Py_UNUSED(ignored)) { MG_ASSERT(self->edge); MG_ASSERT(self->py_graph); MG_ASSERT(self->py_graph->graph); - const auto *vertex = mgp_edge_get_to(self->edge); + const auto *vertex = Call(mgp_edge_get_to, self->edge); MG_ASSERT(vertex); return MakePyVertex(*vertex, self->py_graph); } @@ -1136,7 +1149,7 @@ PyObject *PyEdgeGetId(PyEdge *self, PyObject *Py_UNUSED(ignored)) { MG_ASSERT(self->edge); MG_ASSERT(self->py_graph); MG_ASSERT(self->py_graph->graph); - return PyLong_FromLongLong(mgp_edge_get_id(self->edge).as_int); + return PyLong_FromLongLong(Call(mgp_edge_get_id, self->edge).as_int); } PyObject *PyEdgeIterProperties(PyEdge *self, PyObject *Py_UNUSED(ignored)) { @@ -1144,10 +1157,14 @@ PyObject *PyEdgeIterProperties(PyEdge *self, PyObject *Py_UNUSED(ignored)) { MG_ASSERT(self->edge); MG_ASSERT(self->py_graph); MG_ASSERT(self->py_graph->graph); - auto *properties_it = mgp_edge_iter_properties(self->edge, self->py_graph->memory); - if (!properties_it) { + mgp_properties_iterator *properties_it{nullptr}; + if (const auto err = mgp_edge_iter_properties(self->edge, self->py_graph->memory, &properties_it); + err == MGP_ERROR_UNABLE_TO_ALLOCATE) { PyErr_SetString(PyExc_MemoryError, "Unable to allocate mgp_properties_iterator."); return nullptr; + } else if (err != MGP_ERROR_NO_ERROR) { + PyErr_SetString(PyExc_RuntimeError, "Unexpected error during getting mgp_properties_iterator."); + return nullptr; } auto *py_properties_it = PyObject_New(PyPropertiesIterator, &PyPropertiesIteratorType); if (!py_properties_it) { @@ -1167,10 +1184,14 @@ PyObject *PyEdgeGetProperty(PyEdge *self, PyObject *args) { MG_ASSERT(self->py_graph->graph); const char *prop_name = nullptr; if (!PyArg_ParseTuple(args, "s", &prop_name)) return nullptr; - auto *prop_value = mgp_edge_get_property(self->edge, prop_name, self->py_graph->memory); - if (!prop_value) { + mgp_value *prop_value{nullptr}; + if (const auto err = mgp_edge_get_property(self->edge, prop_name, self->py_graph->memory, &prop_value); + err == MGP_ERROR_UNABLE_TO_ALLOCATE) { PyErr_SetString(PyExc_MemoryError, "Unable to allocate mgp_value for edge property value."); return nullptr; + } else if (err != MGP_ERROR_NO_ERROR) { + PyErr_SetString(PyExc_RuntimeError, "Unexpected error during getting mgp_edge property."); + return nullptr; } auto py_prop_value = MgpValueToPyObject(*prop_value, self->py_graph); mgp_value_destroy(prop_value); @@ -1214,10 +1235,13 @@ static PyTypeObject PyEdgeType = { PyObject *MakePyEdge(const mgp_edge &edge, PyGraph *py_graph) { MG_ASSERT(py_graph); MG_ASSERT(py_graph->graph && py_graph->memory); - auto *edge_copy = mgp_edge_copy(&edge, py_graph->memory); - if (!edge_copy) { + mgp_edge *edge_copy{nullptr}; + if (const auto err = mgp_edge_copy(&edge, py_graph->memory, &edge_copy); err == MGP_ERROR_UNABLE_TO_ALLOCATE) { PyErr_SetString(PyExc_MemoryError, "Unable to allocate mgp_edge."); return nullptr; + } else if (err != MGP_ERROR_NO_ERROR) { + PyErr_SetString(PyExc_RuntimeError, "Unexpected error during creating mgp_edge"); + return nullptr; } auto *py_edge = PyObject_New(PyEdge, &PyEdgeType); if (!py_edge) { @@ -1243,7 +1267,12 @@ PyObject *PyEdgeRichCompare(PyObject *self, PyObject *other, int op) { MG_ASSERT(e1->edge); MG_ASSERT(e2->edge); - return PyBool_FromLong(mgp_edge_equal(e1->edge, e2->edge)); + int equals{0}; + if (const auto err = mgp_edge_equal(e1->edge, e2->edge, &equals); err != MGP_ERROR_NO_ERROR) { + PyErr_SetString(PyExc_RuntimeError, "Unexpected error during comparing edges"); + return nullptr; + } + return PyBool_FromLong(equals); } // clang-format off @@ -1274,7 +1303,12 @@ PyObject *PyVertexGetId(PyVertex *self, PyObject *Py_UNUSED(ignored)) { MG_ASSERT(self->vertex); MG_ASSERT(self->py_graph); MG_ASSERT(self->py_graph->graph); - return PyLong_FromLongLong(mgp_vertex_get_id(self->vertex).as_int); + mgp_vertex_id id{}; + if (const auto err = mgp_vertex_get_id(self->vertex, &id); err != MGP_ERROR_NO_ERROR) { + PyErr_SetString(PyExc_RuntimeError, "Unexpected error during getting id of mgp_vertex"); + return nullptr; + } + return PyLong_FromLongLong(id.as_int); } PyObject *PyVertexLabelsCount(PyVertex *self, PyObject *Py_UNUSED(ignored)) { @@ -1282,7 +1316,12 @@ PyObject *PyVertexLabelsCount(PyVertex *self, PyObject *Py_UNUSED(ignored)) { MG_ASSERT(self->vertex); MG_ASSERT(self->py_graph); MG_ASSERT(self->py_graph->graph); - return PyLong_FromSize_t(mgp_vertex_labels_count(self->vertex)); + size_t label_count{0}; + if (const auto err = mgp_vertex_labels_count(self->vertex, &label_count); err != MGP_ERROR_NO_ERROR) { + PyErr_SetString(PyExc_RuntimeError, "Unexpected error during getting label count of mgp_vertex"); + return nullptr; + } + return PyLong_FromSize_t(label_count); } PyObject *PyVertexLabelAt(PyVertex *self, PyObject *args) { @@ -1292,8 +1331,14 @@ PyObject *PyVertexLabelAt(PyVertex *self, PyObject *args) { MG_ASSERT(self->py_graph->graph); static_assert(std::numeric_limits::max() <= std::numeric_limits::max()); Py_ssize_t id; - if (!PyArg_ParseTuple(args, "n", &id)) return nullptr; - auto label = mgp_vertex_label_at(self->vertex, id); + if (!PyArg_ParseTuple(args, "n", &id)) { + return nullptr; + } + mgp_label label{nullptr}; + if (const auto err = mgp_vertex_label_at(self->vertex, id, &label); err != MGP_ERROR_NO_ERROR) { + PyErr_SetString(PyExc_RuntimeError, "Unexpected error during getting label of mgp_vertex"); + return nullptr; + } if (label.name == nullptr || id < 0) { PyErr_SetString(PyExc_IndexError, "Unable to find the label with given ID."); return nullptr; @@ -1306,10 +1351,14 @@ PyObject *PyVertexIterInEdges(PyVertex *self, PyObject *Py_UNUSED(ignored)) { MG_ASSERT(self->vertex); MG_ASSERT(self->py_graph); MG_ASSERT(self->py_graph->graph); - auto *edges_it = mgp_vertex_iter_in_edges(self->vertex, self->py_graph->memory); - if (!edges_it) { + mgp_edges_iterator *edges_it{nullptr}; + if (const auto err = mgp_vertex_iter_in_edges(self->vertex, self->py_graph->memory, &edges_it); + err == MGP_ERROR_UNABLE_TO_ALLOCATE) { PyErr_SetString(PyExc_MemoryError, "Unable to allocate mgp_edges_iterator for in edges."); return nullptr; + } else if (err != MGP_ERROR_NO_ERROR) { + PyErr_SetString(PyExc_RuntimeError, "Unexpected error during getting mgp_edges_iterator for in edges"); + return nullptr; } auto *py_edges_it = PyObject_New(PyEdgesIterator, &PyEdgesIteratorType); if (!py_edges_it) { @@ -1327,10 +1376,14 @@ PyObject *PyVertexIterOutEdges(PyVertex *self, PyObject *Py_UNUSED(ignored)) { MG_ASSERT(self->vertex); MG_ASSERT(self->py_graph); MG_ASSERT(self->py_graph->graph); - auto *edges_it = mgp_vertex_iter_out_edges(self->vertex, self->py_graph->memory); - if (!edges_it) { + mgp_edges_iterator *edges_it{nullptr}; + if (const auto err = mgp_vertex_iter_out_edges(self->vertex, self->py_graph->memory, &edges_it); + err == MGP_ERROR_UNABLE_TO_ALLOCATE) { PyErr_SetString(PyExc_MemoryError, "Unable to allocate mgp_edges_iterator for out edges."); return nullptr; + } else if (err != MGP_ERROR_NO_ERROR) { + PyErr_SetString(PyExc_RuntimeError, "Unexpected error during getting mgp_edges_iterator for out edges"); + return nullptr; } auto *py_edges_it = PyObject_New(PyEdgesIterator, &PyEdgesIteratorType); if (!py_edges_it) { @@ -1348,10 +1401,14 @@ PyObject *PyVertexIterProperties(PyVertex *self, PyObject *Py_UNUSED(ignored)) { MG_ASSERT(self->vertex); MG_ASSERT(self->py_graph); MG_ASSERT(self->py_graph->graph); - auto *properties_it = mgp_vertex_iter_properties(self->vertex, self->py_graph->memory); - if (!properties_it) { + mgp_properties_iterator *properties_it{nullptr}; + if (const auto err = mgp_vertex_iter_properties(self->vertex, self->py_graph->memory, &properties_it); + err == MGP_ERROR_UNABLE_TO_ALLOCATE) { PyErr_SetString(PyExc_MemoryError, "Unable to allocate mgp_properties_iterator."); return nullptr; + } else if (err != MGP_ERROR_NO_ERROR) { + PyErr_SetString(PyExc_RuntimeError, "Unexpected error during getting mgp_properties_iterator."); + return nullptr; } auto *py_properties_it = PyObject_New(PyPropertiesIterator, &PyPropertiesIteratorType); if (!py_properties_it) { @@ -1370,11 +1427,17 @@ PyObject *PyVertexGetProperty(PyVertex *self, PyObject *args) { MG_ASSERT(self->py_graph); MG_ASSERT(self->py_graph->graph); const char *prop_name = nullptr; - if (!PyArg_ParseTuple(args, "s", &prop_name)) return nullptr; - auto *prop_value = mgp_vertex_get_property(self->vertex, prop_name, self->py_graph->memory); - if (!prop_value) { + if (!PyArg_ParseTuple(args, "s", &prop_name)) { + return nullptr; + } + mgp_value *prop_value{nullptr}; + if (const auto err = mgp_vertex_get_property(self->vertex, prop_name, self->py_graph->memory, &prop_value); + err == MGP_ERROR_UNABLE_TO_ALLOCATE) { PyErr_SetString(PyExc_MemoryError, "Unable to allocate mgp_value for vertex property value."); return nullptr; + } else if (err != MGP_ERROR_NO_ERROR) { + PyErr_SetString(PyExc_RuntimeError, "Unexpected error during getting mgp_vertex property."); + return nullptr; } auto py_prop_value = MgpValueToPyObject(*prop_value, self->py_graph); mgp_value_destroy(prop_value); @@ -1432,10 +1495,14 @@ PyObject *MakePyVertex(mgp_vertex *vertex, PyGraph *py_graph) { PyObject *MakePyVertex(const mgp_vertex &vertex, PyGraph *py_graph) { MG_ASSERT(py_graph); MG_ASSERT(py_graph->graph && py_graph->memory); - auto *vertex_copy = mgp_vertex_copy(&vertex, py_graph->memory); - if (!vertex_copy) { + + mgp_vertex *vertex_copy{nullptr}; + if (const auto err = mgp_vertex_copy(&vertex, py_graph->memory, &vertex_copy); err == MGP_ERROR_UNABLE_TO_ALLOCATE) { PyErr_SetString(PyExc_MemoryError, "Unable to allocate mgp_vertex."); return nullptr; + } else if (err != MGP_ERROR_NO_ERROR) { + PyErr_SetString(PyExc_RuntimeError, "Unexpected error during creating mgp_vertex"); + return nullptr; } auto *py_vertex = MakePyVertex(vertex_copy, py_graph); if (!py_vertex) mgp_vertex_destroy(vertex_copy); @@ -1455,7 +1522,12 @@ PyObject *PyVertexRichCompare(PyObject *self, PyObject *other, int op) { MG_ASSERT(v1->vertex); MG_ASSERT(v2->vertex); - return PyBool_FromLong(mgp_vertex_equal(v1->vertex, v2->vertex)); + int equals{0}; + if (const auto err = mgp_vertex_equal(v1->vertex, v2->vertex, &equals); err != MGP_ERROR_NO_ERROR) { + PyErr_SetString(PyExc_RuntimeError, "Unexpected error during comparing vertices"); + return nullptr; + } + return PyBool_FromLong(equals); } // clang-format off @@ -1492,14 +1564,10 @@ PyObject *PyPathExpand(PyPath *self, PyObject *edge) { return nullptr; } auto *py_edge = reinterpret_cast(edge); - const auto *to = mgp_edge_get_to(py_edge->edge); - const auto *from = mgp_edge_get_from(py_edge->edge); - const auto *last_vertex = mgp_path_vertex_at(self->path, mgp_path_size(self->path)); - if (!mgp_vertex_equal(last_vertex, to) && !mgp_vertex_equal(last_vertex, from)) { + if (const auto err = mgp_path_expand(self->path, py_edge->edge); err == MGP_ERROR_LOGIC_ERROR) { PyErr_SetString(PyExc_ValueError, "Edge is not a continuation of the path."); return nullptr; - } - if (!mgp_path_expand(self->path, py_edge->edge)) { + } else if (err != MGP_ERROR_NO_ERROR) { PyErr_SetString(PyExc_MemoryError, "Unable to expand mgp_path."); return nullptr; } @@ -1510,7 +1578,7 @@ PyObject *PyPathSize(PyPath *self, PyObject *Py_UNUSED(ignored)) { MG_ASSERT(self->path); MG_ASSERT(self->py_graph); MG_ASSERT(self->py_graph->graph); - return PyLong_FromSize_t(mgp_path_size(self->path)); + return PyLong_FromSize_t(Call(mgp_path_size, self->path)); } PyObject *PyPathVertexAt(PyPath *self, PyObject *args) { @@ -1519,11 +1587,16 @@ PyObject *PyPathVertexAt(PyPath *self, PyObject *args) { MG_ASSERT(self->py_graph->graph); static_assert(std::numeric_limits::max() <= std::numeric_limits::max()); Py_ssize_t i; - if (!PyArg_ParseTuple(args, "n", &i)) return nullptr; - const auto *vertex = mgp_path_vertex_at(self->path, i); - if (!vertex) { + if (!PyArg_ParseTuple(args, "n", &i)) { + return nullptr; + } + const mgp_vertex *vertex{nullptr}; + if (const auto err = mgp_path_vertex_at(self->path, i, &vertex); err == MGP_ERROR_OUT_OF_RANGE) { PyErr_SetString(PyExc_IndexError, "Index is out of range."); return nullptr; + } else if (err != MGP_ERROR_NO_ERROR) { + PyErr_SetString(PyExc_RuntimeError, "Unexpected error during getting vertex from path."); + return nullptr; } return MakePyVertex(*vertex, self->py_graph); } @@ -1534,11 +1607,16 @@ PyObject *PyPathEdgeAt(PyPath *self, PyObject *args) { MG_ASSERT(self->py_graph->graph); static_assert(std::numeric_limits::max() <= std::numeric_limits::max()); Py_ssize_t i; - if (!PyArg_ParseTuple(args, "n", &i)) return nullptr; - const auto *edge = mgp_path_edge_at(self->path, i); - if (!edge) { + if (!PyArg_ParseTuple(args, "n", &i)) { + return nullptr; + } + const mgp_edge *edge{nullptr}; + if (const auto err = mgp_path_edge_at(self->path, i, &edge); err == MGP_ERROR_OUT_OF_RANGE) { PyErr_SetString(PyExc_IndexError, "Index is out of range."); return nullptr; + } else if (err != MGP_ERROR_NO_ERROR) { + PyErr_SetString(PyExc_RuntimeError, "Unexpected error during getting edge from path."); + return nullptr; } return MakePyEdge(*edge, self->py_graph); } @@ -1586,10 +1664,13 @@ PyObject *MakePyPath(mgp_path *path, PyGraph *py_graph) { PyObject *MakePyPath(const mgp_path &path, PyGraph *py_graph) { MG_ASSERT(py_graph); MG_ASSERT(py_graph->graph && py_graph->memory); - auto *path_copy = mgp_path_copy(&path, py_graph->memory); - if (!path_copy) { + mgp_path *path_copy{nullptr}; + if (const auto err = mgp_path_copy(&path, py_graph->memory, &path_copy); err == MGP_ERROR_UNABLE_TO_ALLOCATE) { PyErr_SetString(PyExc_MemoryError, "Unable to allocate mgp_path."); return nullptr; + } else if (err != MGP_ERROR_NO_ERROR) { + PyErr_SetString(PyExc_RuntimeError, "Unexpected error during copying a path"); + return nullptr; } auto *py_path = MakePyPath(path_copy, py_graph); if (!py_path) mgp_path_destroy(path_copy); @@ -1606,10 +1687,14 @@ PyObject *PyPathMakeWithStart(PyTypeObject *type, PyObject *vertex) { return nullptr; } auto *py_vertex = reinterpret_cast(vertex); - auto *path = mgp_path_make_with_start(py_vertex->vertex, py_vertex->py_graph->memory); - if (!path) { + mgp_path *path{nullptr}; + if (const auto err = mgp_path_make_with_start(py_vertex->vertex, py_vertex->py_graph->memory, &path); + err == MGP_ERROR_UNABLE_TO_ALLOCATE) { PyErr_SetString(PyExc_MemoryError, "Unable to allocate mgp_path."); return nullptr; + } else if (err != MGP_ERROR_NO_ERROR) { + PyErr_SetString(PyExc_RuntimeError, "Unexpected error during creating a path"); + return nullptr; } auto *py_path = MakePyPath(path, py_vertex->py_graph); if (!py_path) mgp_path_destroy(path); @@ -1698,27 +1783,31 @@ py::Object MgpValueToPyObject(const mgp_value &value, PyObject *py_graph) { } py::Object MgpValueToPyObject(const mgp_value &value, PyGraph *py_graph) { - switch (mgp_value_get_type(&value)) { + switch (Call(mgp_value_get_type, &value)) { case MGP_VALUE_TYPE_NULL: Py_INCREF(Py_None); return py::Object(Py_None); case MGP_VALUE_TYPE_BOOL: - return py::Object(PyBool_FromLong(mgp_value_get_bool(&value))); + return py::Object(PyBool_FromLong(CallBool(mgp_value_get_bool, &value))); case MGP_VALUE_TYPE_INT: - return py::Object(PyLong_FromLongLong(mgp_value_get_int(&value))); + return py::Object(PyLong_FromLongLong(Call(mgp_value_get_int, &value))); case MGP_VALUE_TYPE_DOUBLE: - return py::Object(PyFloat_FromDouble(mgp_value_get_double(&value))); + return py::Object(PyFloat_FromDouble(Call(mgp_value_get_double, &value))); case MGP_VALUE_TYPE_STRING: - return py::Object(PyUnicode_FromString(mgp_value_get_string(&value))); + return py::Object(PyUnicode_FromString(Call(mgp_value_get_string, &value))); case MGP_VALUE_TYPE_LIST: - return MgpListToPyTuple(mgp_value_get_list(&value), py_graph); + return MgpListToPyTuple(Call(mgp_value_get_list, &value), py_graph); case MGP_VALUE_TYPE_MAP: { - const auto *map = mgp_value_get_map(&value); + const auto *map = Call(mgp_value_get_map, &value); py::Object py_dict(PyDict_New()); - if (!py_dict) return nullptr; + if (!py_dict) { + return nullptr; + } for (const auto &[key, val] : map->items) { auto py_val = MgpValueToPyObject(val, py_graph); - if (!py_val) return nullptr; + if (!py_val) { + return nullptr; + } // Unlike PyList_SET_ITEM, PyDict_SetItem does not steal the value. if (PyDict_SetItemString(py_dict.Ptr(), key.c_str(), py_val.Ptr()) != 0) return nullptr; } @@ -1727,21 +1816,21 @@ py::Object MgpValueToPyObject(const mgp_value &value, PyGraph *py_graph) { case MGP_VALUE_TYPE_VERTEX: { py::Object py_mgp(PyImport_ImportModule("mgp")); if (!py_mgp) return nullptr; - const auto *v = mgp_value_get_vertex(&value); + const auto *v = Call(mgp_value_get_vertex, &value); py::Object py_vertex(reinterpret_cast(MakePyVertex(*v, py_graph))); return py_mgp.CallMethod("Vertex", py_vertex); } case MGP_VALUE_TYPE_EDGE: { py::Object py_mgp(PyImport_ImportModule("mgp")); if (!py_mgp) return nullptr; - const auto *e = mgp_value_get_edge(&value); + const auto *e = Call(mgp_value_get_edge, &value); py::Object py_edge(reinterpret_cast(MakePyEdge(*e, py_graph))); return py_mgp.CallMethod("Edge", py_edge); } case MGP_VALUE_TYPE_PATH: { py::Object py_mgp(PyImport_ImportModule("mgp")); if (!py_mgp) return nullptr; - const auto *p = mgp_value_get_path(&value); + const auto *p = Call(mgp_value_get_path, &value); py::Object py_path(reinterpret_cast(MakePyPath(*p, py_graph))); return py_mgp.CallMethod("Path", py_path); } @@ -1751,29 +1840,33 @@ py::Object MgpValueToPyObject(const mgp_value &value, PyGraph *py_graph) { mgp_value *PyObjectToMgpValue(PyObject *o, mgp_memory *memory) { auto py_seq_to_list = [memory](PyObject *seq, Py_ssize_t len, const auto &py_seq_get_item) { static_assert(std::numeric_limits::max() <= std::numeric_limits::max()); - mgp_list *list = mgp_list_make_empty(len, memory); - if (!list) throw std::bad_alloc(); + MgpUniquePtr list{nullptr, &mgp_list_destroy}; + if (const auto err = CreateMgpObject(list, mgp_list_make_empty, len, memory); + err == MGP_ERROR_UNABLE_TO_ALLOCATE) { + throw std::bad_alloc{}; + } else if (err != MGP_ERROR_NO_ERROR) { + throw std::runtime_error{"Unexpected error during making mgp_list"}; + } for (Py_ssize_t i = 0; i < len; ++i) { PyObject *e = py_seq_get_item(seq, i); mgp_value *v{nullptr}; - try { - v = PyObjectToMgpValue(e, memory); - } catch (...) { - mgp_list_destroy(list); - throw; - } - if (!mgp_list_append(list, v)) { - mgp_value_destroy(v); - mgp_list_destroy(list); - throw std::bad_alloc(); - } + v = PyObjectToMgpValue(e, memory); + const auto err = mgp_list_append(list.get(), v); mgp_value_destroy(v); + if (err != MGP_ERROR_NO_ERROR) { + if (err == MGP_ERROR_UNABLE_TO_ALLOCATE) { + throw std::bad_alloc{}; + } + throw std::runtime_error{"Unexpected error during appending to mgp_list"}; + } } - auto *v = mgp_value_make_list(list); - if (!v) { - mgp_list_destroy(list); - throw std::bad_alloc(); + mgp_value *v{nullptr}; + if (const auto err = mgp_value_make_list(list.get(), &v); err == MGP_ERROR_UNABLE_TO_ALLOCATE) { + throw std::bad_alloc{}; + } else if (err != MGP_ERROR_NO_ERROR) { + throw std::runtime_error{"Unexpected error during making mgp_value"}; } + static_cast(list.release()); return v; }; @@ -1802,31 +1895,37 @@ mgp_value *PyObjectToMgpValue(PyObject *o, mgp_memory *memory) { }; mgp_value *mgp_v{nullptr}; + mgp_error last_error{MGP_ERROR_NO_ERROR}; if (o == Py_None) { - mgp_v = mgp_value_make_null(memory); + last_error = mgp_value_make_null(memory, &mgp_v); } else if (PyBool_Check(o)) { - mgp_v = mgp_value_make_bool(static_cast(o == Py_True), memory); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) Py_True is defined with C-style cast + last_error = mgp_value_make_bool(static_cast(o == Py_True), memory, &mgp_v); } else if (PyLong_Check(o)) { int64_t value = PyLong_AsLong(o); if (PyErr_Occurred()) { PyErr_Clear(); throw std::overflow_error("Python integer is out of range"); } - mgp_v = mgp_value_make_int(value, memory); + last_error = mgp_value_make_int(value, memory, &mgp_v); } else if (PyFloat_Check(o)) { - mgp_v = mgp_value_make_double(PyFloat_AsDouble(o), memory); - } else if (PyUnicode_Check(o)) { - mgp_v = mgp_value_make_string(PyUnicode_AsUTF8(o), memory); + last_error = mgp_value_make_double(PyFloat_AsDouble(o), memory, &mgp_v); + } else if (PyUnicode_Check(o)) { // NOLINT(hicpp-signed-bitwise) + last_error = mgp_value_make_string(PyUnicode_AsUTF8(o), memory, &mgp_v); } else if (PyList_Check(o)) { mgp_v = py_seq_to_list(o, PyList_Size(o), [](auto *list, const auto i) { return PyList_GET_ITEM(list, i); }); } else if (PyTuple_Check(o)) { mgp_v = py_seq_to_list(o, PyTuple_Size(o), [](auto *tuple, const auto i) { return PyTuple_GET_ITEM(tuple, i); }); - } else if (PyDict_Check(o)) { - mgp_map *map = mgp_map_make_empty(memory); + } else if (PyDict_Check(o)) { // NOLINT(hicpp-signed-bitwise) + MgpUniquePtr map{nullptr, mgp_map_destroy}; + const auto map_err = CreateMgpObject(map, mgp_map_make_empty, memory); - if (!map) { - throw std::bad_alloc(); + if (map_err == MGP_ERROR_UNABLE_TO_ALLOCATE) { + throw std::bad_alloc{}; + } + if (map_err != MGP_ERROR_NO_ERROR) { + throw std::runtime_error{"Unexpected error during creating mgp_map"}; } PyObject *key{nullptr}; @@ -1834,73 +1933,79 @@ mgp_value *PyObjectToMgpValue(PyObject *o, mgp_memory *memory) { Py_ssize_t pos{0}; while (PyDict_Next(o, &pos, &key, &value)) { if (!PyUnicode_Check(key)) { - mgp_map_destroy(map); throw std::invalid_argument("Dictionary keys must be strings"); } const char *k = PyUnicode_AsUTF8(key); - mgp_value *v{nullptr}; if (!k) { PyErr_Clear(); - mgp_map_destroy(map); - throw std::bad_alloc(); + throw std::bad_alloc{}; } - try { - v = PyObjectToMgpValue(value, memory); - } catch (...) { - mgp_map_destroy(map); - throw; - } + MgpUniquePtr v{PyObjectToMgpValue(value, memory), mgp_value_destroy}; - if (!mgp_map_insert(map, k, v)) { - mgp_value_destroy(v); - mgp_map_destroy(map); - throw std::bad_alloc(); + if (const auto err = mgp_map_insert(map.get(), k, v.get()); err == MGP_ERROR_UNABLE_TO_ALLOCATE) { + throw std::bad_alloc{}; + } else if (err != MGP_ERROR_NO_ERROR) { + throw std::runtime_error{"Unexpected error during inserting an item to mgp_map"}; } - - mgp_value_destroy(v); } - mgp_v = mgp_value_make_map(map); - if (!mgp_v) { - mgp_map_destroy(map); - throw std::bad_alloc(); + if (const auto err = mgp_value_make_map(map.get(), &mgp_v); err == MGP_ERROR_UNABLE_TO_ALLOCATE) { + throw std::bad_alloc{}; + } else if (err != MGP_ERROR_NO_ERROR) { + throw std::runtime_error{"Unexpected error during creating mgp_value"}; } + static_cast(map.release()); } else if (Py_TYPE(o) == &PyEdgeType) { + MgpUniquePtr e{nullptr, mgp_edge_destroy}; // Copy the edge and pass the ownership to the created mgp_value. - auto *e = mgp_edge_copy(reinterpret_cast(o)->edge, memory); - if (!e) { - throw std::bad_alloc(); + + if (const auto err = CreateMgpObject(e, mgp_edge_copy, reinterpret_cast(o)->edge, memory); + err == MGP_ERROR_UNABLE_TO_ALLOCATE) { + throw std::bad_alloc{}; + } else if (err != MGP_ERROR_NO_ERROR) { + throw std::runtime_error{"Unexpected error during copying mgp_edge"}; } - mgp_v = mgp_value_make_edge(e); - if (!mgp_v) { - mgp_edge_destroy(e); - throw std::bad_alloc(); + if (const auto err = mgp_value_make_edge(e.get(), &mgp_v); err == MGP_ERROR_UNABLE_TO_ALLOCATE) { + throw std::bad_alloc{}; + } else if (err != MGP_ERROR_NO_ERROR) { + throw std::runtime_error{"Unexpected error during copying mgp_edge"}; } + static_cast(e.release()); } else if (Py_TYPE(o) == &PyPathType) { - // Copy the path and pass the ownership to the created mgp_value. - auto *p = mgp_path_copy(reinterpret_cast(o)->path, memory); - if (!p) { - throw std::bad_alloc(); + MgpUniquePtr p{nullptr, mgp_path_destroy}; + // Copy the edge and pass the ownership to the created mgp_value. + + if (const auto err = CreateMgpObject(p, mgp_path_copy, reinterpret_cast(o)->path, memory); + err == MGP_ERROR_UNABLE_TO_ALLOCATE) { + throw std::bad_alloc{}; + } else if (err != MGP_ERROR_NO_ERROR) { + throw std::runtime_error{"Unexpected error during copying mgp_path"}; } - mgp_v = mgp_value_make_path(p); - if (!mgp_v) { - mgp_path_destroy(p); - throw std::bad_alloc(); + if (const auto err = mgp_value_make_path(p.get(), &mgp_v); err == MGP_ERROR_UNABLE_TO_ALLOCATE) { + throw std::bad_alloc{}; + } else if (err != MGP_ERROR_NO_ERROR) { + throw std::runtime_error{"Unexpected error during copying mgp_path"}; } + static_cast(p.release()); } else if (Py_TYPE(o) == &PyVertexType) { - // Copy the vertex and pass the ownership to the created mgp_value. - auto *v = mgp_vertex_copy(reinterpret_cast(o)->vertex, memory); - if (!v) { - throw std::bad_alloc(); + MgpUniquePtr v{nullptr, mgp_vertex_destroy}; + // Copy the edge and pass the ownership to the created mgp_value. + + if (const auto err = CreateMgpObject(v, mgp_vertex_copy, reinterpret_cast(o)->vertex, memory); + err == MGP_ERROR_UNABLE_TO_ALLOCATE) { + throw std::bad_alloc{}; + } else if (err != MGP_ERROR_NO_ERROR) { + throw std::runtime_error{"Unexpected error during copying mgp_vertex"}; } - mgp_v = mgp_value_make_vertex(v); - if (!mgp_v) { - mgp_vertex_destroy(v); - throw std::bad_alloc(); + if (const auto err = mgp_value_make_vertex(v.get(), &mgp_v); err == MGP_ERROR_UNABLE_TO_ALLOCATE) { + throw std::bad_alloc{}; + } else if (err != MGP_ERROR_NO_ERROR) { + throw std::runtime_error{"Unexpected error during copying mgp_vertex"}; } + static_cast(v.release()); } else if (is_mgp_instance(o, "Edge")) { py::Object edge(PyObject_GetAttrString(o, "_edge")); if (!edge) { @@ -1926,8 +2031,11 @@ mgp_value *PyObjectToMgpValue(PyObject *o, mgp_memory *memory) { throw std::invalid_argument("Unsupported PyObject conversion"); } - if (!mgp_v) { - throw std::bad_alloc(); + if (last_error == MGP_ERROR_UNABLE_TO_ALLOCATE) { + throw std::bad_alloc{}; + } + if (last_error != MGP_ERROR_NO_ERROR) { + throw std::runtime_error{"Unexpected error while creating mgp_value"}; } return mgp_v; diff --git a/src/storage/v2/edge_accessor.hpp b/src/storage/v2/edge_accessor.hpp index 261a5a60e..b9445731e 100644 --- a/src/storage/v2/edge_accessor.hpp +++ b/src/storage/v2/edge_accessor.hpp @@ -57,7 +57,7 @@ class EdgeAccessor final { /// @throw std::bad_alloc Result> Properties(View view) const; - Gid Gid() const { + Gid Gid() const noexcept { if (config_.properties_on_edges) { return edge_.ptr->gid; } else { @@ -67,10 +67,10 @@ class EdgeAccessor final { bool IsCycle() const { return from_vertex_ == to_vertex_; } - bool operator==(const EdgeAccessor &other) const { + bool operator==(const EdgeAccessor &other) const noexcept { return edge_ == other.edge_ && transaction_ == other.transaction_; } - bool operator!=(const EdgeAccessor &other) const { return !(*this == other); } + bool operator!=(const EdgeAccessor &other) const noexcept { return !(*this == other); } private: EdgeRef edge_; diff --git a/src/storage/v2/edge_ref.hpp b/src/storage/v2/edge_ref.hpp index 60e604284..b66907c6a 100644 --- a/src/storage/v2/edge_ref.hpp +++ b/src/storage/v2/edge_ref.hpp @@ -24,8 +24,8 @@ static_assert(std::is_standard_layout_v, "The Gid must have a standard layo static_assert(std::is_standard_layout_v, "The Edge * must have a standard layout!"); static_assert(std::is_standard_layout_v, "The EdgeRef must have a standard layout!"); -inline bool operator==(const EdgeRef &a, const EdgeRef &b) { return a.gid == b.gid; } +inline bool operator==(const EdgeRef &a, const EdgeRef &b) noexcept { return a.gid == b.gid; } -inline bool operator!=(const EdgeRef &a, const EdgeRef &b) { return a.gid != b.gid; } +inline bool operator!=(const EdgeRef &a, const EdgeRef &b) noexcept { return a.gid != b.gid; } } // namespace storage diff --git a/src/storage/v2/vertex_accessor.hpp b/src/storage/v2/vertex_accessor.hpp index a24f02a23..7b2787da1 100644 --- a/src/storage/v2/vertex_accessor.hpp +++ b/src/storage/v2/vertex_accessor.hpp @@ -83,12 +83,12 @@ class VertexAccessor final { Result OutDegree(View view) const; - Gid Gid() const { return vertex_->gid; } + Gid Gid() const noexcept { return vertex_->gid; } - bool operator==(const VertexAccessor &other) const { + bool operator==(const VertexAccessor &other) const noexcept { return vertex_ == other.vertex_ && transaction_ == other.transaction_; } - bool operator!=(const VertexAccessor &other) const { return !(*this == other); } + bool operator!=(const VertexAccessor &other) const noexcept { return !(*this == other); } private: Vertex *vertex_; diff --git a/src/utils/async_timer.cpp b/src/utils/async_timer.cpp index afe90ce7f..8e8a0f081 100644 --- a/src/utils/async_timer.cpp +++ b/src/utils/async_timer.cpp @@ -168,7 +168,7 @@ AsyncTimer &AsyncTimer::operator=(AsyncTimer &&other) { return *this; }; -bool AsyncTimer::IsExpired() const { +bool AsyncTimer::IsExpired() const noexcept { if (expiration_flag_ != nullptr) { return expiration_flag_->load(std::memory_order_relaxed); } diff --git a/src/utils/async_timer.hpp b/src/utils/async_timer.hpp index 4ac2ffc87..831db4e49 100644 --- a/src/utils/async_timer.hpp +++ b/src/utils/async_timer.hpp @@ -22,7 +22,7 @@ class AsyncTimer { AsyncTimer &operator=(const AsyncTimer &) = delete; // Returns false if the object isn't associated with any timer. - bool IsExpired() const; + bool IsExpired() const noexcept; private: void ReleaseResources(); diff --git a/src/utils/on_scope_exit.hpp b/src/utils/on_scope_exit.hpp index 05897889b..257a22715 100644 --- a/src/utils/on_scope_exit.hpp +++ b/src/utils/on_scope_exit.hpp @@ -28,6 +28,10 @@ class OnScopeExit { explicit OnScopeExit(const std::function &function) : function_(function) {} ~OnScopeExit() { function_(); } + void Disable() { + function_ = [] {}; + } + private: std::function function_; }; diff --git a/tests/.clang-tidy b/tests/.clang-tidy index d6676abd7..1a0f29304 100644 --- a/tests/.clang-tidy +++ b/tests/.clang-tidy @@ -39,7 +39,6 @@ Checks: '*, -hicpp-vararg, -llvm-header-guard, -misc-non-private-member-variables-in-classes, - -misc-unused-parameters, -modernize-avoid-c-arrays, -modernize-concat-nested-namespaces, -modernize-pass-by-value, diff --git a/tests/e2e/memory/procedures/global_memory_limit.c b/tests/e2e/memory/procedures/global_memory_limit.c index a2003cb59..8d4362afc 100644 --- a/tests/e2e/memory/procedures/global_memory_limit.c +++ b/tests/e2e/memory/procedures/global_memory_limit.c @@ -6,26 +6,32 @@ void set_error(struct mgp_result *result) { mgp_result_set_error_msg(result, "So static void procedure(const struct mgp_list *args, const struct mgp_graph *graph, struct mgp_result *result, struct mgp_memory *memory) { - struct mgp_result_record *record = mgp_result_new_record(result); - if (record == NULL) return set_error(result); + struct mgp_result_record *record = NULL; + const enum mgp_error new_record_err = mgp_result_new_record(result, &record); + if (new_record_err != MGP_ERROR_NO_ERROR) return set_error(result); - struct mgp_value *result_msg = mgp_value_make_string("mgp_init_module allocation works", memory); - if (result_msg == NULL) return set_error(result); + struct mgp_value *result_msg = NULL; + const enum mgp_error make_string_err = mgp_value_make_string("mgp_init_module allocation works", memory, &result_msg); + if (make_string_err != MGP_ERROR_NO_ERROR) return set_error(result); - int result_inserted = mgp_result_record_insert(record, "result", result_msg); + const enum mgp_error result_inserted = mgp_result_record_insert(record, "result", result_msg); mgp_value_destroy(result_msg); - if (!result_inserted) return set_error(result); + if (result_inserted != MGP_ERROR_NO_ERROR) return set_error(result); } int mgp_init_module(struct mgp_module *module, struct mgp_memory *memory) { const size_t one_gb = 1 << 30; - gVal = mgp_global_alloc(one_gb); - if (!gVal) return 1; + const enum mgp_error alloc_err = mgp_global_alloc(one_gb, &gVal); + if (alloc_err != MGP_ERROR_NO_ERROR) return 1; - struct mgp_proc *proc = mgp_module_add_read_procedure(module, "procedure", procedure); - if (!proc) return 1; + struct mgp_proc *proc = NULL; + const enum mgp_error proc_err = mgp_module_add_read_procedure(module, "procedure", procedure, &proc); + if (proc_err != MGP_ERROR_NO_ERROR) return 1; - if (!mgp_proc_add_result(proc, "result", mgp_type_string())) return 1; + const struct mgp_type *string_type = NULL; + const enum mgp_error string_type_err = mgp_type_string(&string_type); + if (string_type_err != MGP_ERROR_NO_ERROR) return 1; + if (mgp_proc_add_result(proc, "result", string_type) != MGP_ERROR_NO_ERROR) return 1; return 0; } diff --git a/tests/e2e/memory/procedures/global_memory_limit_proc.c b/tests/e2e/memory/procedures/global_memory_limit_proc.c index 519f11f05..95f7318f3 100644 --- a/tests/e2e/memory/procedures/global_memory_limit_proc.c +++ b/tests/e2e/memory/procedures/global_memory_limit_proc.c @@ -14,45 +14,57 @@ static void error(const struct mgp_list *args, const struct mgp_graph *graph, st gVal = NULL; } if (!gVal) { - gVal = mgp_global_alloc(one_gb); - if (!gVal) return set_out_of_memory_error(result); + const enum mgp_error err = mgp_global_alloc(one_gb, &gVal); + if (err == MGP_ERROR_UNABLE_TO_ALLOCATE) return set_out_of_memory_error(result); + if (err != MGP_ERROR_NO_ERROR) return set_error(result); } - struct mgp_result_record *record = mgp_result_new_record(result); - if (record == NULL) return set_error(result); - struct mgp_value *error_value = mgp_value_make_string("ERROR", memory); - if (error_value == NULL) return set_error(result); - int result_inserted = mgp_result_record_insert(record, "error_result", error_value); + struct mgp_result_record *record = NULL; + const enum mgp_error new_record_err = mgp_result_new_record(result, &record); + if (new_record_err != MGP_ERROR_NO_ERROR) return set_error(result); + struct mgp_value *error_value = NULL; + const enum mgp_error make_string_err = mgp_value_make_string("ERROR", memory, &error_value); + if (make_string_err != MGP_ERROR_NO_ERROR) return set_error(result); + const enum mgp_error result_inserted = mgp_result_record_insert(record, "error_result", error_value); mgp_value_destroy(error_value); - if (!result_inserted) return set_error(result); + if (result_inserted != MGP_ERROR_NO_ERROR) return set_error(result); } static void success(const struct mgp_list *args, const struct mgp_graph *graph, struct mgp_result *result, struct mgp_memory *memory) { const size_t bytes = 1024; if (!gVal) { - gVal = mgp_global_alloc(bytes); - if (!gVal) set_out_of_memory_error(result); + const enum mgp_error err = mgp_global_alloc(bytes, &gVal); + if (err == MGP_ERROR_UNABLE_TO_ALLOCATE) return set_out_of_memory_error(result); + if (err != MGP_ERROR_NO_ERROR) return set_error(result); } - struct mgp_result_record *record = mgp_result_new_record(result); - if (record == NULL) return set_error(result); - struct mgp_value *success_value = mgp_value_make_string("sucess", memory); - if (success_value == NULL) return set_error(result); - int result_inserted = mgp_result_record_insert(record, "success_result", success_value); + struct mgp_result_record *record = NULL; + const enum mgp_error new_record_err = mgp_result_new_record(result, &record); + if (new_record_err != MGP_ERROR_NO_ERROR) return set_error(result); + + struct mgp_value *success_value = NULL; + const enum mgp_error make_string_err = mgp_value_make_string("sucess", memory, &success_value); + if (make_string_err != MGP_ERROR_NO_ERROR) return set_error(result); + const enum mgp_error result_inserted = mgp_result_record_insert(record, "success_result", success_value); mgp_value_destroy(success_value); - if (!result_inserted) return set_error(result); + if (result_inserted != MGP_ERROR_NO_ERROR) return set_error(result); } int mgp_init_module(struct mgp_module *module, struct mgp_memory *memory) { - struct mgp_proc *error_proc = mgp_module_add_read_procedure(module, "error", error); - if (!error_proc) return 1; + struct mgp_proc *error_proc = NULL; + const enum mgp_error error_proc_err = mgp_module_add_read_procedure(module, "error", error, &error_proc); + if (error_proc_err != MGP_ERROR_NO_ERROR) return 1; - if (!mgp_proc_add_result(error_proc, "error_result", mgp_type_string())) return 1; + const struct mgp_type *string_type = NULL; + const enum mgp_error string_type_err = mgp_type_string(&string_type); + if (string_type_err != MGP_ERROR_NO_ERROR) return 1; + if (mgp_proc_add_result(error_proc, "error_result", string_type) != MGP_ERROR_NO_ERROR) return 1; - struct mgp_proc *succ_proc = mgp_module_add_read_procedure(module, "success", success); - if (!succ_proc) return 1; + struct mgp_proc *succ_proc = NULL; + const enum mgp_error succ_proc_err = mgp_module_add_read_procedure(module, "success", success, &succ_proc); + if (succ_proc_err != MGP_ERROR_NO_ERROR) return 1; - if (!mgp_proc_add_result(succ_proc, "success_result", mgp_type_string())) return 1; + if (mgp_proc_add_result(succ_proc, "success_result", string_type) != MGP_ERROR_NO_ERROR) return 1; return 0; } diff --git a/tests/unit/mgp_kafka_c_api.cpp b/tests/unit/mgp_kafka_c_api.cpp index 117c7b9e6..8e1396075 100644 --- a/tests/unit/mgp_kafka_c_api.cpp +++ b/tests/unit/mgp_kafka_c_api.cpp @@ -9,6 +9,7 @@ #include "gtest/gtest.h" #include "integrations/kafka/consumer.hpp" #include "query/procedure/mg_procedure_impl.hpp" +#include "test_utils.hpp" #include "utils/pmr/vector.hpp" /// This class implements the interface of RdKafka::Message such that it can be mocked. @@ -133,20 +134,21 @@ class MgpApiTest : public ::testing::Test { TEST_F(MgpApiTest, TestAllMgpKafkaCApi) { const mgp_messages &messages = Messages(); - EXPECT_EQ(mgp_messages_size(&messages), expected.size()); + EXPECT_EQ(EXPECT_MGP_NO_ERROR(size_t, mgp_messages_size, &messages), expected.size()); for (int i = 0; i < expected.size(); ++i) { - const auto *message = mgp_messages_at(&messages, i); + const auto *message = EXPECT_MGP_NO_ERROR(const mgp_message *, mgp_messages_at, &messages, i); // Test for key and key size. Key size is always 1 in this test. - EXPECT_EQ(mgp_message_key_size(message), 1); - EXPECT_EQ(*mgp_message_key(message), expected[i].key); + EXPECT_EQ(EXPECT_MGP_NO_ERROR(size_t, mgp_message_key_size, message), 1); + EXPECT_EQ(*EXPECT_MGP_NO_ERROR(const char *, mgp_message_key, message), expected[i].key); // Test for payload size - EXPECT_EQ(mgp_message_payload_size(message), expected[i].payload_size); + EXPECT_EQ(EXPECT_MGP_NO_ERROR(size_t, mgp_message_payload_size, message), expected[i].payload_size); // Test for payload - EXPECT_FALSE(std::strcmp(mgp_message_payload(message), expected[i].payload)); + EXPECT_FALSE(std::strcmp(EXPECT_MGP_NO_ERROR(const char *, mgp_message_payload, message), expected[i].payload)); // Test for topic name - EXPECT_FALSE(std::strcmp(mgp_message_topic_name(message), expected[i].topic_name)); + EXPECT_FALSE( + std::strcmp(EXPECT_MGP_NO_ERROR(const char *, mgp_message_topic_name, message), expected[i].topic_name)); } // Unfortunately, we can't test timestamp here because we can't mock (as explained above) diff --git a/tests/unit/mgp_trans_c_api.cpp b/tests/unit/mgp_trans_c_api.cpp index 54051c079..150dfcc4e 100644 --- a/tests/unit/mgp_trans_c_api.cpp +++ b/tests/unit/mgp_trans_c_api.cpp @@ -13,13 +13,13 @@ TEST(MgpTransTest, TestMgpTransApi) { // for different string cases as these are all handled by // IsValidIdentifier(). // Maybe add a mock instead and expect IsValidIdentifier() to be called once? - EXPECT_FALSE(mgp_module_add_transformation(&module, "dash-dash", no_op_cb)); - EXPECT_TRUE(module.transformations.size() == 0); + EXPECT_EQ(mgp_module_add_transformation(&module, "dash-dash", no_op_cb), MGP_ERROR_INVALID_ARGUMENT); + EXPECT_TRUE(module.transformations.empty()); - EXPECT_TRUE(mgp_module_add_transformation(&module, "transform", no_op_cb)); + EXPECT_EQ(mgp_module_add_transformation(&module, "transform", no_op_cb), MGP_ERROR_NO_ERROR); EXPECT_NE(module.transformations.find("transform"), module.transformations.end()); // Try to register a transformation twice - EXPECT_FALSE(mgp_module_add_transformation(&module, "transform", no_op_cb)); + EXPECT_EQ(mgp_module_add_transformation(&module, "transform", no_op_cb), MGP_ERROR_LOGIC_ERROR); EXPECT_TRUE(module.transformations.size() == 1); } diff --git a/tests/unit/query_procedure_mgp_module.cpp b/tests/unit/query_procedure_mgp_module.cpp index 74e2da106..f01596bf4 100644 --- a/tests/unit/query_procedure_mgp_module.cpp +++ b/tests/unit/query_procedure_mgp_module.cpp @@ -11,34 +11,42 @@ static void DummyCallback(const mgp_list *, const mgp_graph *, mgp_result *, mgp TEST(Module, InvalidProcedureRegistration) { mgp_module module(utils::NewDeleteResource()); - EXPECT_FALSE(mgp_module_add_read_procedure(&module, "dashes-not-supported", DummyCallback)); + mgp_proc *proc{nullptr}; + EXPECT_EQ(mgp_module_add_read_procedure(&module, "dashes-not-supported", DummyCallback, &proc), + MGP_ERROR_INVALID_ARGUMENT); // as u8string this is u8"unicode\u22c6not\u2014supported" - EXPECT_FALSE(mgp_module_add_read_procedure(&module, "unicode\xE2\x8B\x86not\xE2\x80\x94supported", DummyCallback)); + EXPECT_EQ(mgp_module_add_read_procedure(&module, "unicode\xE2\x8B\x86not\xE2\x80\x94supported", DummyCallback, &proc), + MGP_ERROR_INVALID_ARGUMENT); // as u8string this is u8"`backticks⋆\u22c6won't-save\u2014you`" - EXPECT_FALSE( - mgp_module_add_read_procedure(&module, "`backticks⋆\xE2\x8B\x86won't-save\xE2\x80\x94you`", DummyCallback)); - EXPECT_FALSE(mgp_module_add_read_procedure(&module, "42_name_must_not_start_with_number", DummyCallback)); - EXPECT_FALSE(mgp_module_add_read_procedure(&module, "div/", DummyCallback)); - EXPECT_FALSE(mgp_module_add_read_procedure(&module, "mul*", DummyCallback)); - EXPECT_FALSE(mgp_module_add_read_procedure(&module, "question_mark_is_not_valid?", DummyCallback)); + EXPECT_EQ( + mgp_module_add_read_procedure(&module, "`backticks⋆\xE2\x8B\x86won't-save\xE2\x80\x94you`", DummyCallback, &proc), + MGP_ERROR_INVALID_ARGUMENT); + EXPECT_EQ(mgp_module_add_read_procedure(&module, "42_name_must_not_start_with_number", DummyCallback, &proc), + MGP_ERROR_INVALID_ARGUMENT); + EXPECT_EQ(mgp_module_add_read_procedure(&module, "div/", DummyCallback, &proc), MGP_ERROR_INVALID_ARGUMENT); + EXPECT_EQ(mgp_module_add_read_procedure(&module, "mul*", DummyCallback, &proc), MGP_ERROR_INVALID_ARGUMENT); + EXPECT_EQ(mgp_module_add_read_procedure(&module, "question_mark_is_not_valid?", DummyCallback, &proc), + MGP_ERROR_INVALID_ARGUMENT); } TEST(Module, RegisteringTheSameProcedureMultipleTimes) { mgp_module module(utils::NewDeleteResource()); + mgp_proc *proc{nullptr}; EXPECT_EQ(module.procedures.find("same_name"), module.procedures.end()); - EXPECT_TRUE(mgp_module_add_read_procedure(&module, "same_name", DummyCallback)); + EXPECT_EQ(mgp_module_add_read_procedure(&module, "same_name", DummyCallback, &proc), MGP_ERROR_NO_ERROR); EXPECT_NE(module.procedures.find("same_name"), module.procedures.end()); - EXPECT_FALSE(mgp_module_add_read_procedure(&module, "same_name", DummyCallback)); - EXPECT_FALSE(mgp_module_add_read_procedure(&module, "same_name", DummyCallback)); + EXPECT_EQ(mgp_module_add_read_procedure(&module, "same_name", DummyCallback, &proc), MGP_ERROR_LOGIC_ERROR); + EXPECT_EQ(mgp_module_add_read_procedure(&module, "same_name", DummyCallback, &proc), MGP_ERROR_LOGIC_ERROR); EXPECT_NE(module.procedures.find("same_name"), module.procedures.end()); } TEST(Module, CaseSensitiveProcedureNames) { mgp_module module(utils::NewDeleteResource()); EXPECT_TRUE(module.procedures.empty()); - EXPECT_TRUE(mgp_module_add_read_procedure(&module, "not_same", DummyCallback)); - EXPECT_TRUE(mgp_module_add_read_procedure(&module, "NoT_saME", DummyCallback)); - EXPECT_TRUE(mgp_module_add_read_procedure(&module, "NOT_SAME", DummyCallback)); + mgp_proc *proc{nullptr}; + EXPECT_EQ(mgp_module_add_read_procedure(&module, "not_same", DummyCallback, &proc), MGP_ERROR_NO_ERROR); + EXPECT_EQ(mgp_module_add_read_procedure(&module, "NoT_saME", DummyCallback, &proc), MGP_ERROR_NO_ERROR); + EXPECT_EQ(mgp_module_add_read_procedure(&module, "NOT_SAME", DummyCallback, &proc), MGP_ERROR_NO_ERROR); EXPECT_EQ(module.procedures.size(), 3U); } @@ -51,29 +59,42 @@ static void CheckSignature(const mgp_proc *proc, const std::string &expected) { TEST(Module, ProcedureSignature) { mgp_memory memory{utils::NewDeleteResource()}; mgp_module module(utils::NewDeleteResource()); - auto *proc = mgp_module_add_read_procedure(&module, "proc", DummyCallback); + auto *proc = EXPECT_MGP_NO_ERROR(mgp_proc *, mgp_module_add_read_procedure, &module, "proc", &DummyCallback); CheckSignature(proc, "proc() :: ()"); - mgp_proc_add_arg(proc, "arg1", mgp_type_number()); + EXPECT_EQ(mgp_proc_add_arg(proc, "arg1", EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number)), MGP_ERROR_NO_ERROR); CheckSignature(proc, "proc(arg1 :: NUMBER) :: ()"); - mgp_proc_add_opt_arg(proc, "opt1", mgp_type_nullable(mgp_type_any()), - test_utils::CreateValueOwningPtr(mgp_value_make_null(&memory)).get()); + EXPECT_EQ( + mgp_proc_add_opt_arg( + proc, "opt1", + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any)), + test_utils::CreateValueOwningPtr(EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_null, &memory)).get()), + MGP_ERROR_NO_ERROR); CheckSignature(proc, "proc(arg1 :: NUMBER, opt1 = Null :: ANY?) :: ()"); - mgp_proc_add_result(proc, "res1", mgp_type_list(mgp_type_int())); + EXPECT_EQ(mgp_proc_add_result(proc, "res1", + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_list, + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int))), + MGP_ERROR_NO_ERROR); CheckSignature(proc, "proc(arg1 :: NUMBER, opt1 = Null :: ANY?) :: (res1 :: LIST OF INTEGER)"); - EXPECT_FALSE(mgp_proc_add_arg(proc, "arg2", mgp_type_number())); + EXPECT_EQ(mgp_proc_add_arg(proc, "arg2", EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number)), + MGP_ERROR_LOGIC_ERROR); CheckSignature(proc, "proc(arg1 :: NUMBER, opt1 = Null :: ANY?) :: (res1 :: LIST OF INTEGER)"); - EXPECT_FALSE(mgp_proc_add_arg(proc, "arg2", mgp_type_map())); - CheckSignature(proc, - "proc(arg1 :: NUMBER, opt1 = Null :: ANY?) :: " - "(res1 :: LIST OF INTEGER)"); - mgp_proc_add_deprecated_result(proc, "res2", mgp_type_string()); + EXPECT_EQ(mgp_proc_add_arg(proc, "arg2", EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map)), MGP_ERROR_LOGIC_ERROR); + CheckSignature(proc, "proc(arg1 :: NUMBER, opt1 = Null :: ANY?) :: (res1 :: LIST OF INTEGER)"); + EXPECT_EQ(mgp_proc_add_deprecated_result(proc, "res2", EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string)), + MGP_ERROR_NO_ERROR); CheckSignature(proc, "proc(arg1 :: NUMBER, opt1 = Null :: ANY?) :: " "(res1 :: LIST OF INTEGER, DEPRECATED res2 :: STRING)"); - EXPECT_FALSE(mgp_proc_add_result(proc, "res2", mgp_type_any())); - EXPECT_FALSE(mgp_proc_add_deprecated_result(proc, "res1", mgp_type_any())); - mgp_proc_add_opt_arg(proc, "opt2", mgp_type_string(), - test_utils::CreateValueOwningPtr(mgp_value_make_string("string=\"value\"", &memory)).get()); + EXPECT_EQ(mgp_proc_add_result(proc, "res2", EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any)), + MGP_ERROR_LOGIC_ERROR); + EXPECT_EQ(mgp_proc_add_deprecated_result(proc, "res1", EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any)), + MGP_ERROR_LOGIC_ERROR); + EXPECT_EQ( + mgp_proc_add_opt_arg(proc, "opt2", EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string), + test_utils::CreateValueOwningPtr( + EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_string, "string=\"value\"", &memory)) + .get()), + MGP_ERROR_NO_ERROR); CheckSignature(proc, "proc(arg1 :: NUMBER, opt1 = Null :: ANY?, " "opt2 = \"string=\\\"value\\\"\" :: STRING) :: " @@ -83,8 +104,12 @@ TEST(Module, ProcedureSignature) { TEST(Module, ProcedureSignatureOnlyOptArg) { mgp_memory memory{utils::NewDeleteResource()}; mgp_module module(utils::NewDeleteResource()); - auto *proc = mgp_module_add_read_procedure(&module, "proc", DummyCallback); - mgp_proc_add_opt_arg(proc, "opt1", mgp_type_nullable(mgp_type_any()), - test_utils::CreateValueOwningPtr(mgp_value_make_null(&memory)).get()); + auto *proc = EXPECT_MGP_NO_ERROR(mgp_proc *, mgp_module_add_read_procedure, &module, "proc", &DummyCallback); + EXPECT_EQ( + mgp_proc_add_opt_arg( + proc, "opt1", + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any)), + test_utils::CreateValueOwningPtr(EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_null, &memory)).get()), + MGP_ERROR_NO_ERROR); CheckSignature(proc, "proc(opt1 = Null :: ANY?) :: ()"); } diff --git a/tests/unit/query_procedure_mgp_type.cpp b/tests/unit/query_procedure_mgp_type.cpp index 588a35a85..3c1a46120 100644 --- a/tests/unit/query_procedure_mgp_type.cpp +++ b/tests/unit/query_procedure_mgp_type.cpp @@ -9,46 +9,63 @@ #include "test_utils.hpp" TEST(CypherType, PresentableNameSimpleTypes) { - EXPECT_EQ(mgp_type_any()->impl->GetPresentableName(), "ANY"); - EXPECT_EQ(mgp_type_bool()->impl->GetPresentableName(), "BOOLEAN"); - EXPECT_EQ(mgp_type_string()->impl->GetPresentableName(), "STRING"); - EXPECT_EQ(mgp_type_int()->impl->GetPresentableName(), "INTEGER"); - EXPECT_EQ(mgp_type_float()->impl->GetPresentableName(), "FLOAT"); - EXPECT_EQ(mgp_type_number()->impl->GetPresentableName(), "NUMBER"); - EXPECT_EQ(mgp_type_map()->impl->GetPresentableName(), "MAP"); - EXPECT_EQ(mgp_type_node()->impl->GetPresentableName(), "NODE"); - EXPECT_EQ(mgp_type_relationship()->impl->GetPresentableName(), "RELATIONSHIP"); - EXPECT_EQ(mgp_type_path()->impl->GetPresentableName(), "PATH"); + EXPECT_EQ(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any)->impl->GetPresentableName(), "ANY"); + EXPECT_EQ(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_bool)->impl->GetPresentableName(), "BOOLEAN"); + EXPECT_EQ(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string)->impl->GetPresentableName(), "STRING"); + EXPECT_EQ(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int)->impl->GetPresentableName(), "INTEGER"); + EXPECT_EQ(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_float)->impl->GetPresentableName(), "FLOAT"); + EXPECT_EQ(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number)->impl->GetPresentableName(), "NUMBER"); + EXPECT_EQ(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map)->impl->GetPresentableName(), "MAP"); + EXPECT_EQ(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_node)->impl->GetPresentableName(), "NODE"); + EXPECT_EQ(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_relationship)->impl->GetPresentableName(), "RELATIONSHIP"); + EXPECT_EQ(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path)->impl->GetPresentableName(), "PATH"); } TEST(CypherType, PresentableNameCompositeTypes) { + const mgp_type *any_type = EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any); { - const auto *nullable_any = mgp_type_nullable(mgp_type_any()); + const auto *nullable_any = EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, any_type); EXPECT_EQ(nullable_any->impl->GetPresentableName(), "ANY?"); } { - const auto *nullable_any = mgp_type_nullable(mgp_type_nullable(mgp_type_any())); + const auto *nullable_any = + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, any_type))); EXPECT_EQ(nullable_any->impl->GetPresentableName(), "ANY?"); } { - const auto *nullable_list = mgp_type_nullable(mgp_type_list(mgp_type_any())); + const auto *nullable_list = EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_list, any_type)); EXPECT_EQ(nullable_list->impl->GetPresentableName(), "LIST? OF ANY"); } { - const auto *list_of_int = mgp_type_list(mgp_type_int()); + const auto *list_of_int = + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_list, EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int)); EXPECT_EQ(list_of_int->impl->GetPresentableName(), "LIST OF INTEGER"); } { - const auto *list_of_nullable_path = mgp_type_list(mgp_type_nullable(mgp_type_path())); + const auto *list_of_nullable_path = EXPECT_MGP_NO_ERROR( + const mgp_type *, mgp_type_list, + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path))); EXPECT_EQ(list_of_nullable_path->impl->GetPresentableName(), "LIST OF PATH?"); } { - const auto *list_of_list_of_map = mgp_type_list(mgp_type_list(mgp_type_map())); + const auto *list_of_list_of_map = EXPECT_MGP_NO_ERROR( + const mgp_type *, mgp_type_list, + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_list, EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map))); EXPECT_EQ(list_of_list_of_map->impl->GetPresentableName(), "LIST OF LIST OF MAP"); } { - const auto *nullable_list_of_nullable_list_of_nullable_string = - mgp_type_nullable(mgp_type_list(mgp_type_nullable(mgp_type_list(mgp_type_nullable(mgp_type_string()))))); + const auto *nullable_list_of_nullable_list_of_nullable_string = EXPECT_MGP_NO_ERROR( + const mgp_type *, mgp_type_nullable, + EXPECT_MGP_NO_ERROR( + const mgp_type *, mgp_type_list, + EXPECT_MGP_NO_ERROR( + const mgp_type *, mgp_type_nullable, + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_list, + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string)))))); EXPECT_EQ(nullable_list_of_nullable_list_of_nullable_string->impl->GetPresentableName(), "LIST? OF LIST? OF STRING?"); } @@ -57,17 +74,26 @@ TEST(CypherType, PresentableNameCompositeTypes) { TEST(CypherType, NullSatisfiesType) { mgp_memory memory{utils::NewDeleteResource()}; { - auto *mgp_null = mgp_value_make_null(&memory); + auto *mgp_null = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_null, &memory); const query::TypedValue tv_null; - std::vector primitive_types{ - mgp_type_any(), mgp_type_bool(), mgp_type_string(), mgp_type_int(), mgp_type_float(), - mgp_type_number(), mgp_type_map(), mgp_type_node(), mgp_type_relationship(), mgp_type_path()}; + std::vector primitive_types{EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_bool), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_float), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_node), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_relationship), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path)}; for (const auto *primitive_type : primitive_types) { for (const auto *type : - {primitive_type, mgp_type_list(primitive_type), mgp_type_list(mgp_type_nullable(primitive_type))}) { + {primitive_type, EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_list, primitive_type), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_list, + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, primitive_type))}) { EXPECT_FALSE(type->impl->SatisfiesType(*mgp_null)); EXPECT_FALSE(type->impl->SatisfiesType(tv_null)); - const auto *null_type = mgp_type_nullable(type); + const auto *null_type = EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, type); EXPECT_TRUE(null_type->impl->SatisfiesType(*mgp_null)); EXPECT_TRUE(null_type->impl->SatisfiesType(tv_null)); } @@ -81,7 +107,7 @@ static void CheckSatisfiesTypesAndNullable(const mgp_value *mgp_val, const query for (const auto *type : types) { EXPECT_TRUE(type->impl->SatisfiesType(*mgp_val)) << type->impl->GetPresentableName(); EXPECT_TRUE(type->impl->SatisfiesType(tv)); - const auto *null_type = mgp_type_nullable(type); + const auto *null_type = EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, type); EXPECT_TRUE(null_type->impl->SatisfiesType(*mgp_val)) << null_type->impl->GetPresentableName(); EXPECT_TRUE(null_type->impl->SatisfiesType(tv)); } @@ -90,10 +116,13 @@ static void CheckSatisfiesTypesAndNullable(const mgp_value *mgp_val, const query static void CheckNotSatisfiesTypesAndListAndNullable(const mgp_value *mgp_val, const query::TypedValue &tv, const std::vector &elem_types) { for (const auto *elem_type : elem_types) { - for (const auto *type : {elem_type, mgp_type_list(elem_type), mgp_type_list(mgp_type_nullable(elem_type))}) { + for (const auto *type : + {elem_type, EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_list, elem_type), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_list, + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, elem_type))}) { EXPECT_FALSE(type->impl->SatisfiesType(*mgp_val)) << type->impl->GetPresentableName(); EXPECT_FALSE(type->impl->SatisfiesType(tv)); - const auto *null_type = mgp_type_nullable(type); + const auto *null_type = EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, type); EXPECT_FALSE(null_type->impl->SatisfiesType(*mgp_val)) << null_type->impl->GetPresentableName(); EXPECT_FALSE(null_type->impl->SatisfiesType(tv)); } @@ -102,59 +131,94 @@ static void CheckNotSatisfiesTypesAndListAndNullable(const mgp_value *mgp_val, c TEST(CypherType, BoolSatisfiesType) { mgp_memory memory{utils::NewDeleteResource()}; - auto *mgp_bool = mgp_value_make_bool(1, &memory); + auto *mgp_bool = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_bool, 1, &memory); const query::TypedValue tv_bool(true); - CheckSatisfiesTypesAndNullable(mgp_bool, tv_bool, {mgp_type_any(), mgp_type_bool()}); - CheckNotSatisfiesTypesAndListAndNullable(mgp_bool, tv_bool, - {mgp_type_string(), mgp_type_int(), mgp_type_float(), mgp_type_number(), - mgp_type_map(), mgp_type_node(), mgp_type_relationship(), mgp_type_path()}); + CheckSatisfiesTypesAndNullable( + mgp_bool, tv_bool, + {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_bool)}); + CheckNotSatisfiesTypesAndListAndNullable( + mgp_bool, tv_bool, + {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_float), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_node), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_relationship), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path)}); mgp_value_destroy(mgp_bool); } TEST(CypherType, IntSatisfiesType) { mgp_memory memory{utils::NewDeleteResource()}; - auto *mgp_int = mgp_value_make_int(42, &memory); + auto *mgp_int = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_int, 42, &memory); const query::TypedValue tv_int(42); - CheckSatisfiesTypesAndNullable(mgp_int, tv_int, {mgp_type_any(), mgp_type_int(), mgp_type_number()}); - CheckNotSatisfiesTypesAndListAndNullable(mgp_int, tv_int, - {mgp_type_bool(), mgp_type_string(), mgp_type_float(), mgp_type_map(), - mgp_type_node(), mgp_type_relationship(), mgp_type_path()}); + CheckSatisfiesTypesAndNullable( + mgp_int, tv_int, + {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number)}); + CheckNotSatisfiesTypesAndListAndNullable( + mgp_int, tv_int, + {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_bool), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_float), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_node), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_relationship), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path)}); mgp_value_destroy(mgp_int); } TEST(CypherType, DoubleSatisfiesType) { mgp_memory memory{utils::NewDeleteResource()}; - auto *mgp_double = mgp_value_make_double(42, &memory); + auto *mgp_double = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_double, 42, &memory); const query::TypedValue tv_double(42.0); - CheckSatisfiesTypesAndNullable(mgp_double, tv_double, {mgp_type_any(), mgp_type_float(), mgp_type_number()}); - CheckNotSatisfiesTypesAndListAndNullable(mgp_double, tv_double, - {mgp_type_bool(), mgp_type_string(), mgp_type_int(), mgp_type_map(), - mgp_type_node(), mgp_type_relationship(), mgp_type_path()}); + CheckSatisfiesTypesAndNullable( + mgp_double, tv_double, + {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_float), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number)}); + CheckNotSatisfiesTypesAndListAndNullable( + mgp_double, tv_double, + {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_bool), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_node), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_relationship), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path)}); mgp_value_destroy(mgp_double); } TEST(CypherType, StringSatisfiesType) { mgp_memory memory{utils::NewDeleteResource()}; - auto *mgp_string = mgp_value_make_string("text", &memory); + auto *mgp_string = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_string, "text", &memory); const query::TypedValue tv_string("text"); - CheckSatisfiesTypesAndNullable(mgp_string, tv_string, {mgp_type_any(), mgp_type_string()}); - CheckNotSatisfiesTypesAndListAndNullable(mgp_string, tv_string, - {mgp_type_bool(), mgp_type_int(), mgp_type_float(), mgp_type_number(), - mgp_type_map(), mgp_type_node(), mgp_type_relationship(), mgp_type_path()}); + CheckSatisfiesTypesAndNullable( + mgp_string, tv_string, + {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string)}); + CheckNotSatisfiesTypesAndListAndNullable( + mgp_string, tv_string, + {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_bool), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_float), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_node), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_relationship), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path)}); mgp_value_destroy(mgp_string); } TEST(CypherType, MapSatisfiesType) { mgp_memory memory{utils::NewDeleteResource()}; - auto *map = mgp_map_make_empty(&memory); - mgp_map_insert(map, "key", test_utils::CreateValueOwningPtr(mgp_value_make_int(42, &memory)).get()); - auto *mgp_map_v = mgp_value_make_map(map); + auto *map = EXPECT_MGP_NO_ERROR(mgp_map *, mgp_map_make_empty, &memory); + EXPECT_EQ( + mgp_map_insert( + map, "key", + test_utils::CreateValueOwningPtr(EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_int, 42, &memory)).get()), + MGP_ERROR_NO_ERROR); + auto *mgp_map_v = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_map, map); const query::TypedValue tv_map(std::map{{"key", query::TypedValue(42)}}); - CheckSatisfiesTypesAndNullable(mgp_map_v, tv_map, {mgp_type_any(), mgp_type_map()}); + CheckSatisfiesTypesAndNullable( + mgp_map_v, tv_map, + {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map)}); CheckNotSatisfiesTypesAndListAndNullable( mgp_map_v, tv_map, - {mgp_type_bool(), mgp_type_string(), mgp_type_int(), mgp_type_float(), mgp_type_number(), mgp_type_node(), - mgp_type_relationship(), mgp_type_path()}); + {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_bool), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_float), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_node), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_relationship), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path)}); mgp_value_destroy(mgp_map_v); } @@ -166,12 +230,20 @@ TEST(CypherType, VertexSatisfiesType) { mgp_memory memory{utils::NewDeleteResource()}; utils::Allocator alloc(memory.impl); mgp_graph graph{&dba, storage::View::NEW}; - auto *mgp_vertex_v = mgp_value_make_vertex(alloc.new_object(vertex, &graph)); + auto *mgp_vertex_v = + EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_vertex, alloc.new_object(vertex, &graph)); const query::TypedValue tv_vertex(vertex); - CheckSatisfiesTypesAndNullable(mgp_vertex_v, tv_vertex, {mgp_type_any(), mgp_type_node(), mgp_type_map()}); - CheckNotSatisfiesTypesAndListAndNullable(mgp_vertex_v, tv_vertex, - {mgp_type_bool(), mgp_type_string(), mgp_type_int(), mgp_type_float(), - mgp_type_number(), mgp_type_relationship(), mgp_type_path()}); + CheckSatisfiesTypesAndNullable( + mgp_vertex_v, tv_vertex, + {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_node), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map)}); + CheckNotSatisfiesTypesAndListAndNullable( + mgp_vertex_v, tv_vertex, + {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_bool), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_float), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_relationship), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path)}); mgp_value_destroy(mgp_vertex_v); } @@ -185,12 +257,18 @@ TEST(CypherType, EdgeSatisfiesType) { mgp_memory memory{utils::NewDeleteResource()}; utils::Allocator alloc(memory.impl); mgp_graph graph{&dba, storage::View::NEW}; - auto *mgp_edge_v = mgp_value_make_edge(alloc.new_object(edge, &graph)); + auto *mgp_edge_v = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_edge, alloc.new_object(edge, &graph)); const query::TypedValue tv_edge(edge); - CheckSatisfiesTypesAndNullable(mgp_edge_v, tv_edge, {mgp_type_any(), mgp_type_relationship(), mgp_type_map()}); - CheckNotSatisfiesTypesAndListAndNullable(mgp_edge_v, tv_edge, - {mgp_type_bool(), mgp_type_string(), mgp_type_int(), mgp_type_float(), - mgp_type_number(), mgp_type_node(), mgp_type_path()}); + CheckSatisfiesTypesAndNullable(mgp_edge_v, tv_edge, + {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_relationship), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map)}); + CheckNotSatisfiesTypesAndListAndNullable( + mgp_edge_v, tv_edge, + {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_bool), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_float), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_node), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path)}); mgp_value_destroy(mgp_edge_v); } @@ -205,19 +283,24 @@ TEST(CypherType, PathSatisfiesType) { utils::Allocator alloc(memory.impl); mgp_graph graph{&dba, storage::View::NEW}; auto *mgp_vertex_v = alloc.new_object(v1, &graph); - auto path = mgp_path_make_with_start(mgp_vertex_v, &memory); + auto path = EXPECT_MGP_NO_ERROR(mgp_path *, mgp_path_make_with_start, mgp_vertex_v, &memory); ASSERT_TRUE(path); alloc.delete_object(mgp_vertex_v); auto mgp_edge_v = alloc.new_object(edge, &graph); - ASSERT_TRUE(mgp_path_expand(path, mgp_edge_v)); + ASSERT_EQ(mgp_path_expand(path, mgp_edge_v), MGP_ERROR_NO_ERROR); alloc.delete_object(mgp_edge_v); - auto *mgp_path_v = mgp_value_make_path(path); + auto *mgp_path_v = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_path, path); const query::TypedValue tv_path(query::Path(v1, edge, v2)); - CheckSatisfiesTypesAndNullable(mgp_path_v, tv_path, {mgp_type_any(), mgp_type_path()}); + CheckSatisfiesTypesAndNullable( + mgp_path_v, tv_path, + {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path)}); CheckNotSatisfiesTypesAndListAndNullable( mgp_path_v, tv_path, - {mgp_type_bool(), mgp_type_string(), mgp_type_int(), mgp_type_float(), mgp_type_number(), mgp_type_map(), - mgp_type_node(), mgp_type_relationship()}); + {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_bool), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_float), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_node), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_relationship)}); mgp_value_destroy(mgp_path_v); } @@ -225,23 +308,31 @@ static std::vector MakeListTypes(const std::vector list_types; list_types.reserve(2U * element_types.size()); for (const auto *type : element_types) { - list_types.push_back(mgp_type_list(type)); - list_types.push_back(mgp_type_list(mgp_type_nullable(type))); + list_types.push_back(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_list, type)); + list_types.push_back(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_list, + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, type))); } return list_types; } TEST(CypherType, EmptyListSatisfiesType) { mgp_memory memory{utils::NewDeleteResource()}; - auto *list = mgp_list_make_empty(0, &memory); - auto *mgp_list_v = mgp_value_make_list(list); + auto *list = EXPECT_MGP_NO_ERROR(mgp_list *, mgp_list_make_empty, 0, &memory); + auto *mgp_list_v = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_list, list); query::TypedValue tv_list(std::vector{}); // Empty List satisfies all list element types - std::vector primitive_types{ - mgp_type_any(), mgp_type_bool(), mgp_type_string(), mgp_type_int(), mgp_type_float(), - mgp_type_number(), mgp_type_map(), mgp_type_node(), mgp_type_relationship(), mgp_type_path()}; + std::vector primitive_types{EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_bool), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_float), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_node), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_relationship), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path)}; auto all_types = MakeListTypes(primitive_types); - all_types.push_back(mgp_type_any()); + all_types.push_back(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any)); CheckSatisfiesTypesAndNullable(mgp_list_v, tv_list, all_types); mgp_value_destroy(mgp_list_v); } @@ -249,18 +340,28 @@ TEST(CypherType, EmptyListSatisfiesType) { TEST(CypherType, ListOfIntSatisfiesType) { mgp_memory memory{utils::NewDeleteResource()}; constexpr int64_t elem_count = 3; - auto *list = mgp_list_make_empty(elem_count, &memory); - auto *mgp_list_v = mgp_value_make_list(list); + auto *list = EXPECT_MGP_NO_ERROR(mgp_list *, mgp_list_make_empty, elem_count, &memory); + auto *mgp_list_v = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_list, list); query::TypedValue tv_list(std::vector{}); for (int64_t i = 0; i < elem_count; ++i) { - ASSERT_TRUE(mgp_list_append(list, test_utils::CreateValueOwningPtr(mgp_value_make_int(i, &memory)).get())); + ASSERT_EQ( + mgp_list_append( + list, + test_utils::CreateValueOwningPtr(EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_int, i, &memory)).get()), + MGP_ERROR_NO_ERROR); tv_list.ValueList().emplace_back(i); - auto valid_types = MakeListTypes({mgp_type_any(), mgp_type_int(), mgp_type_number()}); - valid_types.push_back(mgp_type_any()); + auto valid_types = MakeListTypes({EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number)}); + valid_types.push_back(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any)); CheckSatisfiesTypesAndNullable(mgp_list_v, tv_list, valid_types); - CheckNotSatisfiesTypesAndListAndNullable(mgp_list_v, tv_list, - {mgp_type_bool(), mgp_type_string(), mgp_type_float(), mgp_type_map(), - mgp_type_node(), mgp_type_relationship(), mgp_type_path()}); + CheckNotSatisfiesTypesAndListAndNullable( + mgp_list_v, tv_list, + {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_bool), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_float), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_node), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_relationship), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path)}); } mgp_value_destroy(mgp_list_v); } @@ -268,52 +369,75 @@ TEST(CypherType, ListOfIntSatisfiesType) { TEST(CypherType, ListOfIntAndBoolSatisfiesType) { mgp_memory memory{utils::NewDeleteResource()}; constexpr int64_t elem_count = 2; - auto *list = mgp_list_make_empty(elem_count, &memory); - auto *mgp_list_v = mgp_value_make_list(list); + auto *list = EXPECT_MGP_NO_ERROR(mgp_list *, mgp_list_make_empty, elem_count, &memory); + auto *mgp_list_v = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_list, list); query::TypedValue tv_list(std::vector{}); // Add an int - ASSERT_TRUE(mgp_list_append(list, test_utils::CreateValueOwningPtr(mgp_value_make_int(42, &memory)).get())); + ASSERT_EQ( + mgp_list_append( + list, + test_utils::CreateValueOwningPtr(EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_int, 42, &memory)).get()), + MGP_ERROR_NO_ERROR); tv_list.ValueList().emplace_back(42); // Add a boolean - ASSERT_TRUE(mgp_list_append(list, test_utils::CreateValueOwningPtr(mgp_value_make_bool(1, &memory)).get())); + ASSERT_EQ( + mgp_list_append( + list, + test_utils::CreateValueOwningPtr(EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_bool, 1, &memory)).get()), + MGP_ERROR_NO_ERROR); tv_list.ValueList().emplace_back(true); - auto valid_types = MakeListTypes({mgp_type_any()}); - valid_types.push_back(mgp_type_any()); + auto valid_types = MakeListTypes({EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any)}); + valid_types.push_back(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any)); CheckSatisfiesTypesAndNullable(mgp_list_v, tv_list, valid_types); // All other types will not be satisfied CheckNotSatisfiesTypesAndListAndNullable( mgp_list_v, tv_list, - {mgp_type_bool(), mgp_type_string(), mgp_type_int(), mgp_type_float(), mgp_type_number(), mgp_type_map(), - mgp_type_node(), mgp_type_relationship(), mgp_type_path()}); + {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_bool), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_float), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_node), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_relationship), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path)}); mgp_value_destroy(mgp_list_v); } TEST(CypherType, ListOfNullSatisfiesType) { mgp_memory memory{utils::NewDeleteResource()}; - auto *list = mgp_list_make_empty(1, &memory); - auto *mgp_list_v = mgp_value_make_list(list); + auto *list = EXPECT_MGP_NO_ERROR(mgp_list *, mgp_list_make_empty, 1, &memory); + auto *mgp_list_v = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_list, list); query::TypedValue tv_list(std::vector{}); - ASSERT_TRUE(mgp_list_append(list, test_utils::CreateValueOwningPtr(mgp_value_make_null(&memory)).get())); + ASSERT_EQ( + mgp_list_append( + list, test_utils::CreateValueOwningPtr(EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_null, &memory)).get()), + MGP_ERROR_NO_ERROR); tv_list.ValueList().emplace_back(); // List with Null satisfies all nullable list element types - std::vector primitive_types{ - mgp_type_any(), mgp_type_bool(), mgp_type_string(), mgp_type_int(), mgp_type_float(), - mgp_type_number(), mgp_type_map(), mgp_type_node(), mgp_type_relationship(), mgp_type_path()}; - std::vector valid_types{mgp_type_any()}; + std::vector primitive_types{EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_bool), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_float), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_node), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_relationship), + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path)}; + std::vector valid_types{EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any)}; valid_types.reserve(1U + primitive_types.size()); for (const auto *elem_type : primitive_types) { - valid_types.push_back(mgp_type_list(mgp_type_nullable(elem_type))); + valid_types.push_back(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_list, + EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, elem_type))); } CheckSatisfiesTypesAndNullable(mgp_list_v, tv_list, valid_types); std::vector invalid_types; invalid_types.reserve(primitive_types.size()); for (const auto *elem_type : primitive_types) { - invalid_types.push_back(mgp_type_list(elem_type)); + invalid_types.push_back(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_list, elem_type)); } for (const auto *type : invalid_types) { EXPECT_FALSE(type->impl->SatisfiesType(*mgp_list_v)) << type->impl->GetPresentableName(); EXPECT_FALSE(type->impl->SatisfiesType(tv_list)); - const auto *null_type = mgp_type_nullable(type); + const auto *null_type = EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, type); EXPECT_FALSE(null_type->impl->SatisfiesType(*mgp_list_v)) << null_type->impl->GetPresentableName(); EXPECT_FALSE(null_type->impl->SatisfiesType(tv_list)); } diff --git a/tests/unit/query_procedure_py_module.cpp b/tests/unit/query_procedure_py_module.cpp index 95a7af2a1..9602c2972 100644 --- a/tests/unit/query_procedure_py_module.cpp +++ b/tests/unit/query_procedure_py_module.cpp @@ -5,25 +5,29 @@ #include "query/procedure/mg_procedure_impl.hpp" #include "query/procedure/py_module.hpp" +#include "test_utils.hpp" TEST(PyModule, MgpValueToPyObject) { mgp_memory memory{utils::NewDeleteResource()}; - auto *list = mgp_list_make_empty(42, &memory); + auto *list = EXPECT_MGP_NO_ERROR(mgp_list *, mgp_list_make_empty, 42, &memory); { // Create a list: [null, false, true, 42, 0.1, "some text"] - auto primitive_values = {mgp_value_make_null(&memory), mgp_value_make_bool(0, &memory), - mgp_value_make_bool(1, &memory), mgp_value_make_int(42, &memory), - mgp_value_make_double(0.1, &memory), mgp_value_make_string("some text", &memory)}; + auto primitive_values = {EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_null, &memory), + EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_bool, 0, &memory), + EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_bool, 1, &memory), + EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_int, 42, &memory), + EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_double, 0.1, &memory), + EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_string, "some text", &memory)}; for (auto *val : primitive_values) { - mgp_list_append(list, val); + EXPECT_EQ(mgp_list_append(list, val), MGP_ERROR_NO_ERROR); mgp_value_destroy(val); } } - auto *list_val = mgp_value_make_list(list); - auto *map = mgp_map_make_empty(&memory); - mgp_map_insert(map, "list", list_val); + auto *list_val = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_list, list); + auto *map = EXPECT_MGP_NO_ERROR(mgp_map *, mgp_map_make_empty, &memory); + EXPECT_EQ(mgp_map_insert(map, "list", list_val), MGP_ERROR_NO_ERROR); mgp_value_destroy(list_val); - auto *map_val = mgp_value_make_map(map); + auto *map_val = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_map, map); auto gil = py::EnsureGIL(); py::Object py_graph(query::procedure::MakePyGraph(nullptr, &memory)); auto py_dict = @@ -99,9 +103,10 @@ TEST(PyModule, PyVertex) { query::DbAccessor dba(&storage_dba); mgp_memory memory{utils::NewDeleteResource()}; mgp_graph graph{&dba, storage::View::OLD}; - auto *vertex = mgp_graph_get_vertex_by_id(&graph, mgp_vertex_id{0}, &memory); + auto *vertex = EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, mgp_vertex_id{0}, &memory); ASSERT_TRUE(vertex); - auto *vertex_value = mgp_value_make_vertex(mgp_vertex_copy(vertex, &memory)); + auto *vertex_value = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_vertex, + EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_vertex_copy, vertex, &memory)); mgp_vertex_destroy(vertex); // Initialize the Python graph object. auto gil = py::EnsureGIL(); @@ -116,8 +121,11 @@ TEST(PyModule, PyVertex) { // Test for equality. ASSERT_TRUE(new_vertex_value); ASSERT_NE(new_vertex_value, vertex_value); // Pointer compare. - ASSERT_TRUE(mgp_value_is_vertex(new_vertex_value)); - ASSERT_TRUE(mgp_vertex_equal(mgp_value_get_vertex(vertex_value), mgp_value_get_vertex(new_vertex_value))); + ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_value_is_vertex, new_vertex_value), 1); + ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_vertex_equal, + EXPECT_MGP_NO_ERROR(const mgp_vertex *, mgp_value_get_vertex, vertex_value), + EXPECT_MGP_NO_ERROR(const mgp_vertex *, mgp_value_get_vertex, new_vertex_value)), + 1); // Clean up. mgp_value_destroy(new_vertex_value); mgp_value_destroy(vertex_value); @@ -144,14 +152,15 @@ TEST(PyModule, PyEdge) { query::DbAccessor dba(&storage_dba); mgp_memory memory{utils::NewDeleteResource()}; mgp_graph graph{&dba, storage::View::OLD}; - auto *start_v = mgp_graph_get_vertex_by_id(&graph, mgp_vertex_id{0}, &memory); + auto *start_v = EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, mgp_vertex_id{0}, &memory); ASSERT_TRUE(start_v); - auto *edges_it = mgp_vertex_iter_out_edges(start_v, &memory); + auto *edges_it = EXPECT_MGP_NO_ERROR(mgp_edges_iterator *, mgp_vertex_iter_out_edges, start_v, &memory); ASSERT_TRUE(edges_it); - auto *edge = mgp_edge_copy(mgp_edges_iterator_get(edges_it), &memory); - auto *edge_value = mgp_value_make_edge(edge); - mgp_edges_iterator_next(edges_it); - ASSERT_EQ(mgp_edges_iterator_get(edges_it), nullptr); + auto *edge = EXPECT_MGP_NO_ERROR(mgp_edge *, mgp_edge_copy, + EXPECT_MGP_NO_ERROR(const mgp_edge *, mgp_edges_iterator_get, edges_it), &memory); + auto *edge_value = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_edge, edge); + ASSERT_EQ(EXPECT_MGP_NO_ERROR(const mgp_edge *, mgp_edges_iterator_next, edges_it), nullptr); + ASSERT_EQ(EXPECT_MGP_NO_ERROR(const mgp_edge *, mgp_edges_iterator_get, edges_it), nullptr); mgp_edges_iterator_destroy(edges_it); mgp_vertex_destroy(start_v); // Initialize the Python graph object. @@ -167,8 +176,11 @@ TEST(PyModule, PyEdge) { // Test for equality. ASSERT_TRUE(new_edge_value); ASSERT_NE(new_edge_value, edge_value); // Pointer compare. - ASSERT_TRUE(mgp_value_is_edge(new_edge_value)); - ASSERT_TRUE(mgp_edge_equal(mgp_value_get_edge(edge_value), mgp_value_get_edge(new_edge_value))); + ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_value_is_edge, new_edge_value), 1); + ASSERT_EQ( + EXPECT_MGP_NO_ERROR(int, mgp_edge_equal, EXPECT_MGP_NO_ERROR(const mgp_edge *, mgp_value_get_edge, edge_value), + EXPECT_MGP_NO_ERROR(const mgp_edge *, mgp_value_get_edge, new_edge_value)), + 1); // Clean up. mgp_value_destroy(new_edge_value); mgp_value_destroy(edge_value); @@ -188,19 +200,20 @@ TEST(PyModule, PyPath) { query::DbAccessor dba(&storage_dba); mgp_memory memory{utils::NewDeleteResource()}; mgp_graph graph{&dba, storage::View::OLD}; - auto *start_v = mgp_graph_get_vertex_by_id(&graph, mgp_vertex_id{0}, &memory); + auto *start_v = EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, mgp_vertex_id{0}, &memory); ASSERT_TRUE(start_v); - auto *path = mgp_path_make_with_start(start_v, &memory); + auto *path = EXPECT_MGP_NO_ERROR(mgp_path *, mgp_path_make_with_start, start_v, &memory); ASSERT_TRUE(path); - auto *edges_it = mgp_vertex_iter_out_edges(start_v, &memory); + auto *edges_it = EXPECT_MGP_NO_ERROR(mgp_edges_iterator *, mgp_vertex_iter_out_edges, start_v, &memory); ASSERT_TRUE(edges_it); - for (const auto *edge = mgp_edges_iterator_get(edges_it); edge; edge = mgp_edges_iterator_next(edges_it)) { - ASSERT_TRUE(mgp_path_expand(path, edge)); + for (const auto *edge = EXPECT_MGP_NO_ERROR(const mgp_edge *, mgp_edges_iterator_get, edges_it); edge != nullptr; + edge = EXPECT_MGP_NO_ERROR(const mgp_edge *, mgp_edges_iterator_next, edges_it)) { + ASSERT_EQ(mgp_path_expand(path, edge), MGP_ERROR_NO_ERROR); } - ASSERT_EQ(mgp_path_size(path), 1); + ASSERT_EQ(EXPECT_MGP_NO_ERROR(size_t, mgp_path_size, path), 1); mgp_edges_iterator_destroy(edges_it); mgp_vertex_destroy(start_v); - auto *path_value = mgp_value_make_path(path); + auto *path_value = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_path, path); ASSERT_TRUE(path_value); auto gil = py::EnsureGIL(); py::Object py_graph(query::procedure::MakePyGraph(&graph, &memory)); @@ -213,8 +226,11 @@ TEST(PyModule, PyPath) { auto *new_path_value = query::procedure::PyObjectToMgpValue(py_path_value.Ptr(), &memory); ASSERT_TRUE(new_path_value); ASSERT_NE(new_path_value, path_value); // Pointer compare. - ASSERT_TRUE(mgp_value_is_path(new_path_value)); - ASSERT_TRUE(mgp_path_equal(mgp_value_get_path(path_value), mgp_value_get_path(new_path_value))); + ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_value_is_path, new_path_value), 1); + ASSERT_EQ( + EXPECT_MGP_NO_ERROR(int, mgp_path_equal, EXPECT_MGP_NO_ERROR(const mgp_path *, mgp_value_get_path, path_value), + EXPECT_MGP_NO_ERROR(const mgp_path *, mgp_value_get_path, new_path_value)), + 1); mgp_value_destroy(new_path_value); mgp_value_destroy(path_value); ASSERT_FALSE(dba.Commit().HasError()); @@ -225,35 +241,57 @@ TEST(PyModule, PyObjectToMgpValue) { auto gil = py::EnsureGIL(); py::Object py_value{ Py_BuildValue("[i f s (i f s) {s i s f}]", 1, 1.0, "one", 2, 2.0, "two", "three", 3, "four", 4.0)}; - mgp_value *value = query::procedure::PyObjectToMgpValue(py_value.Ptr(), &memory); + auto *value = query::procedure::PyObjectToMgpValue(py_value.Ptr(), &memory); - ASSERT_TRUE(mgp_value_is_list(value)); - const mgp_list *list1 = mgp_value_get_list(value); - EXPECT_EQ(mgp_list_size(list1), 5); - ASSERT_TRUE(mgp_value_is_int(mgp_list_at(list1, 0))); - EXPECT_EQ(mgp_value_get_int(mgp_list_at(list1, 0)), 1); - ASSERT_TRUE(mgp_value_is_double(mgp_list_at(list1, 1))); - EXPECT_EQ(mgp_value_get_double(mgp_list_at(list1, 1)), 1.0); - ASSERT_TRUE(mgp_value_is_string(mgp_list_at(list1, 2))); - EXPECT_STREQ(mgp_value_get_string(mgp_list_at(list1, 2)), "one"); - ASSERT_TRUE(mgp_value_is_list(mgp_list_at(list1, 3))); - const mgp_list *list2 = mgp_value_get_list(mgp_list_at(list1, 3)); - EXPECT_EQ(mgp_list_size(list2), 3); - ASSERT_TRUE(mgp_value_is_int(mgp_list_at(list2, 0))); - EXPECT_EQ(mgp_value_get_int(mgp_list_at(list2, 0)), 2); - ASSERT_TRUE(mgp_value_is_double(mgp_list_at(list2, 1))); - EXPECT_EQ(mgp_value_get_double(mgp_list_at(list2, 1)), 2.0); - ASSERT_TRUE(mgp_value_is_string(mgp_list_at(list2, 2))); - EXPECT_STREQ(mgp_value_get_string(mgp_list_at(list2, 2)), "two"); - ASSERT_TRUE(mgp_value_is_map(mgp_list_at(list1, 4))); - const mgp_map *map = mgp_value_get_map(mgp_list_at(list1, 4)); - EXPECT_EQ(mgp_map_size(map), 2); - const mgp_value *v1 = mgp_map_at(map, "three"); - ASSERT_TRUE(mgp_value_is_int(v1)); - EXPECT_EQ(mgp_value_get_int(v1), 3); - const mgp_value *v2 = mgp_map_at(map, "four"); - ASSERT_TRUE(mgp_value_is_double(v2)); - EXPECT_EQ(mgp_value_get_double(v2), 4.0); + ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_value_is_list, value), 1); + auto *list1 = EXPECT_MGP_NO_ERROR(const mgp_list *, mgp_value_get_list, value); + EXPECT_EQ(EXPECT_MGP_NO_ERROR(size_t, mgp_list_size, list1), 5); + ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_value_is_int, EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list1, 0)), + 1); + EXPECT_EQ( + EXPECT_MGP_NO_ERROR(int64_t, mgp_value_get_int, EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list1, 0)), + 1); + ASSERT_EQ( + EXPECT_MGP_NO_ERROR(int, mgp_value_is_double, EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list1, 1)), 1); + EXPECT_EQ( + EXPECT_MGP_NO_ERROR(double, mgp_value_get_double, EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list1, 1)), + 1.0); + ASSERT_EQ( + EXPECT_MGP_NO_ERROR(int, mgp_value_is_string, EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list1, 2)), 1); + EXPECT_STREQ(EXPECT_MGP_NO_ERROR(const char *, mgp_value_get_string, + EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list1, 2)), + "one"); + ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_value_is_list, EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list1, 3)), + 1); + auto *list2 = EXPECT_MGP_NO_ERROR(const mgp_list *, mgp_value_get_list, + EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list1, 3)); + EXPECT_EQ(EXPECT_MGP_NO_ERROR(size_t, mgp_list_size, list2), 3); + ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_value_is_int, EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list2, 0)), + 1); + EXPECT_EQ( + EXPECT_MGP_NO_ERROR(int64_t, mgp_value_get_int, EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list2, 0)), + 2); + ASSERT_EQ( + EXPECT_MGP_NO_ERROR(int, mgp_value_is_double, EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list2, 1)), 1); + EXPECT_EQ( + EXPECT_MGP_NO_ERROR(double, mgp_value_get_double, EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list2, 1)), + 2.0); + ASSERT_EQ( + EXPECT_MGP_NO_ERROR(int, mgp_value_is_string, EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list2, 2)), 1); + EXPECT_STREQ(EXPECT_MGP_NO_ERROR(const char *, mgp_value_get_string, + EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list2, 2)), + "two"); + ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_value_is_map, EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list1, 4)), + 1); + auto *map = EXPECT_MGP_NO_ERROR(const mgp_map *, mgp_value_get_map, + EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list1, 4)); + EXPECT_EQ(EXPECT_MGP_NO_ERROR(size_t, mgp_map_size, map), 2); + const mgp_value *v1 = EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_map_at, map, "three"); + ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_value_is_int, v1), 1); + EXPECT_EQ(EXPECT_MGP_NO_ERROR(int64_t, mgp_value_get_int, v1), 3); + const mgp_value *v2 = EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_map_at, map, "four"); + ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_value_is_double, v2), 1); + EXPECT_EQ(EXPECT_MGP_NO_ERROR(double, mgp_value_get_double, v2), 4.0); mgp_value_destroy(value); } diff --git a/tests/unit/test_utils.hpp b/tests/unit/test_utils.hpp index 4efc0f4b2..36591d97e 100644 --- a/tests/unit/test_utils.hpp +++ b/tests/unit/test_utils.hpp @@ -1,9 +1,26 @@ +#pragma once + #include +#include +#include + +#include "mg_procedure.h" #include "query/procedure/mg_procedure_impl.hpp" namespace test_utils { using MgpValueOwningPtr = std::unique_ptr; MgpValueOwningPtr CreateValueOwningPtr(mgp_value *value) { return MgpValueOwningPtr(value, &mgp_value_destroy); } + +template +TResult ExpectNoError(const char *file, int line, TFunc func, TArgs &&...args) { + static_assert(std::is_trivially_copyable_v); + static_assert((std::is_trivially_copyable_v> && ...)); + TResult result{}; + EXPECT_EQ(func(args..., &result), MGP_ERROR_NO_ERROR) << fmt::format("Source of error: {} at line {}", file, line); + return result; +} } // namespace test_utils + +#define EXPECT_MGP_NO_ERROR(type, ...) test_utils::ExpectNoError(__FILE__, __LINE__, __VA_ARGS__) \ No newline at end of file