From cc01641e1858f8e135f1f149ea6647ee6e924cf9 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 22 Apr 2026 19:27:23 -0700 Subject: [PATCH] Cherrypick changes only related to aerospike_helpers to reduce scope of PR --- .../operations/list_operations.py | 48 +-- .../operations/map_operations.py | 28 +- src/include/conversions.h | 34 +- src/include/operate.h | 9 +- src/main/client/batch_operate.c | 5 +- src/main/client/batch_write.c | 5 +- src/main/client/cdt_operation_utils.c | 19 +- src/main/client/operate.c | 80 +++-- src/main/conversions.c | 340 ++++++++++++++---- src/main/query/add_ops.c | 7 +- src/main/scan/add_ops.c | 7 +- 11 files changed, 411 insertions(+), 171 deletions(-) diff --git a/aerospike_helpers/operations/list_operations.py b/aerospike_helpers/operations/list_operations.py index 238929bd4a..028723db0f 100755 --- a/aerospike_helpers/operations/list_operations.py +++ b/aerospike_helpers/operations/list_operations.py @@ -147,8 +147,8 @@ def list_insert(bin_name: str, index, value, policy: Optional[dict] = None, ctx: Args: bin_name (str): The name of the bin to be operated on. - index (int): The index at which to insert an item. The value may be positive to use - zero based indexing or negative to index from the end of the list. + index (int): The index at which to insert an item. (64-bit signed integer) The value may be + positive to use zero based indexing or negative to index from the end of the list. value: The value to be inserted into the list. policy (dict): An optional dictionary of :ref:`list write options `. ctx (list): An optional list of nested CDT :class:`cdt_ctx ` context operation @@ -177,8 +177,8 @@ def list_insert_items(bin_name: str, index, values, policy: Optional[dict] = Non Args: bin_name (str): The name of the bin to be operated on. - index (int): The index at which to insert the items. The value may be positive to use - zero based indexing or negative to index from the end of the list. + index (int): The index at which to insert the items. (64-bit signed integer) The value may be + positive to use zero based indexing or negative to index from the end of the list. values (list): The values to be inserted into the list. policy (dict): An optional dictionary of :ref:`list write options `. ctx (list): An optional list of nested CDT :class:`cdt_ctx ` context operation @@ -207,7 +207,7 @@ def list_increment(bin_name: str, index, value, policy: Optional[dict] = None, c Args: bin_name (str): The name of the bin to be operated on. - index (int): The index of the list item to increment. + index (int): The index of the list item to increment. (64-bit signed integer) value (int, float) : The value to be added to the list item. policy (dict): An optional dictionary of :ref:`list write options `. ctx (list): An optional list of nested CDT :class:`cdt_ctx ` context operation @@ -236,7 +236,7 @@ def list_pop(bin_name: str, index, ctx: Optional[list] = None): Args: bin_name (str): The name of the bin to be operated on. - index (int): The index of the item to be removed. + index (int): The index of the item to be removed. (64-bit signed integer) ctx (list): An optional list of nested CDT :class:`cdt_ctx ` context operation objects. Returns: @@ -260,7 +260,7 @@ def list_pop_range(bin_name: str, index, count, ctx: Optional[list] = None): Args: bin_name (str): The name of the bin to be operated on. - index (int): The index of the first item to be removed. + index (int): The index of the first item to be removed. (64-bit signed integer) count (int): A positive number indicating how many items, including the first, to be removed and returned ctx (list): An optional list of nested CDT :class:`cdt_ctx ` context operation objects. @@ -285,7 +285,7 @@ def list_remove(bin_name: str, index, ctx: Optional[list] = None): Args: bin_name (str): The name of the bin containing the item to be removed. - index (int): The index at which to remove the item. + index (int): The index at which to remove the item. (64-bit signed integer) ctx (list): An optional list of nested CDT :class:`cdt_ctx ` context operation objects. Returns: @@ -309,7 +309,7 @@ def list_remove_range(bin_name: str, index, count, ctx: Optional[list] = None): Args: bin_name (str): The name of the bin containing the items to be removed. - index (int): The index of the first item to remove. + index (int): The index of the first item to remove. (64-bit signed integer) count (int): A positive number representing the number of items to be removed. ctx (list): An optional list of nested CDT :class:`cdt_ctx ` context operation objects. @@ -355,7 +355,7 @@ def list_set(bin_name: str, index, value, policy: Optional[dict] = None, ctx: Op Args: bin_name (str): The name of the bin containing the list to be operated on. - index (int): The index of the item to be set. + index (int): The index of the item to be set. (64-bit signed integer) value: The value to be assigned to the list item. policy (dict): An optional dictionary of :ref:`list write options `. ctx (list): An optional list of nested CDT :class:`cdt_ctx ` context operation @@ -382,7 +382,7 @@ def list_get(bin_name: str, index, ctx: Optional[list] = None): Args: bin_name (str): The name of the bin containing the list to fetch items from. - index (int): The index of the item to be returned. + index (int): The index of the item to be returned. (64-bit signed integer) ctx (list): An optional list of nested CDT :class:`cdt_ctx ` context operation objects. @@ -405,7 +405,7 @@ def list_get_range(bin_name: str, index, count, ctx: Optional[list] = None): Args: bin_name (str): The name of the bin containing the list to fetch items from. - index (int): The index of the item to be returned. + index (int): The index of the item to be returned. (64-bit signed integer) count (int): A positive number of items to be returned. ctx (list): An optional list of nested CDT :class:`cdt_ctx ` context operation objects. @@ -429,7 +429,7 @@ def list_trim(bin_name: str, index, count, ctx: Optional[list] = None): Args: bin_name (str): The name of the bin containing the list to be trimmed. - index (int): The index of the items to be kept. + index (int): The index of the items to be kept. (64-bit signed integer) count (int): A positive number of items to be kept. ctx (list): An optional list of nested CDT :class:`cdt_ctx ` context operation objects. @@ -479,7 +479,7 @@ def list_get_by_index(bin_name: str, index, return_type, ctx: Optional[list] = N Args: bin_name (str): The name of the bin containing the list to fetch items from. - index (int): The index of the item to be returned. + index (int): The index of the item to be returned. (64-bit signed integer) return_type (int): Value specifying what should be returned from the operation. This should be one of the :ref:`list_return_types` values ctx (list): An optional list of nested CDT :class:`cdt_ctx ` context operation @@ -510,7 +510,7 @@ def list_get_by_index_range(bin_name: str, index, return_type, count=None, inver Args: bin_name (str): The name of the bin containing the list to fetch items from. - index (int): The index of the first item to be returned. + index (int): The index of the first item to be returned. (64-bit signed integer) return_type (int): Value specifying what should be returned from the operation. This should be one of the :ref:`list_return_types` values. count (int): The number of list items to be selected. @@ -549,7 +549,7 @@ def list_get_by_rank(bin_name: str, rank, return_type, ctx: Optional[list] = Non Args: bin_name (str): The name of the bin containing the list to fetch a value from. - rank (int): The rank of the item to be fetched. + rank (int): The rank of the item to be fetched. (64-bit signed integer) return_type (int): Value specifying what should be returned from the operation. This should be one of the :ref:`list_return_types` values ctx (list): An optional list of nested CDT :class:`cdt_ctx ` context operation @@ -575,7 +575,7 @@ def list_get_by_rank_range(bin_name: str, rank, return_type, count=None, inverte Args: bin_name (str): The name of the bin containing the list to fetch items from. - rank (int): The rank of the first items to be returned. + rank (int): The rank of the first items to be returned. (64-bit signed integer) return_type (int): Value specifying what should be returned from the operation. This should be one of the :ref:`list_return_types` values count (int): A positive number indicating number of items to be returned. @@ -730,7 +730,7 @@ def list_remove_by_index(bin_name: str, index, return_type, ctx: Optional[list] Args: bin_name (str): The name of the bin containing the list to remove an item from. - index (int): The index of the item to be removed. + index (int): The index of the item to be removed. (64-bit signed integer) return_type (int): Value specifying what should be returned from the operation. This should be one of the :ref:`list_return_types` values ctx (list): An optional list of nested CDT :class:`cdt_ctx ` context operation @@ -768,7 +768,7 @@ def list_remove_by_index_range( Args: bin_name (str): The name of the bin containing the list to remove items from. - index (int): The index of the first item to be removed. + index (int): The index of the first item to be removed. (64-bit signed integer) return_type (int): Value specifying what should be returned from the operation. This should be one of the :ref:`list_return_types` values. count (int): The number of items to be removed @@ -807,7 +807,7 @@ def list_remove_by_rank(bin_name: str, rank, return_type, ctx: Optional[list] = Args: bin_name (str): The name of the bin containing the list to fetch a value from. - rank (int): The rank of the item to be removed. + rank (int): The rank of the item to be removed. (64-bit signed integer) return_type (int): Value specifying what should be returned from the operation. This should be one of the :ref:`list_return_types` values ctx (list): An optional list of nested CDT :class:`cdt_ctx ` context operation @@ -838,7 +838,7 @@ def list_remove_by_rank_range(bin_name: str, rank, return_type, count=None, inve Args: bin_name (str): The name of the bin containing the list to fetch items from. - rank (int): The rank of the first item to removed. + rank (int): The rank of the first item to removed. (64-bit signed integer) return_type (int): Value specifying what should be returned from the operation. This should be one of the :ref:`list_return_types` values count (int): A positive number indicating number of items to be removed. @@ -1040,7 +1040,8 @@ def list_get_by_value_rank_range_relative( Args: bin_name (str): The name of the bin containing the list. value (str): The value of the item in the list for which to search - offset (int): Begin returning items with rank == rank(found_item) + offset + offset (int): Begin returning items with rank == rank(found_item) + offset. + (64-bit signed integer) return_type (int): Value specifying what should be returned from the operation. This should be one of the :ref:`list_return_types` values count (int): If specified, the number of items to return. If None, @@ -1118,7 +1119,8 @@ def list_remove_by_value_rank_range_relative( Args: bin_name (str): The name of the bin containing the list. value (str): The value of the item in the list for which to search - offset (int): Begin removing and returning items with rank == rank(found_item) + offset + offset (int): Begin removing and returning items with rank == rank(found_item) + offset. + (64-bit signed integer) return_type (int): Value specifying what should be returned from the operation. This should be one of the :ref:`list_return_types` values count (int): If specified, the number of items to remove and return. If None, diff --git a/aerospike_helpers/operations/map_operations.py b/aerospike_helpers/operations/map_operations.py index 32f62df5d5..aac433a35a 100755 --- a/aerospike_helpers/operations/map_operations.py +++ b/aerospike_helpers/operations/map_operations.py @@ -479,7 +479,7 @@ def map_remove_by_index(bin_name: str, index, return_type, ctx: Optional[list] = Args: bin_name (str): The name of the bin containing the map. - index (int): The index of the entry to remove. + index (int): The index of the entry to remove. (64-bit signed integer) return_type (int): Value specifying what should be returned from the operation. This should be one of the :ref:`map_return_types` values. ctx (list): An optional list of nested CDT :class:`cdt_ctx ` context operation @@ -515,7 +515,7 @@ def map_remove_by_index_range( Args: bin_name (str): The name of the bin containing the map. - index_start (int): The index of the first entry to remove. + index_start (int): The index of the first entry to remove. (64-bit signed integer) remove_amt (int): The number of entries to remove from the map. return_type (int): Value specifying what should be returned from the operation. This should be one of the :ref:`map_return_types` values. @@ -549,7 +549,7 @@ def map_remove_by_rank(bin_name: str, rank, return_type, ctx: Optional[list] = N Args: bin_name (str): The name of the bin containing the map. - rank (int): The rank of the entry to remove. + rank (int): The rank of the entry to remove. (64-bit signed integer) return_type (int): Value specifying what should be returned from the operation. This should be one of the :ref:`map_return_types` values. ctx (list): An optional list of nested CDT :class:`cdt_ctx ` context operation @@ -585,7 +585,7 @@ def map_remove_by_rank_range( Args: bin_name (str): The name of the bin containing the map. - rank_start (int): The rank of the entry to remove. + rank_start (int): The rank of the entry to remove. (64-bit signed integer) remove_amt (int): The number of entries to remove. return_type (int): Value specifying what should be returned from the operation. This should be one of the :ref:`map_return_types` values. @@ -823,7 +823,7 @@ def map_get_by_index(bin_name: str, index, return_type, ctx: Optional[list] = No Args: bin_name (str): The name of the bin containing the map. - index (int): The index of the entry to return. + index (int): The index of the entry to return. (64-bit signed integer) return_type (int): Value specifying what should be returned from the operation. This should be one of the :ref:`map_return_types` values. ctx (list): An optional list of nested CDT :class:`cdt_ctx ` context operation @@ -854,7 +854,7 @@ def map_get_by_index_range( Args: bin_name (str): The name of the bin containing the map. - index_start (int): The index of the first entry to return. + index_start (int): The index of the first entry to return. (64-bit signed integer) get_amt (int): The number of entries to return from the map. return_type (int): Value specifying what should be returned from the operation. This should be one of the :ref:`map_return_types` values. @@ -888,7 +888,7 @@ def map_get_by_rank(bin_name: str, rank, return_type, ctx: Optional[list] = None Args: bin_name (str): The name of the bin containing the map. - rank (int): The rank of the entry to return. + rank (int): The rank of the entry to return. (64-bit signed integer) return_type (int): Value specifying what should be returned from the operation. This should be one of the :ref:`map_return_types` values. ctx (list): An optional list of nested CDT :class:`cdt_ctx ` context operation @@ -912,7 +912,7 @@ def map_get_by_rank_range(bin_name: str, rank_start, get_amt, return_type, inver Args: bin_name (str): The name of the bin containing the map. - rank_start (int): The start of the rank of the entries to return. + rank_start (int): The start of the rank of the entries to return. (64-bit signed integer) get_amt (int): The number of entries to return. return_type (int): Value specifying what should be returned from the operation. This should be one of the :ref:`map_return_types` values. @@ -951,7 +951,8 @@ def map_remove_by_value_rank_range_relative( Args: bin_name (str): The name of the bin containing the map. value: The value of the entry in the map for which to search - offset (int): Begin removing and returning items with rank == rank(found_item) + offset + offset (int): Begin removing and returning items with rank == rank(found_item) + offset. + (64-bit signed integer) return_type (int): Value specifying what should be returned from the operation. This should be one of the :ref:`map_return_types` values. count (int): If specified, the number of items to remove and return. If None, @@ -1024,7 +1025,8 @@ def map_get_by_value_rank_range_relative( Args: bin_name (str): The name of the bin containing the map. value (str): The value of the item in the list for which to search - offset (int): Begin removing and returning items with rank == rank(fount_item) + offset + offset (int): Begin removing and returning items with rank == rank(fount_item) + offset. + (64-bit signed integer) return_type (int): Value specifying what should be returned from the operation. This should be one of the :ref:`map_return_types` values. count (int): If specified, the number of items to remove and return. If None, @@ -1086,7 +1088,8 @@ def map_remove_by_key_index_range_relative( Args: bin_name (str): The name of the bin containing the list. key (str): The key of the item in the list for which to search - offset (int): Begin removing and returning items with rank == rank(fount_item) + offset + offset (int): Begin removing and returning items with rank == rank(fount_item) + offset. + (64-bit signed integer) return_type (int): Value specifying what should be returned from the operation. This should be one of the :ref:`map_return_types` values. count (int): If specified, the number of items to remove and return. If None, @@ -1157,7 +1160,8 @@ def map_get_by_key_index_range_relative( Args: bin_name (str): The name of the bin containing the list. value (str): The value of the item in the list for which to search - offset (int): Begin removing and returning items with rank == rank(fount_item) + offset + offset (int): Begin removing and returning items with rank == rank(fount_item) + offset. + (64-bit signed integer) return_type (int): Value specifying what should be returned from the operation. This should be one of the :ref:`map_return_types` values. count (int): If specified, the number of items to remove and return. If None, diff --git a/src/include/conversions.h b/src/include/conversions.h index 91504bf629..5e71f6b25a 100644 --- a/src/include/conversions.h +++ b/src/include/conversions.h @@ -222,16 +222,36 @@ PyObject *create_class_instance_from_module(as_error *error_p, // We return an unsigned long long because it should be able to fit all fixed-width int types up to uint64_t // Returns -1 on error. Error indicator can be checked to verify if error occurred // TODO: replace this with new API calls in Python 3.14 -unsigned long long -convert_pyobject_to_fixed_width_integer_type(PyObject *pyobject, - unsigned long long max_bound); -uint8_t convert_pyobject_to_uint8_t(PyObject *pyobject); +unsigned long long convert_pyobject_to_unsigned_fixed_width_integer_type( + PyObject *pyobject, unsigned long long max_bound); -uint16_t convert_pyobject_to_uint16_t(PyObject *pyobject); +uint64_t convert_pylong_to_uint64_t(as_error *err, PyObject *pyobject, + const char *component); -uint32_t convert_pyobject_to_uint32_t(PyObject *pyobject); +int64_t convert_pylong_to_int64_t(as_error *err, PyObject *pyobject, + const char *component); -uint64_t convert_pyobject_to_uint64_t(PyObject *pyobject); +uint32_t convert_pylong_into_uint32_t(as_error *err, PyObject *pyobject, + const char *component); + +int32_t convert_pylong_to_int32_t(as_error *err, PyObject *pyobject, + const char *component); + +int convert_pylong_to_int(as_error *err, PyObject *pyobject, + const char *component); + +unsigned int convert_pylong_to_enum_value(as_error *err, PyObject *py_long, + unsigned int max_enum_value, + const char *component); + +uint16_t convert_pylong_to_uint16_t(as_error *err, PyObject *pyobject, + const char *component); + +int16_t convert_pylong_to_int16_t(as_error *err, PyObject *pyobject, + const char *component); + +uint8_t convert_pylong_to_uint8_t(as_error *err, PyObject *pyobject, + const char *component); // Returns NULL on error. const char *convert_pyobject_to_str(PyObject *py_obj); diff --git a/src/include/operate.h b/src/include/operate.h index 0e0af7667f..7bbbd0602c 100644 --- a/src/include/operate.h +++ b/src/include/operate.h @@ -23,6 +23,9 @@ #include "client.h" #include "exceptions.h" -as_status add_op(AerospikeClient *self, as_error *err, PyObject *py_val, - as_vector *unicodeStrVector, as_static_pool *static_pool, - as_operations *ops, long *op, long *ret_type); +as_status as_operations_add_from_pyobject(AerospikeClient *self, as_error *err, + PyObject *py_val, + as_vector *unicodeStrVector, + as_static_pool *static_pool, + as_operations *ops, long *op, + long *ret_type); diff --git a/src/main/client/batch_operate.c b/src/main/client/batch_operate.c index d2e0c53043..fffd46b53e 100644 --- a/src/main/client/batch_operate.c +++ b/src/main/client/batch_operate.c @@ -169,8 +169,9 @@ static PyObject *AerospikeClient_Batch_Operate_Invoke( goto CLEANUP; } - if (add_op(self, err, py_val, unicodeStrVector, &static_pool, &ops, - &operation, &return_type) != AEROSPIKE_OK) { + if (as_operations_add_from_pyobject(self, err, py_val, unicodeStrVector, + &static_pool, &ops, &operation, + &return_type) != AEROSPIKE_OK) { goto CLEANUP; } } diff --git a/src/main/client/batch_write.c b/src/main/client/batch_write.c index 46154e56a6..ed281cc399 100644 --- a/src/main/client/batch_write.c +++ b/src/main/client/batch_write.c @@ -314,8 +314,9 @@ static PyObject *AerospikeClient_BatchWriteInvoke(AerospikeClient *self, goto CLEANUP_ON_ERROR; } - if (add_op(self, err, py_op, unicodeStrVector, &static_pool, - ops, &operation, &return_type) != AEROSPIKE_OK) { + if (as_operations_add_from_pyobject( + self, err, py_op, unicodeStrVector, &static_pool, ops, + &operation, &return_type) != AEROSPIKE_OK) { goto CLEANUP_ON_ERROR; } } diff --git a/src/main/client/cdt_operation_utils.c b/src/main/client/cdt_operation_utils.c index 2d7a2da6d9..09d7a18800 100644 --- a/src/main/client/cdt_operation_utils.c +++ b/src/main/client/cdt_operation_utils.c @@ -135,22 +135,15 @@ as_status get_optional_int64_t(as_error *err, const char *key, if (!py_val) { return AEROSPIKE_OK; } - if (!PyLong_Check(py_val)) { - return as_error_update(err, AEROSPIKE_ERR_PARAM, - "%s must be an integer", key); + as_error_update(err, AEROSPIKE_ERR_PARAM, + "Unable to convert Python object %s to int64_t", key); + return err->code; } - - *i64_valptr = (int64_t)PyLong_AsLongLong(py_val); - if (PyErr_Occurred()) { - if (PyErr_ExceptionMatches(PyExc_OverflowError)) { - return as_error_update(err, AEROSPIKE_ERR_PARAM, "%s too large", - key); - } - return as_error_update(err, AEROSPIKE_ERR_PARAM, "Failed to convert %s", - key); + *i64_valptr = convert_pylong_to_int64_t(err, py_val, key); + if (err->code != AEROSPIKE_OK) { + return err->code; } - *found = true; return AEROSPIKE_OK; } diff --git a/src/main/client/operate.c b/src/main/client/operate.c index b743252b83..6376f9a972 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -309,10 +309,12 @@ bool opRequiresKey(int op) op == OP_MAP_GET_BY_KEY_RANGE); } -as_status add_op(AerospikeClient *self, as_error *err, - PyObject *py_operation_dict, as_vector *unicodeStrVector, - as_static_pool *static_pool, as_operations *ops, long *op, - long *ret_type) +as_status as_operations_add_from_pyobject(AerospikeClient *self, as_error *err, + PyObject *py_operation_dict, + as_vector *unicodeStrVector, + as_static_pool *static_pool, + as_operations *ops, long *op, + long *ret_type) { as_val *put_val = NULL; as_val *put_key = NULL; @@ -323,9 +325,10 @@ as_status add_op(AerospikeClient *self, as_error *err, char *bin = NULL; char *val = NULL; long offset = 0; + long long long_offset = 0; long ttl = 0; double double_offset = 0.0; - int index = 0; + int64_t index_or_rank = 0; long operation = 0; uint64_t return_type = AS_MAP_RETURN_NONE; PyObject *py_ustr = NULL; @@ -544,7 +547,11 @@ as_status add_op(AerospikeClient *self, as_error *err, goto CLEANUP; } if (PyLong_Check(py_index)) { - index = PyLong_AsLong(py_index); + index_or_rank = + convert_pylong_to_int64_t(err, py_index, "py_index"); + if (err->code != AEROSPIKE_OK) { + goto CLEANUP; + } } else { as_error_update(err, AEROSPIKE_ERR_PARAM, @@ -573,10 +580,15 @@ as_status add_op(AerospikeClient *self, as_error *err, as_error_update(err, AEROSPIKE_ERR_CLIENT, "Internal error"); goto CLEANUP; } - - uint32_t flags = convert_pyobject_to_uint32_t(py_flags); + if (!PyLong_Check(py_flags)) { + as_error_update(err, AEROSPIKE_ERR_PARAM, + "CDT operation's flags argument must be a long"); + goto CLEANUP; + } + uint32_t flags = + convert_pylong_into_uint32_t(err, py_flags, "path flags"); Py_DECREF(py_flags); - if (PyErr_Occurred()) { + if (err->code != AEROSPIKE_OK) { as_error_update(err, AEROSPIKE_ERR_PARAM, "CDT operation's flags argument is invalid"); goto CLEANUP; @@ -680,15 +692,12 @@ as_status add_op(AerospikeClient *self, as_error *err, break; case AS_OPERATOR_INCR: if (PyLong_Check(py_value)) { - offset = PyLong_AsLong(py_value); - if (offset == -1 && PyErr_Occurred() && self->strict_types) { - if (PyErr_ExceptionMatches(PyExc_OverflowError)) { - as_error_update(err, AEROSPIKE_ERR_PARAM, - "integer value exceeds sys.maxsize"); - goto CLEANUP; - } + long_offset = + convert_pylong_to_int64_t(err, py_value, "AS_OPERATOR_INCR"); + if (err->code != AEROSPIKE_OK) { + goto CLEANUP; } - as_operations_add_incr(ops, bin, offset); + as_operations_add_incr(ops, bin, long_offset); } else if (PyFloat_Check(py_value)) { double_offset = PyFloat_AsDouble(py_value); @@ -796,25 +805,26 @@ as_status add_op(AerospikeClient *self, as_error *err, put_range, return_type); break; case OP_MAP_REMOVE_BY_INDEX: - as_operations_map_remove_by_index(ops, bin, ctx_ref, index, + as_operations_map_remove_by_index(ops, bin, ctx_ref, index_or_rank, return_type); break; case OP_MAP_REMOVE_BY_INDEX_RANGE: if (pyobject_to_index(self, err, py_value, &offset) != AEROSPIKE_OK) { goto CLEANUP; } - as_operations_map_remove_by_index_range(ops, bin, ctx_ref, index, - offset, return_type); + as_operations_map_remove_by_index_range( + ops, bin, ctx_ref, index_or_rank, offset, return_type); break; case OP_MAP_REMOVE_BY_RANK: - as_operations_map_remove_by_rank(ops, bin, ctx_ref, index, return_type); + as_operations_map_remove_by_rank(ops, bin, ctx_ref, index_or_rank, + return_type); break; case OP_MAP_REMOVE_BY_RANK_RANGE: if (pyobject_to_index(self, err, py_value, &offset) != AEROSPIKE_OK) { goto CLEANUP; } - as_operations_map_remove_by_rank_range(ops, bin, ctx_ref, index, offset, - return_type); + as_operations_map_remove_by_rank_range(ops, bin, ctx_ref, index_or_rank, + offset, return_type); break; case OP_MAP_GET_BY_KEY: CONVERT_KEY_TO_AS_VAL(); @@ -847,24 +857,26 @@ as_status add_op(AerospikeClient *self, as_error *err, (as_list *)put_val, return_type); break; case OP_MAP_GET_BY_INDEX: - as_operations_map_get_by_index(ops, bin, ctx_ref, index, return_type); + as_operations_map_get_by_index(ops, bin, ctx_ref, index_or_rank, + return_type); break; case OP_MAP_GET_BY_INDEX_RANGE: if (pyobject_to_index(self, err, py_value, &offset) != AEROSPIKE_OK) { goto CLEANUP; } - as_operations_map_get_by_index_range(ops, bin, ctx_ref, index, offset, - return_type); + as_operations_map_get_by_index_range(ops, bin, ctx_ref, index_or_rank, + offset, return_type); break; case OP_MAP_GET_BY_RANK: - as_operations_map_get_by_rank(ops, bin, ctx_ref, index, return_type); + as_operations_map_get_by_rank(ops, bin, ctx_ref, index_or_rank, + return_type); break; case OP_MAP_GET_BY_RANK_RANGE: if (pyobject_to_index(self, err, py_value, &offset) != AEROSPIKE_OK) { goto CLEANUP; } - as_operations_map_get_by_rank_range(ops, bin, ctx_ref, index, offset, - return_type); + as_operations_map_get_by_rank_range(ops, bin, ctx_ref, index_or_rank, + offset, return_type); break; default: @@ -945,8 +957,9 @@ static PyObject *AerospikeClient_Operate_Invoke(AerospikeClient *self, PyObject *py_val = PyList_GetItem(py_list, i); if (PyDict_Check(py_val)) { - if (add_op(self, err, py_val, unicodeStrVector, &static_pool, &ops, - &operation, &return_type) != AEROSPIKE_OK) { + if (as_operations_add_from_pyobject( + self, err, py_val, unicodeStrVector, &static_pool, &ops, + &operation, &return_type) != AEROSPIKE_OK) { goto CLEANUP; } } @@ -1118,8 +1131,9 @@ AerospikeClient_OperateOrdered_Invoke(AerospikeClient *self, as_error *err, py_current_op = PyList_GetItem(py_list, i); if (PyDict_Check(py_current_op)) { - if (add_op(self, err, py_current_op, unicodeStrVector, &static_pool, - &ops, &operation, &return_type) != AEROSPIKE_OK) { + if (as_operations_add_from_pyobject( + self, err, py_current_op, unicodeStrVector, &static_pool, + &ops, &operation, &return_type) != AEROSPIKE_OK) { goto CLEANUP; } } diff --git a/src/main/conversions.c b/src/main/conversions.c index 6576252d5c..f9be514c8d 100644 --- a/src/main/conversions.c +++ b/src/main/conversions.c @@ -1281,14 +1281,12 @@ as_status as_val_new_from_pyobject(AerospikeClient *self, as_error *err, } } else if (PyLong_Check(py_obj)) { - int64_t i = (int64_t)PyLong_AsLongLong(py_obj); - if (i == -1 && PyErr_Occurred()) { - if (PyErr_ExceptionMatches(PyExc_OverflowError)) { - return as_error_update(err, AEROSPIKE_ERR_PARAM, - "integer value exceeds sys.maxsize"); - } + int64_t int_val = + convert_pylong_to_int64_t(err, py_obj, "as_val_new_from_pyobject"); + if (err->code != AEROSPIKE_OK) { + return err->code; } - *val = (as_val *)as_integer_new(i); + *val = (as_val *)as_integer_new(int_val); } else if (PyUnicode_Check(py_obj)) { PyObject *py_ustr = PyUnicode_AsUTF8String(py_obj); @@ -1554,14 +1552,11 @@ as_status pyobject_to_key(as_error *err, PyObject *py_keytuple, as_key *key) Py_DECREF(py_ustr); } else if (PyLong_Check(py_key)) { - int64_t k = (int64_t)PyLong_AsLongLong(py_key); - if (-1 == k && PyErr_Occurred()) { - as_error_update(err, AEROSPIKE_ERR_PARAM, - "integer value for KEY exceeds sys.maxsize"); - } - else { - returnResult = as_key_init_int64(key, ns, set, k); + int64_t int_val = convert_pylong_to_int64_t(err, py_key, "KEY"); + if (err->code != AEROSPIKE_OK) { + return err->code; } + returnResult = as_key_init_int64(key, ns, set, int_val); } else if (PyByteArray_Check(py_key)) { uint32_t sz = (uint32_t)PyByteArray_Size(py_key); @@ -2218,8 +2213,9 @@ void initialize_bin_for_strictypes(AerospikeClient *self, as_error *err, as_bin *binop_bin = &binop->bin; if (PyLong_Check(py_value)) { - int val = PyLong_AsLong(py_value); - as_integer_init((as_integer *)&binop_bin->value, val); + int64_t int_val = convert_pylong_to_int64_t( + err, py_value, "initialize_bin_for_strictypes"); + as_integer_init((as_integer *)&binop_bin->value, (int64_t)int_val); binop_bin->valuep = &binop_bin->value; } else if (PyUnicode_Check(py_value)) { @@ -2326,8 +2322,6 @@ as_status check_and_set_meta(PyObject *py_meta, uint32_t *ttl_ref, PyObject *py_gen = PyDict_GetItemString(py_meta, "gen"); PyObject *py_ttl = PyDict_GetItemString(py_meta, "ttl"); - uint32_t ttl = 0; - uint16_t gen = 0; if (py_ttl) { int retval = PyErr_WarnEx(PyExc_DeprecationWarning, @@ -2340,19 +2334,16 @@ as_status check_and_set_meta(PyObject *py_meta, uint32_t *ttl_ref, } if (PyLong_Check(py_ttl)) { - ttl = (uint32_t)PyLong_AsLong(py_ttl); + *ttl_ref = convert_pylong_into_uint32_t(err, py_ttl, "Ttl"); + + if (err->code != AEROSPIKE_OK) { + return err->code; + } } else { return as_error_update(err, AEROSPIKE_ERR_PARAM, "Ttl should be an int or long"); } - - if ((uint32_t)-1 == ttl && PyErr_Occurred()) { - return as_error_update( - err, AEROSPIKE_ERR_PARAM, - "integer value for ttl exceeds sys.maxsize"); - } - *ttl_ref = ttl; } else { // Metadata dict was present, but ttl field did not exist @@ -2361,20 +2352,16 @@ as_status check_and_set_meta(PyObject *py_meta, uint32_t *ttl_ref, if (py_gen) { if (PyLong_Check(py_gen)) { - // TODO: Needs to check value doesn't go past unsigned 16 bit limit - gen = (uint16_t)PyLong_AsLong(py_gen); + *gen_ref = + convert_pylong_to_uint16_t(err, py_gen, "Generation"); + if (err->code != AEROSPIKE_OK) { + return err->code; + } } else { return as_error_update(err, AEROSPIKE_ERR_PARAM, "Generation should be an int or long"); } - - if ((uint16_t)-1 == gen && PyErr_Occurred()) { - return as_error_update( - err, AEROSPIKE_ERR_PARAM, - "integer value for gen exceeds sys.maxsize"); - } - *gen_ref = gen; } } else if (py_meta && (py_meta != Py_None)) { @@ -2393,10 +2380,11 @@ as_status pyobject_to_index(AerospikeClient *self, as_error *err, { if (PyLong_Check(py_value)) { *long_val = PyLong_AsLong(py_value); - if (*long_val == -1 && PyErr_Occurred() && self->strict_types) { + if (PyErr_Occurred() && self->strict_types) { if (PyErr_ExceptionMatches(PyExc_OverflowError)) { return as_error_update(err, AEROSPIKE_ERR_PARAM, - "integer value exceeds sys.maxsize"); + "integer value for pyobject_to_index " + "must be between LONG_MIN and LONG_MAX"); } } } @@ -2746,25 +2734,10 @@ as_status get_int_from_py_int(as_error *err, PyObject *py_long, return as_error_update(err, AEROSPIKE_ERR_PARAM, "%s must be an integer.", py_object_name); } - - long int_to_return = PyLong_AsLong(py_long); - if (PyErr_Occurred()) { - if (PyErr_ExceptionMatches(PyExc_OverflowError)) { - return as_error_update(err, AEROSPIKE_ERR_PARAM, - "%s too large for C long.", py_object_name); - } - - return as_error_update(err, AEROSPIKE_ERR_PARAM, - "Failed to convert %s.", py_object_name); - } - - if (int_to_return > INT_MAX || int_to_return < INT_MIN) { - return as_error_update(err, AEROSPIKE_ERR_PARAM, - "%s too large for C int.", py_object_name); + *int_pointer = convert_pylong_to_int(err, py_long, py_object_name); + if (err->code != AEROSPIKE_OK) { + return err->code; } - - *int_pointer = (int)int_to_return; - return AEROSPIKE_OK; } @@ -2812,11 +2785,38 @@ as_status as_batch_result_to_BatchRecord(AerospikeClient *self, as_error *err, return err->code; } +long long convert_pyobject_to_signed_fixed_width_integer_type( + PyObject *pyobject, long long min_bound, long long max_bound) +{ + if (!PyLong_Check(pyobject)) { + PyErr_Format(PyExc_TypeError, "%S must be an integer", pyobject); + goto error; + } + long long value = PyLong_AsLongLong(pyobject); + if (PyErr_Occurred()) { + goto error; + } + + if (value > max_bound) { + PyErr_Format(PyExc_ValueError, "%S exceeds %lli", pyobject, max_bound); + goto error; + } + + if (value < min_bound) { + PyErr_Format(PyExc_ValueError, "%S exceeds %lli", pyobject, max_bound); + goto error; + } + + return value; + +error: + return -1; +} + // TODO: There's a helper function in the Python client wrapper code called // get_uint32_value, but this can replace it. -unsigned long long -convert_pyobject_to_fixed_width_integer_type(PyObject *pyobject, - unsigned long long max_bound) +unsigned long long convert_pyobject_to_unsigned_fixed_width_integer_type( + PyObject *pyobject, unsigned long long max_bound) { if (!PyLong_Check(pyobject)) { PyErr_Format(PyExc_TypeError, "%S must be an integer", pyobject); @@ -2838,28 +2838,228 @@ convert_pyobject_to_fixed_width_integer_type(PyObject *pyobject, return -1; } -uint8_t convert_pyobject_to_uint8_t(PyObject *pyobject) +uint64_t convert_pylong_to_uint64_t(as_error *err, PyObject *py_long, + const char *component) { - return (uint8_t)convert_pyobject_to_fixed_width_integer_type(pyobject, - UINT8_MAX); + uint64_t long_value = 0; + PyLong_AsUInt64(py_long, &long_value); + if (PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_OverflowError)) { + as_error_update( + err, AEROSPIKE_ERR_PARAM, + "integer value for %s must be between 0 and UINT64_MAX", + component); + } + else { + as_error_update( + err, AEROSPIKE_ERR_PARAM, + "Failed to convert integer value for %s to uint64_t", component) + } + return 0; + } + return (uint64_t)long_value; } -uint16_t convert_pyobject_to_uint16_t(PyObject *pyobject) +int64_t convert_pylong_to_int64_t(as_error *err, PyObject *py_long, + const char *component) { - return (uint16_t)convert_pyobject_to_fixed_width_integer_type(pyobject, - UINT16_MAX); + int64_t long_value = 0; + PyLong_AsInt64(py_long, &long_value); + if (PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_OverflowError)) { + as_error_update( + err, AEROSPIKE_ERR_PARAM, + "integer value for %s must be between INT64_MIN and INT64_MAX", + component); + } + else { + as_error_update(err, AEROSPIKE_ERR_PARAM, + "Failed to convert integer value for %s to int64_t", + component) + } + return 0; + } + return (int64_t)long_value; } -uint32_t convert_pyobject_to_uint32_t(PyObject *pyobject) +uint32_t convert_pylong_into_uint32_t(as_error *err, PyObject *py_long, + const char *component) { - return (uint32_t)convert_pyobject_to_fixed_width_integer_type(pyobject, - UINT32_MAX); + uint32_t long_value = 0; + PyLong_AsUInt32(py_long, &long_value); + if (PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_OverflowError)) { + as_error_update( + err, AEROSPIKE_ERR_PARAM, + "integer value for %s must be between 0 and UINT32_MAX", + component); + } + else { + as_error_update( + err, AEROSPIKE_ERR_PARAM, + "Failed to convert integer value for %s to uint32_t", component) + } + return 0; + } + return long_value; +} + +int32_t convert_pylong_to_int32_t(as_error *err, PyObject *py_long, + const char *component) +{ + int32_t long_value = 0; + PyLong_AsInt32(py_long, &long_value); + if (PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_OverflowError)) { + as_error_update( + err, AEROSPIKE_ERR_PARAM, + "integer value for %s must be between INT32_MIN and INT32_MAX", + component); + } + else { + as_error_update(err, AEROSPIKE_ERR_PARAM, + "Failed to convert integer value for %s to int32_t", + component) + } + return 0; + } + return long_value; +} + +uint16_t convert_pylong_to_uint16_t(as_error *err, PyObject *py_long, + const char *component) +{ + unsigned long long_value = PyLong_AsUnsignedLong(py_long); + if (PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_OverflowError)) { + as_error_update( + err, AEROSPIKE_ERR_PARAM, + "integer value for %s must be between 0 and UINT16_MAX", + component); + } + else { + as_error_update( + err, AEROSPIKE_ERR_PARAM, + "Failed to convert integer value for %s to uint16_t", component) + } + return 0; + } + if (long_value > (unsigned long)UINT16_MAX) { + as_error_update(err, AEROSPIKE_ERR_PARAM, + "integer value for %s exceeds UINT16_MAX", component); + return 0; + } + return (uint16_t)long_value; +} + +int16_t convert_pylong_to_int16_t(as_error *err, PyObject *py_long, + const char *component) +{ + long long_value = PyLong_AsLong(py_long); + if (PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_OverflowError)) { + as_error_update( + err, AEROSPIKE_ERR_PARAM, + "integer value for %s must be between INT16_MIN and INT16_MAX", + component); + } + else { + as_error_update(err, AEROSPIKE_ERR_PARAM, + "Failed to convert integer value for %s to int16_t", + component) + } + return 0; + } + if (long_value > (long)INT16_MAX) { + as_error_update(err, AEROSPIKE_ERR_PARAM, + "integer value for %s exceeds INT16_MAX", component); + return 0; + } + else if (long_value < (long)INT16_MIN) { + as_error_update(err, AEROSPIKE_ERR_PARAM, + "integer value for %s exceeds INT16_MIN", component); + return 0; + } + return (int16_t)long_value; +} + +uint8_t convert_pylong_to_uint8_t(as_error *err, PyObject *py_long, + const char *component) +{ + unsigned long long_value = PyLong_AsUnsignedLong(py_long); + if (PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_OverflowError)) { + as_error_update( + err, AEROSPIKE_ERR_PARAM, + "integer value for %s must be between 0 and UINT8_MAX", + component); + } + else { + as_error_update(err, AEROSPIKE_ERR_PARAM, + "Failed to convert integer value for %s to uint8_t", + component) + } + return 0; + } + if (long_value > (unsigned long)UINT8_MAX) { + as_error_update(err, AEROSPIKE_ERR_PARAM, + "integer value for %s must be between 0 and UINT8_MAX", + component); + return 0; + } + return (uint8_t)long_value; +} + +int convert_pylong_to_int(as_error *err, PyObject *py_long, + const char *component) +{ + // CHECK ALL CONVERSION TO LONG VALUE AND MAKE SURE FUNCTION SIGNATURE IS CORRECT + int long_value = PyLong_AsInt(py_long); + if (PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_OverflowError)) { + as_error_update( + err, AEROSPIKE_ERR_PARAM, + "integer value for %s must be between INT_MIN and INT_MAX", + component); + } + else { + as_error_update(err, AEROSPIKE_ERR_PARAM, + "Failed to convert integer value for %s to int", + component) + } + return 0; + } + return long_value; } -uint64_t convert_pyobject_to_uint64_t(PyObject *pyobject) +unsigned int convert_pylong_to_enum_value(as_error *err, PyObject *py_long, + unsigned int max_enum_value, + const char *component) { - return (uint64_t)convert_pyobject_to_fixed_width_integer_type(pyobject, - UINT64_MAX); + unsigned long long_value = PyLong_AsUnsignedLong(py_long); + + if (PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_OverflowError)) { + as_error_update( + err, AEROSPIKE_ERR_PARAM, + "integer value for %s must be between LONG_MIN and LONG_MAX", + component); + } + else { + as_error_update(err, AEROSPIKE_ERR_PARAM, + "Failed to convert integer value for %s to long", + component) + } + return 0; + } + if (long_value > (unsigned long)max_enum_value) { + as_error_update( + err, AEROSPIKE_ERR_PARAM, + "integer value for %s exceeds value for the enumeration", + component); + return 0; + } + return (unsigned int)long_value; } const char *convert_pyobject_to_str(PyObject *py_obj) diff --git a/src/main/query/add_ops.c b/src/main/query/add_ops.c index 45b2c5d9c7..fb500c2ca1 100644 --- a/src/main/query/add_ops.c +++ b/src/main/query/add_ops.c @@ -86,9 +86,10 @@ AerospikeQuery *AerospikeQuery_Add_Ops(AerospikeQuery *self, PyObject *args, for (int i = 0; i < size; i++) { PyObject *py_val = PyList_GetItem(py_ops, (Py_ssize_t)i); if (PyDict_Check(py_val)) { - if (add_op(self->client, &err, py_val, self->unicodeStrVector, - self->static_pool, self->query.ops, &operation, - &return_type) != + if (as_operations_add_from_pyobject( + self->client, &err, py_val, self->unicodeStrVector, + self->static_pool, self->query.ops, &operation, + &return_type) != AEROSPIKE_OK) { //something wrong with ops bin name and value as_error_update(&err, AEROSPIKE_ERR_PARAM, "Failed to convert ops."); diff --git a/src/main/scan/add_ops.c b/src/main/scan/add_ops.c index 3c72a8a8e9..e828322956 100644 --- a/src/main/scan/add_ops.c +++ b/src/main/scan/add_ops.c @@ -82,9 +82,10 @@ AerospikeScan *AerospikeScan_Add_Ops(AerospikeScan *self, PyObject *args, PyObject *py_val = PyList_GetItem(py_ops, (Py_ssize_t)i); if (PyDict_Check(py_val)) { - if (add_op(self->client, &err, py_val, self->unicodeStrVector, - self->static_pool, self->scan.ops, &operation, - &return_type) != AEROSPIKE_OK) { + if (as_operations_add_from_pyobject( + self->client, &err, py_val, self->unicodeStrVector, + self->static_pool, self->scan.ops, &operation, + &return_type) != AEROSPIKE_OK) { as_error_update(&err, AEROSPIKE_ERR_PARAM, "Failed to convert ops."); goto CLEANUP;