From 4057529d7c0b2ca68fb845995a06b28691b373ca Mon Sep 17 00:00:00 2001 From: Dominic Pelini <111786059+DomPeliniAerospike@users.noreply.github.com> Date: Mon, 8 Jan 2024 09:50:08 -0700 Subject: [PATCH 01/36] CLIENT-2383 CLIENT-2383 Added support for context lists in Client.index_cdt_create() and Client.Query.where() Fixed the check for TestBaseClass.version not working corerctly upon initialization --- src/main/conversions.c | 10 +++++- test/new_tests/test_base_class.py | 6 ++-- test/new_tests/test_cdt_index.py | 56 +++++++++++++++---------------- test/new_tests/test_query.py | 6 ++-- 4 files changed, 43 insertions(+), 35 deletions(-) diff --git a/src/main/conversions.c b/src/main/conversions.c index 8a152a9468..32dcbcbb54 100644 --- a/src/main/conversions.c +++ b/src/main/conversions.c @@ -2382,7 +2382,15 @@ as_status get_cdt_ctx(AerospikeClient *self, as_error *err, as_cdt_ctx *cdt_ctx, PyObject *op_dict, bool *ctx_in_use, as_static_pool *static_pool, int serializer_type) { - PyObject *py_ctx = PyDict_GetItemString(op_dict, CTX_KEY); + PyObject *py_ctx = NULL; + if(PyDict_Check(op_dict)){ + py_ctx = PyDict_GetItemString(op_dict, CTX_KEY); + } + else{ + py_ctx = op_dict; + } + + long int_val = 0; as_val *val = NULL; diff --git a/test/new_tests/test_base_class.py b/test/new_tests/test_base_class.py index 4c5acc7dd7..4f660ae20e 100644 --- a/test/new_tests/test_base_class.py +++ b/test/new_tests/test_base_class.py @@ -178,9 +178,9 @@ def get_new_connection(add_config=None): if res is not None: break res = res.split(".") - # major_ver = res[0] - # minor_ver = res[1] - # print("major_ver:", major_ver, "minor_ver:", minor_ver) + TestBaseClass.major_ver = res[0] + TestBaseClass.minor_ver = res[1] + # print("major_ver:", TestBaseClass.major_ver, "minor_ver:", TestBaseClass.minor_ver) return client diff --git a/test/new_tests/test_cdt_index.py b/test/new_tests/test_cdt_index.py index e5a3d9c54e..947acd12af 100644 --- a/test/new_tests/test_cdt_index.py +++ b/test/new_tests/test_cdt_index.py @@ -103,7 +103,7 @@ def test_pos_cdtindex_with_correct_parameters(self): aerospike.INDEX_TYPE_LIST, aerospike.INDEX_STRING, "test_string_list_cdt_index", - {"ctx": ctx_list_index}, + ctx_list_index, policy, ) @@ -148,7 +148,7 @@ def test_pos_cdtindex_with_listrank_correct_parameters(self): aerospike.INDEX_TYPE_LIST, aerospike.INDEX_STRING, "test_string_list_cdt_index", - {"ctx": ctx_list_rank}, + ctx_list_rank, policy, ) @@ -169,7 +169,7 @@ def test_pos_cdtindex_with_listvalue_correct_parameters(self): aerospike.INDEX_TYPE_LIST, aerospike.INDEX_STRING, "test_string_list_cdt_index", - {"ctx": ctx_list_value}, + ctx_list_value, policy, ) @@ -190,7 +190,7 @@ def test_pos_cdtindex_with_mapindex_correct_parameters(self): aerospike.INDEX_TYPE_MAPKEYS, aerospike.INDEX_STRING, "test_string_list_cdt_index", - {"ctx": ctx_map_index}, + ctx_map_index, policy, ) @@ -211,7 +211,7 @@ def test_pos_cdtindex_with_mapvalue_correct_parameters(self): aerospike.INDEX_TYPE_MAPVALUES, aerospike.INDEX_STRING, "test_string_list_cdt_index", - {"ctx": ctx_map_value}, + ctx_map_value, policy, ) @@ -232,7 +232,7 @@ def test_pos_cdtindex_with_maprankvalue_correct_parameters(self): aerospike.INDEX_TYPE_MAPVALUES, aerospike.INDEX_STRING, "test_string_list_cdt_index", - {"ctx": ctx_map_rank}, + ctx_map_rank, policy, ) @@ -254,7 +254,7 @@ def test_pos_cdtindex_with_correct_parameters1(self): aerospike.INDEX_TYPE_MAPVALUES, aerospike.INDEX_STRING, "test_string_list_cdt_index", - {"ctx": ctx_map_rank}, + ctx_map_rank, policy, ) @@ -275,7 +275,7 @@ def test_pos_cdtindex_with_correct_parameters_numeric(self): aerospike.INDEX_TYPE_LIST, aerospike.INDEX_NUMERIC, "test_numeric_list_cdt_index", - {"ctx": ctx_list_index}, + ctx_list_index, policy, ) @@ -298,7 +298,7 @@ def test_pos_cdtindex_with_correct_parameters_set_length_extra(self): aerospike.INDEX_TYPE_LIST, aerospike.INDEX_STRING, "test_string_list_cdt_index", - {"ctx": ctx_list_index}, + ctx_list_index, policy, ) assert False @@ -319,7 +319,7 @@ def test_pos_cdtindex_with_incorrect_bin(self): aerospike.INDEX_TYPE_LIST, aerospike.INDEX_STRING, "test_string_list_cdt_index", - {"ctx": ctx_list_index}, + ctx_list_index, policy, ) @@ -339,7 +339,7 @@ def test_pos_create_same_cdtindex_multiple_times(self): aerospike.INDEX_TYPE_LIST, aerospike.INDEX_NUMERIC, "test_numeric_list_cdt_index", - {"ctx": ctx_list_index}, + ctx_list_index, policy, ) if retobj == 0: @@ -351,7 +351,7 @@ def test_pos_create_same_cdtindex_multiple_times(self): aerospike.INDEX_TYPE_LIST, aerospike.INDEX_NUMERIC, "test_numeric_list_cdt_index", - {"ctx": ctx_list_index}, + ctx_list_index, policy, ) except e.IndexFoundError: @@ -373,7 +373,7 @@ def test_pos_create_same_cdtindex_multiple_times_different_bin(self): aerospike.INDEX_TYPE_LIST, aerospike.INDEX_STRING, "test_string_list_cdt_index", - {"ctx": ctx_list_index}, + ctx_list_index, policy, ) if retobj == 0: @@ -385,7 +385,7 @@ def test_pos_create_same_cdtindex_multiple_times_different_bin(self): aerospike.INDEX_TYPE_LIST, aerospike.INDEX_NUMERIC, "test_string_list_cdt_index", - {"ctx": ctx_list_index}, + ctx_list_index, policy, ) self.as_connection.index_remove("test", "test_string_list_cdt_index", policy) @@ -409,7 +409,7 @@ def test_pos_create_different_cdtindex_multiple_times_same_bin(self): aerospike.INDEX_TYPE_LIST, aerospike.INDEX_STRING, "test_string_list_cdt_index", - {"ctx": ctx_list_index}, + ctx_list_index, policy, ) if retobj == 0: @@ -421,7 +421,7 @@ def test_pos_create_different_cdtindex_multiple_times_same_bin(self): aerospike.INDEX_TYPE_LIST, aerospike.INDEX_STRING, "test_string_list_cdt_index1", - {"ctx": ctx_list_index}, + ctx_list_index, policy, ) except e.IndexFoundError: @@ -444,7 +444,7 @@ def test_pos_createcdtindex_with_policy(self): aerospike.INDEX_TYPE_LIST, aerospike.INDEX_NUMERIC, "test_numeric_list_cdt_index_pol", - {"ctx": ctx_list_index}, + ctx_list_index, policy, ) @@ -464,7 +464,7 @@ def test_pos_createcdtindex_with_policystring(self): aerospike.INDEX_TYPE_LIST, aerospike.INDEX_STRING, "test_string_list_cdt_index", - {"ctx": ctx_list_index}, + ctx_list_index, policy, ) @@ -501,7 +501,7 @@ def test_pos_create_liststringindex_unicode_positive(self): aerospike.INDEX_TYPE_LIST, aerospike.INDEX_STRING, "uni_name_index", - {"ctx": ctx_list_index}, + ctx_list_index, policy, ) @@ -521,7 +521,7 @@ def test_pos_create_list_integer_index_unicode(self): aerospike.INDEX_TYPE_LIST, aerospike.INDEX_NUMERIC, "uni_age_index", - {"ctx": ctx_list_index}, + ctx_list_index, policy, ) @@ -557,7 +557,7 @@ def test_neg_cdtindex_with_namespace_is_none(self): aerospike.INDEX_TYPE_LIST, aerospike.INDEX_STRING, "test_string_list_cdt_index", - {"ctx": ctx_list_index}, + ctx_list_index, policy, ) @@ -578,7 +578,7 @@ def test_neg_cdtindex_with_set_is_int(self): aerospike.INDEX_TYPE_LIST, aerospike.INDEX_STRING, "test_string_list_cdt_index", - {"ctx": ctx_list_index}, + ctx_list_index, policy, ) assert False @@ -601,7 +601,7 @@ def test_neg_cdtindex_with_set_is_none(self): aerospike.INDEX_TYPE_LIST, aerospike.INDEX_STRING, "test_string_list_cdt_index", - {"ctx": ctx_list_index}, + ctx_list_index, policy, ) @@ -624,7 +624,7 @@ def test_neg_cdtindex_with_bin_is_none(self): aerospike.INDEX_TYPE_LIST, aerospike.INDEX_NUMERIC, "test_numeric_list_cdt_index", - {"ctx": ctx_list_index}, + ctx_list_index, policy, ) @@ -645,7 +645,7 @@ def test_neg_cdtindex_with_index_is_none(self): aerospike.INDEX_TYPE_LIST, aerospike.INDEX_STRING, None, - {"ctx": ctx_list_index}, + ctx_list_index, policy, ) @@ -667,7 +667,7 @@ def test_neg_cdtindex_with_incorrect_namespace(self): aerospike.INDEX_TYPE_DEFAULT, aerospike.INDEX_NUMERIC, "test_numeric_list_cdt_index", - {"ctx": ctx_list_index}, + ctx_list_index, policy, ) @@ -686,7 +686,7 @@ def test_neg_cdtindex_with_incorrect_set(self): aerospike.INDEX_TYPE_LIST, aerospike.INDEX_NUMERIC, "test_numeric_list_cdt_index", - {"ctx": ctx_list_index}, + ctx_list_index, policy, ) @@ -711,7 +711,7 @@ def test_neg_cdtindex_with_correct_parameters_no_connection(self): aerospike.INDEX_TYPE_LIST, aerospike.INDEX_STRING, "test_string_list_cdt_index", - {"ctx": ctx_list_index}, + ctx_list_index, policy, ) diff --git a/test/new_tests/test_query.py b/test/new_tests/test_query.py index b6edee579b..c61a235c9d 100644 --- a/test/new_tests/test_query.py +++ b/test/new_tests/test_query.py @@ -120,7 +120,7 @@ def setup_class(cls): except e.IndexFoundError: pass - if (TestBaseClass.major_ver, TestBaseClass.minor_ver) >= (7, 0): + if (int(TestBaseClass.major_ver), int(TestBaseClass.minor_ver)) >= (7, 0): # These indexes are only used for server 7.0+ tests try: client.index_list_create("test", "demo", "blob_list", aerospike.INDEX_BLOB, "blob_list_index") @@ -147,7 +147,7 @@ def setup_class(cls): aerospike.INDEX_TYPE_DEFAULT, aerospike.INDEX_NUMERIC, "numeric_list_cdt_index", - {"ctx": ctx_list_index}, + ctx_list_index, ) except e.IndexFoundError: pass @@ -160,7 +160,7 @@ def setup_class(cls): aerospike.INDEX_TYPE_DEFAULT, aerospike.INDEX_NUMERIC, "numeric_map_cdt_index", - {"ctx": ctx_map_index}, + ctx_map_index, ) except e.IndexFoundError: pass From 1c7d6c8a21d8bdc5d4891337b62000b3bf1a031f Mon Sep 17 00:00:00 2001 From: Dominic Pelini <111786059+DomPeliniAerospike@users.noreply.github.com> Date: Mon, 5 Feb 2024 09:37:27 -0700 Subject: [PATCH 02/36] Moved logic from conversions.c to individual functions Added Documentation Added test cases for ctx dictionary legacy support --- doc/client.rst | 39 ++++++++++++++++++++++++++++++++ src/include/client.h | 2 +- src/main/client/sec_index.c | 16 ++++++++++++- src/main/conversions.c | 8 +------ src/main/query/where.c | 18 ++++++++++++++- test/new_tests/test_cdt_index.py | 20 ++++++++++++++++ test/new_tests/test_query.py | 29 +++++++++++++++++++++++- 7 files changed, 121 insertions(+), 11 deletions(-) diff --git a/doc/client.rst b/doc/client.rst index 13cd4b4dd2..ccefe3e9ba 100755 --- a/doc/client.rst +++ b/doc/client.rst @@ -982,6 +982,45 @@ Index Operations client.index_map_values_create('test', 'demo', 'fav_movies', aerospike.INDEX_NUMERIC, 'demo_fav_movies_views_idx') client.close() + .. method:: index_cdt_create(ns, set, bin, index_type, index_datatype, name, ctx[, policy: dict]) + + Create an index named *name* for numeric, string or GeoJSON values \ + (as defined by *index_datatype*) on records of the specified *ns*, *set* \ + whose *bin* is a Collection Data Type whose context is *ctx*. *index_type* describes the CDT type. + + :param str ns: the namespace in the aerospike cluster. + :param str set: the set name. + :param str bin: the name of bin the secondary index is built on. + :param index_type: Possible values are ``aerospike.INDEX_TYPE_DEFAULT``, ``aerospike.INDEX_TYPE_LIST``, ``aerospike.INDEX_TYPE_MAPKEYS``, and ``aerospike.INDEX_TYPE_MAPVALUES`` + :param index_datatype: Possible values are ``aerospike.INDEX_STRING``, ``aerospike.INDEX_NUMERIC``, ``aerospike.INDEX_BLOB``, and ``aerospike.INDEX_GEO2DSPHERE``. + :param str name: the name of the index. + :param list ctx: the :class:`list` produced by one of the :mod:`aerospike_helpers.cdt_ctx` methods. + :param dict policy: optional :ref:`aerospike_info_policies`. + :raises: a subclass of :exc:`~aerospike.exception.AerospikeError`. + + .. note:: Requires server version >= 4.6.0 + + .. code-block:: python + + import aerospike + + client = aerospike.client({ 'hosts': [ ('127.0.0.1', 3000)]}) + + # assume the bin fav_movies in the set test.demo bin should contain + # a dict { (str) _title_ : (int) _times_viewed_ } + # create a secondary index for string values of test.demo records whose 'fav_movies' bin is a map inside the `martha` map. + ctx_map_rank = [] + ctx_map_rank.append(add_ctx_op(map_rank, -1)) + index_cdt_create("test", "demo", "martha", aerospike.INDEX_TYPE_MAPKEYS, aerospike.INDEX_STRING, "demo_fav_movies_titles_idx", ctx_map_rank + "test_string_list_cdt_index", + ctx_map_rank, + policy, + ) + client.index_map_keys_create('test', 'demo', 'fav_movies', aerospike.INDEX_STRING, 'demo_fav_movies_titles_idx') + # create a secondary index for integer values of test.demo records whose 'fav_movies' bin is a map + client.index_map_values_create('test', 'demo', 'fav_movies', aerospike.INDEX_NUMERIC, 'demo_fav_movies_views_idx') + client.close() + .. method:: index_geo2dsphere_create(ns, set, bin, name[, policy: dict]) Create a geospatial 2D spherical index with *name* on the *bin* \ diff --git a/src/include/client.h b/src/include/client.h index 3f442e014c..fb265660e0 100644 --- a/src/include/client.h +++ b/src/include/client.h @@ -365,7 +365,7 @@ PyObject *AerospikeClient_Index_Blob_Create(AerospikeClient *self, /** * Create secondary cdt index * - * client.index_cdt_create(namespace, set, bin, index_name, ctx, policy) + * client.index_cdt_create(namespace, set, bin, index_type, index_datatype, index_name, ctx, policy) * */ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, diff --git a/src/main/client/sec_index.c b/src/main/client/sec_index.c index 0276148b6d..fd5dab65c9 100644 --- a/src/main/client/sec_index.c +++ b/src/main/client/sec_index.c @@ -181,8 +181,10 @@ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, PyObject *py_datatype = NULL; PyObject *py_name = NULL; PyObject *py_ctx = NULL; + PyObject *py_ctx_dict = NULL; as_cdt_ctx ctx; bool ctx_in_use = false; + bool new_dict_in_use = false; PyObject *py_obj = NULL; as_index_datatype data_type; as_index_type index_type; @@ -208,10 +210,19 @@ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, goto CLEANUP; } + if(PyList_Check(py_ctx)){ + py_ctx_dict = PyDict_New(); + PyDict_SetItemString(py_ctx_dict, "ctx", py_ctx); + bool new_dict_in_use = true; + } + else{ + py_ctx_dict = py_ctx; + } + as_static_pool static_pool; memset(&static_pool, 0, sizeof(static_pool)); - if (get_cdt_ctx(self, &err, &ctx, py_ctx, &ctx_in_use, &static_pool, + if (get_cdt_ctx(self, &err, &ctx, py_ctx_dict, &ctx_in_use, &static_pool, SERIALIZER_PYTHON) != AEROSPIKE_OK) { goto CLEANUP; } @@ -237,6 +248,9 @@ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, } PyErr_SetObject(exception_type, py_err); Py_DECREF(py_err); + if(new_dict_in_use){ + Py_XDECREF(py_ctx_dict); + } return NULL; } diff --git a/src/main/conversions.c b/src/main/conversions.c index 32dcbcbb54..13f678dfc3 100644 --- a/src/main/conversions.c +++ b/src/main/conversions.c @@ -2382,13 +2382,7 @@ as_status get_cdt_ctx(AerospikeClient *self, as_error *err, as_cdt_ctx *cdt_ctx, PyObject *op_dict, bool *ctx_in_use, as_static_pool *static_pool, int serializer_type) { - PyObject *py_ctx = NULL; - if(PyDict_Check(op_dict)){ - py_ctx = PyDict_GetItemString(op_dict, CTX_KEY); - } - else{ - py_ctx = op_dict; - } + PyObject *py_ctx = PyDict_GetItemString(op_dict, CTX_KEY); long int_val = 0; diff --git a/src/main/query/where.c b/src/main/query/where.c index fc3a25195e..47ea50809b 100644 --- a/src/main/query/where.c +++ b/src/main/query/where.c @@ -52,6 +52,8 @@ static int AerospikeQuery_Where_Add(AerospikeQuery *self, PyObject *py_ctx, PyObject *py_ubin = NULL; as_cdt_ctx *pctx = NULL; bool ctx_in_use = false; + bool new_dict_in_use = false; + PyObject *py_ctx_dict = NULL; int rc = 0; if (py_ctx) { @@ -59,10 +61,24 @@ static int AerospikeQuery_Where_Add(AerospikeQuery *self, PyObject *py_ctx, memset(&static_pool, 0, sizeof(static_pool)); pctx = cf_malloc(sizeof(as_cdt_ctx)); memset(pctx, 0, sizeof(as_cdt_ctx)); - if (get_cdt_ctx(self->client, &err, pctx, py_ctx, &ctx_in_use, + if(PyList_Check(py_ctx)){ + py_ctx_dict = PyDict_New(); + PyDict_SetItemString(py_ctx_dict, "ctx", py_ctx); + new_dict_in_use = true; + } + else{ + py_ctx_dict = py_ctx; + } + if (get_cdt_ctx(self->client, &err, pctx, py_ctx_dict, &ctx_in_use, &static_pool, SERIALIZER_PYTHON) != AEROSPIKE_OK) { + if(new_dict_in_use){ + Py_XDECREF(py_ctx_dict); + } return err.code; } + if(new_dict_in_use){ + Py_XDECREF(py_ctx_dict); + } if (!ctx_in_use) { cf_free(pctx); pctx = NULL; diff --git a/test/new_tests/test_cdt_index.py b/test/new_tests/test_cdt_index.py index 947acd12af..886f13ebd7 100644 --- a/test/new_tests/test_cdt_index.py +++ b/test/new_tests/test_cdt_index.py @@ -110,6 +110,26 @@ def test_pos_cdtindex_with_correct_parameters(self): self.as_connection.index_remove("test", "test_string_list_cdt_index", policy) ensure_dropped_index(self.as_connection, "test", "test_string_list_cdt_index") + assert retobj == 0 + def test_pos_cdtindex_dict_with_correct_parameters(self): + """ + Invoke index_cdt_create() with correct arguments + """ + policy = {} + retobj = self.as_connection.index_cdt_create( + "test", + "demo", + "string_list", + aerospike.INDEX_TYPE_LIST, + aerospike.INDEX_STRING, + "test_string_list_cdt_index", + {"ctx": ctx_list_index}, + policy, + ) + + self.as_connection.index_remove("test", "test_string_list_cdt_index", policy) + ensure_dropped_index(self.as_connection, "test", "test_string_list_cdt_index") + assert retobj == 0 def test_pos_cdtindex_with_info_command(self): diff --git a/test/new_tests/test_query.py b/test/new_tests/test_query.py index c61a235c9d..dfb210b02f 100644 --- a/test/new_tests/test_query.py +++ b/test/new_tests/test_query.py @@ -1036,7 +1036,7 @@ def test_query_with_list_cdt_ctx(self): query = self.as_connection.query("test", "demo") query.select("numeric_list") - query.where(p.range("numeric_list", aerospike.INDEX_TYPE_DEFAULT, 2, 4), {"ctx": ctx_list_index}) + query.where(p.range("numeric_list", aerospike.INDEX_TYPE_DEFAULT, 2, 4), ctx_list_index) records = [] @@ -1063,6 +1063,33 @@ def test_query_with_map_cdt_ctx(self): query = self.as_connection.query("test", "demo") query.select("numeric_map") + query.where(p.range("numeric_map", aerospike.INDEX_TYPE_DEFAULT, 2, 4), ctx_map_index) + + records = [] + + def callback(input_tuple): + try: + records.append(input_tuple) + except Exception as ex: + print(ex) + + query.foreach(callback) + + assert records + assert len(records) == 3 + + def test_query_with_map_cdt_ctx_dict(self): + """ + Invoke query() with cdt_ctx and correct arguments + """ + from .test_base_class import TestBaseClass + + if TestBaseClass.major_ver < 6 or (TestBaseClass.major_ver == 6 and TestBaseClass.minor_ver == 0): + pytest.skip("It only applies to >= 6.1 enterprise edition") + + query = self.as_connection.query("test", "demo") + query.select("numeric_map") + query.where(p.range("numeric_map", aerospike.INDEX_TYPE_DEFAULT, 2, 4), {"ctx": ctx_map_index}) records = [] From 0afea08480e060d459b9ced105b81f48ab6dbdf1 Mon Sep 17 00:00:00 2001 From: Dominic Pelini <111786059+DomPeliniAerospike@users.noreply.github.com> Date: Mon, 5 Feb 2024 09:42:48 -0700 Subject: [PATCH 03/36] Fixed compiler warning --- src/main/client/sec_index.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/client/sec_index.c b/src/main/client/sec_index.c index fd5dab65c9..ac6b0348c7 100644 --- a/src/main/client/sec_index.c +++ b/src/main/client/sec_index.c @@ -213,7 +213,7 @@ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, if(PyList_Check(py_ctx)){ py_ctx_dict = PyDict_New(); PyDict_SetItemString(py_ctx_dict, "ctx", py_ctx); - bool new_dict_in_use = true; + new_dict_in_use = true; } else{ py_ctx_dict = py_ctx; From 2f83de6ae37099dd53184e09ec14411487133c6a Mon Sep 17 00:00:00 2001 From: Dominic Pelini <111786059+DomPeliniAerospike@users.noreply.github.com> Date: Mon, 5 Feb 2024 12:19:05 -0700 Subject: [PATCH 04/36] Ran precommit lint Ran precommit lint --- src/main/client/sec_index.c | 8 ++++---- src/main/conversions.c | 1 - src/main/query/where.c | 12 ++++++------ test/new_tests/test_cdt_index.py | 1 + 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/client/sec_index.c b/src/main/client/sec_index.c index ac6b0348c7..2f91a61a05 100644 --- a/src/main/client/sec_index.c +++ b/src/main/client/sec_index.c @@ -210,12 +210,12 @@ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, goto CLEANUP; } - if(PyList_Check(py_ctx)){ + if (PyList_Check(py_ctx)) { py_ctx_dict = PyDict_New(); PyDict_SetItemString(py_ctx_dict, "ctx", py_ctx); new_dict_in_use = true; } - else{ + else { py_ctx_dict = py_ctx; } @@ -248,8 +248,8 @@ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, } PyErr_SetObject(exception_type, py_err); Py_DECREF(py_err); - if(new_dict_in_use){ - Py_XDECREF(py_ctx_dict); + if (new_dict_in_use) { + Py_XDECREF(py_ctx_dict); } return NULL; } diff --git a/src/main/conversions.c b/src/main/conversions.c index 13f678dfc3..cc1b209ce0 100644 --- a/src/main/conversions.c +++ b/src/main/conversions.c @@ -2384,7 +2384,6 @@ as_status get_cdt_ctx(AerospikeClient *self, as_error *err, as_cdt_ctx *cdt_ctx, { PyObject *py_ctx = PyDict_GetItemString(op_dict, CTX_KEY); - long int_val = 0; as_val *val = NULL; diff --git a/src/main/query/where.c b/src/main/query/where.c index 47ea50809b..9a747c80c6 100644 --- a/src/main/query/where.c +++ b/src/main/query/where.c @@ -61,23 +61,23 @@ static int AerospikeQuery_Where_Add(AerospikeQuery *self, PyObject *py_ctx, memset(&static_pool, 0, sizeof(static_pool)); pctx = cf_malloc(sizeof(as_cdt_ctx)); memset(pctx, 0, sizeof(as_cdt_ctx)); - if(PyList_Check(py_ctx)){ + if (PyList_Check(py_ctx)) { py_ctx_dict = PyDict_New(); PyDict_SetItemString(py_ctx_dict, "ctx", py_ctx); new_dict_in_use = true; } - else{ + else { py_ctx_dict = py_ctx; } if (get_cdt_ctx(self->client, &err, pctx, py_ctx_dict, &ctx_in_use, &static_pool, SERIALIZER_PYTHON) != AEROSPIKE_OK) { - if(new_dict_in_use){ - Py_XDECREF(py_ctx_dict); + if (new_dict_in_use) { + Py_XDECREF(py_ctx_dict); } return err.code; } - if(new_dict_in_use){ - Py_XDECREF(py_ctx_dict); + if (new_dict_in_use) { + Py_XDECREF(py_ctx_dict); } if (!ctx_in_use) { cf_free(pctx); diff --git a/test/new_tests/test_cdt_index.py b/test/new_tests/test_cdt_index.py index 886f13ebd7..236fd356da 100644 --- a/test/new_tests/test_cdt_index.py +++ b/test/new_tests/test_cdt_index.py @@ -111,6 +111,7 @@ def test_pos_cdtindex_with_correct_parameters(self): ensure_dropped_index(self.as_connection, "test", "test_string_list_cdt_index") assert retobj == 0 + def test_pos_cdtindex_dict_with_correct_parameters(self): """ Invoke index_cdt_create() with correct arguments From 8475d8eb483efcfc69271da8824c68bda1505e2a Mon Sep 17 00:00:00 2001 From: Dominic Pelini Date: Fri, 14 Nov 2025 10:30:45 -0700 Subject: [PATCH 05/36] Fixed compile issue --- src/include/exceptions.h | 1 - src/main/client/sec_index.c | 14 +++----------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/include/exceptions.h b/src/include/exceptions.h index f57673c912..ffa34d1f69 100644 --- a/src/include/exceptions.h +++ b/src/include/exceptions.h @@ -23,7 +23,6 @@ void raise_exception(as_error *err); void raise_exception_base(as_error *err, PyObject *py_key, PyObject *py_bin, PyObject *py_module, PyObject *py_func, PyObject *py_name); -PyObject *raise_exception_old(as_error *err); void remove_exception(as_error *err); void set_aerospike_exc_attrs_using_tuple_of_attrs(PyObject *py_exc, PyObject *py_tuple); diff --git a/src/main/client/sec_index.c b/src/main/client/sec_index.c index 01d46fb252..17fa55ea00 100644 --- a/src/main/client/sec_index.c +++ b/src/main/client/sec_index.c @@ -265,6 +265,7 @@ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, SERIALIZER_PYTHON) != AEROSPIKE_OK) { goto CLEANUP; } + Py_XDECREF(py_ctx_dict); if (!ctx_in_use) { goto CLEANUP; } @@ -278,18 +279,9 @@ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, return py_obj; CLEANUP: + if (py_obj == NULL) { - PyObject *py_err = NULL; - error_to_pyobject(&err, &py_err); - PyObject *exception_type = raise_exception_old(&err); - if (PyObject_HasAttrString(exception_type, "name")) { - PyObject_SetAttrString(exception_type, "name", py_name); - } - PyErr_SetObject(exception_type, py_err); - Py_DECREF(py_err); - if (new_dict_in_use) { - Py_XDECREF(py_ctx_dict); - } + raise_exception_base(&err, Py_None, Py_None, Py_None, Py_None, py_name); return NULL; } return py_obj; From 9be33255f49cbda9ca420dc0826f2e751d0b55e6 Mon Sep 17 00:00:00 2001 From: Dominic Pelini Date: Fri, 14 Nov 2025 10:38:12 -0700 Subject: [PATCH 06/36] Update sec_index.c --- src/main/client/sec_index.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/client/sec_index.c b/src/main/client/sec_index.c index 17fa55ea00..62137cfc37 100644 --- a/src/main/client/sec_index.c +++ b/src/main/client/sec_index.c @@ -265,7 +265,9 @@ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, SERIALIZER_PYTHON) != AEROSPIKE_OK) { goto CLEANUP; } - Py_XDECREF(py_ctx_dict); + if(new_dict_in_use){ + Py_DECREF(py_ctx_dict); + } if (!ctx_in_use) { goto CLEANUP; } From a6cd67b9aca00e35c395639946d5684a4886c5a1 Mon Sep 17 00:00:00 2001 From: Dominic Pelini Date: Fri, 14 Nov 2025 10:58:36 -0700 Subject: [PATCH 07/36] Fixed linting issue --- src/main/client/sec_index.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/client/sec_index.c b/src/main/client/sec_index.c index 62137cfc37..72293da02f 100644 --- a/src/main/client/sec_index.c +++ b/src/main/client/sec_index.c @@ -265,7 +265,7 @@ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, SERIALIZER_PYTHON) != AEROSPIKE_OK) { goto CLEANUP; } - if(new_dict_in_use){ + if (new_dict_in_use) { Py_DECREF(py_ctx_dict); } if (!ctx_in_use) { From 1d76db08e1eca8c9c78f7f7fe234491b38901d5e Mon Sep 17 00:00:00 2001 From: Dominic Pelini Date: Mon, 17 Nov 2025 14:49:56 -0700 Subject: [PATCH 08/36] Fixed doc build error --- doc/client.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/client.rst b/doc/client.rst index 7dafbb8cbd..7cdfd1a931 100755 --- a/doc/client.rst +++ b/doc/client.rst @@ -975,10 +975,10 @@ Index Operations ctx_map_rank = [] ctx_map_rank.append(add_ctx_op(map_rank, -1)) index_cdt_create("test", "demo", "martha", aerospike.INDEX_TYPE_MAPKEYS, aerospike.INDEX_STRING, "demo_fav_movies_titles_idx", ctx_map_rank - "test_string_list_cdt_index", - ctx_map_rank, - policy, - ) + "test_string_list_cdt_index", + ctx_map_rank, + policy, + ) client.index_map_keys_create('test', 'demo', 'fav_movies', aerospike.INDEX_STRING, 'demo_fav_movies_titles_idx') # create a secondary index for integer values of test.demo records whose 'fav_movies' bin is a map client.index_map_values_create('test', 'demo', 'fav_movies', aerospike.INDEX_NUMERIC, 'demo_fav_movies_views_idx') From dc2df7bf6eeef9129e3728dae62e35ff7621eab1 Mon Sep 17 00:00:00 2001 From: Dominic Pelini Date: Tue, 18 Nov 2025 13:21:44 -0700 Subject: [PATCH 09/36] Added test coverage Fixed memory leak in get_cdt_ctx --- src/main/query/where.c | 10 ++-------- test/new_tests/test_query.py | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/main/query/where.c b/src/main/query/where.c index 7cec06bd96..6dfec197dc 100644 --- a/src/main/query/where.c +++ b/src/main/query/where.c @@ -79,18 +79,12 @@ static int AerospikeQuery_Where_Add(AerospikeQuery *self, PyObject *py_ctx, } if (get_cdt_ctx(self->client, &err, pctx, py_ctx_dict, &ctx_in_use, &static_pool, SERIALIZER_PYTHON) != AEROSPIKE_OK) { - if (new_dict_in_use) { - Py_XDECREF(py_ctx_dict); - } - return err.code; - } - if (new_dict_in_use) { Py_XDECREF(py_ctx_dict); - } - if (!ctx_in_use) { cf_free(pctx); pctx = NULL; + return err.code; } + Py_XDECREF(py_ctx_dict); } as_exp *exp_list = NULL; diff --git a/test/new_tests/test_query.py b/test/new_tests/test_query.py index 2df77aad5e..bc2ac4c1c6 100644 --- a/test/new_tests/test_query.py +++ b/test/new_tests/test_query.py @@ -1140,6 +1140,24 @@ def callback(input_tuple): assert records assert len(records) == 3 + def test_query_with_invalid_list_cdt_ctx_dict(self): + """ + Invoke query() with cdt_ctx containing incorrect arguments + """ + from .test_base_class import TestBaseClass + + if TestBaseClass.major_ver < 6 or (TestBaseClass.major_ver == 6 and TestBaseClass.minor_ver == 0): + pytest.skip("It only applies to >= 6.1 enterprise edition") + + query = self.as_connection.query("test", "demo") + + with pytest.raises(e.ParamError) as param_error: + query.where(p.range("numeric_map", aerospike.INDEX_TYPE_DEFAULT, 2, 4), ['not a ctx list']) + + assert param_error.value.msg == "Failed to add predicate" + assert param_error.value.code == -2 + + def test_query_with_base64_cdt_ctx(self): bs_b4_cdt = self.as_connection.get_cdtctx_base64(ctx_list_index) assert bs_b4_cdt == "khAA" From 1012a31375918c2138f3187d8634d30daf11e1be Mon Sep 17 00:00:00 2001 From: Dominic Pelini Date: Tue, 18 Nov 2025 15:04:50 -0700 Subject: [PATCH 10/36] Update where.c --- src/main/query/where.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/query/where.c b/src/main/query/where.c index 6dfec197dc..eec1fcfd68 100644 --- a/src/main/query/where.c +++ b/src/main/query/where.c @@ -84,7 +84,9 @@ static int AerospikeQuery_Where_Add(AerospikeQuery *self, PyObject *py_ctx, pctx = NULL; return err.code; } - Py_XDECREF(py_ctx_dict); + if(new_dict_in_use){ + Py_DECREF(py_ctx_dict); + } } as_exp *exp_list = NULL; From c1bd2fb77e33d9f414cb1b2c0aa18e40ff50fba5 Mon Sep 17 00:00:00 2001 From: Dominic Pelini Date: Tue, 18 Nov 2025 15:11:56 -0700 Subject: [PATCH 11/36] Fixed linting issue Fixed misuse of Py_Decref --- src/main/query/where.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/query/where.c b/src/main/query/where.c index eec1fcfd68..2afab43e03 100644 --- a/src/main/query/where.c +++ b/src/main/query/where.c @@ -79,12 +79,14 @@ static int AerospikeQuery_Where_Add(AerospikeQuery *self, PyObject *py_ctx, } if (get_cdt_ctx(self->client, &err, pctx, py_ctx_dict, &ctx_in_use, &static_pool, SERIALIZER_PYTHON) != AEROSPIKE_OK) { - Py_XDECREF(py_ctx_dict); + if (new_dict_in_use) { + Py_DECREF(py_ctx_dict); + } cf_free(pctx); pctx = NULL; return err.code; } - if(new_dict_in_use){ + if (new_dict_in_use) { Py_DECREF(py_ctx_dict); } } From 7fd63beab1cee960e94e64a72cebc536427ee16c Mon Sep 17 00:00:00 2001 From: Dominic Pelini Date: Tue, 18 Nov 2025 15:26:00 -0700 Subject: [PATCH 12/36] Update test_query.py --- test/new_tests/test_query.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/new_tests/test_query.py b/test/new_tests/test_query.py index bc2ac4c1c6..429634abb4 100644 --- a/test/new_tests/test_query.py +++ b/test/new_tests/test_query.py @@ -1076,6 +1076,7 @@ def test_query_with_list_cdt_ctx_and_invalid_bin(self): Make sure that ctx is being cleaned up properly """ query = self.as_connection.query("test", "demo") + # Invalid bin with pytest.raises(e.ParamError): query.where(p.range(5, aerospike.INDEX_TYPE_DEFAULT, 2, 4), {"ctx": ctx_list_index}) @@ -1144,10 +1145,6 @@ def test_query_with_invalid_list_cdt_ctx_dict(self): """ Invoke query() with cdt_ctx containing incorrect arguments """ - from .test_base_class import TestBaseClass - - if TestBaseClass.major_ver < 6 or (TestBaseClass.major_ver == 6 and TestBaseClass.minor_ver == 0): - pytest.skip("It only applies to >= 6.1 enterprise edition") query = self.as_connection.query("test", "demo") From 1ccc8e680e05d6cc0c7d68098af6c11c3e3f9e26 Mon Sep 17 00:00:00 2001 From: Dominic Pelini <111786059+DomPeliniAerospike@users.noreply.github.com> Date: Tue, 18 Nov 2025 15:36:08 -0700 Subject: [PATCH 13/36] FF CLIENT-2383 to Dev (#867) * [CLIENT-3793] Remove macOS 13 support (#846) * Auto-bump version to 18.1.0rc3.dev1 [skip ci] * [CLIENT-3106] Remove dead code in conversions.c (#817) - record_to_resultpyobject() was a helper function for client.batch_get_ops(), which is now removed. - record_to_pyobject_cnvt_list_to_map() and as_list_of_map_to_py_tuple_list(): these were helper functions that were used before Python client version 2.1.3 to return the result of certain map operations. They are no longer used starting from Python client 2.1.3 and higher, so it is safe to remove - bin_strict_type_checking() isn't used anywhere. But it would be good to consolidate the bin checking code into one place, since it is currently spread out all over the codebase - as_batch_read_results_to_pyobject() was used by get_many() which has been removed - batch_read_records_to_pyobject() was used by select_many() which has been removed Extra Changes Merge do_*_to_pyobject() methods into their calling methods, since they have the same function signature * Auto-bump version to 18.1.0rc3.dev2 [skip ci] --------- Co-authored-by: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/workflows/build-wheels.yml | 4 +- .github/workflows/stage-tests.yml | 1 - README.rst | 2 +- VERSION | 2 +- src/include/conversions.h | 38 +--- src/main/conversions.c | 334 ++--------------------------- 6 files changed, 17 insertions(+), 364 deletions(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index adee326bb9..7b92afe981 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -161,8 +161,8 @@ jobs: declare -A hashmap hashmap[manylinux_x86_64]="ubuntu-22.04" hashmap[manylinux_aarch64]="ubuntu-22.04-arm" - hashmap[macosx_x86_64]="macos-13-large" - hashmap[macosx_arm64]="macos-13-xlarge" + hashmap[macosx_x86_64]="macos-14-large" + hashmap[macosx_arm64]="macos-14" hashmap[win_amd64]="windows-2022" echo runner_os=${hashmap[${{ inputs.platform-tag }}]} >> $GITHUB_OUTPUT # Bash >= 4 supports hashmaps diff --git a/.github/workflows/stage-tests.yml b/.github/workflows/stage-tests.yml index 29b686d19c..c3beebb9c2 100644 --- a/.github/workflows/stage-tests.yml +++ b/.github/workflows/stage-tests.yml @@ -190,7 +190,6 @@ jobs: matrix: runner-os: [ # These larger runners run on intel - macos-14-large, macos-15-large ] python-version: [ diff --git a/README.rst b/README.rst index 82e62e1842..48eadb3668 100644 --- a/README.rst +++ b/README.rst @@ -13,7 +13,7 @@ Compatibility The Python client for Aerospike works with Python 3.9 - 3.14 and supports the following OS'es: -* macOS 13 - 15 +* macOS 14 - 15 * RHEL 8 and 9 * Amazon Linux 2023 * Debian 11, 12, and 13 diff --git a/VERSION b/VERSION index c931b95d4c..3a6ccf6571 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -18.1.0rc2 +18.1.0rc3.dev2 diff --git a/src/include/conversions.h b/src/include/conversions.h index 4b8bb72997..91504bf629 100644 --- a/src/include/conversions.h +++ b/src/include/conversions.h @@ -110,52 +110,29 @@ as_status as_record_init_from_pyobject(AerospikeClient *self, as_error *err, as_status val_to_pyobject(AerospikeClient *self, as_error *err, const as_val *val, PyObject **py_map); -as_status val_to_pyobject_cnvt_list_to_map(AerospikeClient *self, as_error *err, - const as_val *val, - PyObject **py_map); - as_status map_to_pyobject(AerospikeClient *self, as_error *err, const as_map *map, PyObject **py_map); as_status list_to_pyobject(AerospikeClient *self, as_error *err, const as_list *list, PyObject **py_list); -as_status as_list_of_map_to_py_tuple_list(AerospikeClient *self, as_error *err, - const as_list *list, - PyObject **py_list); - as_status record_to_pyobject(AerospikeClient *self, as_error *err, const as_record *rec, const as_key *key, PyObject **obj); -as_status record_to_resultpyobject(AerospikeClient *self, as_error *err, - const as_record *rec, PyObject **obj); - as_status operate_bins_to_pyobject(AerospikeClient *self, as_error *err, const as_record *rec, PyObject **py_bins); -as_status record_to_pyobject_cnvt_list_to_map(AerospikeClient *self, - as_error *err, - const as_record *rec, - const as_key *key, - PyObject **obj); - as_status key_to_pyobject(as_error *err, const as_key *key, PyObject **obj); as_status metadata_to_pyobject(as_error *err, const as_record *rec, PyObject **obj); as_status bins_to_pyobject(AerospikeClient *self, as_error *err, - const as_record *rec, PyObject **obj, - bool cnvt_list_to_map); + const as_record *rec, PyObject **obj); void error_to_pyobject(const as_error *err, PyObject **obj); -as_status pyobject_to_astype_write(AerospikeClient *self, as_error *err, - PyObject *py_value, as_val **val, - as_static_pool *static_pool, - int serializer_type); - as_status as_privilege_to_pyobject(as_error *err, as_privilege privileges[], PyObject *py_as_privilege, int privilege_size); @@ -180,25 +157,12 @@ void initialize_bin_for_strictypes(AerospikeClient *self, as_error *err, PyObject *py_value, as_binop *binop, char *bin, as_static_pool *static_pool); -as_status bin_strict_type_checking(AerospikeClient *self, as_error *err, - PyObject *py_bin, char **bin); - // Both as_operations and as_record have ttl and gen fields, // so we have ttl and gen as separate parameters instead of accepting either as_operations or as_record as_status check_and_set_meta(PyObject *py_meta, uint32_t *ttl_ref, uint16_t *gen_ref, as_error *err, bool validate_keys); -as_status as_batch_read_results_to_pyobject(as_error *err, - AerospikeClient *client, - const as_batch_read *results, - uint32_t size, - PyObject **py_records); - -as_status batch_read_records_to_pyobject(AerospikeClient *self, as_error *err, - as_batch_read_records *records, - PyObject **py_recs); - as_status string_and_pyuni_from_pystring(PyObject *py_string, PyObject **pyuni_r, char **c_str_ptr, as_error *err); diff --git a/src/main/conversions.c b/src/main/conversions.c index 0360b70994..b9e446c5c4 100644 --- a/src/main/conversions.c +++ b/src/main/conversions.c @@ -1604,9 +1604,8 @@ typedef struct { void *udata; } conversion_data; -as_status do_val_to_pyobject(AerospikeClient *self, as_error *err, - const as_val *val, PyObject **py_val, - bool cnvt_list_to_map) +as_status val_to_pyobject(AerospikeClient *self, as_error *err, + const as_val *val, PyObject **py_val) { as_error_reset(err); switch (as_val_type(val)) { @@ -1669,12 +1668,7 @@ as_status do_val_to_pyobject(AerospikeClient *self, as_error *err, as_list *l = as_list_fromval((as_val *)val); if (l) { PyObject *py_list = NULL; - if (cnvt_list_to_map) { - as_list_of_map_to_py_tuple_list(self, err, l, &py_list); - } - else { - list_to_pyobject(self, err, l, &py_list); - } + list_to_pyobject(self, err, l, &py_list); if (err->code == AEROSPIKE_OK) { *py_val = py_list; } @@ -1732,82 +1726,6 @@ as_status do_val_to_pyobject(AerospikeClient *self, as_error *err, return err->code; } -as_status val_to_pyobject(AerospikeClient *self, as_error *err, - const as_val *val, PyObject **py_val) -{ - return do_val_to_pyobject(self, err, val, py_val, false); -} - -as_status val_to_pyobject_cnvt_list_to_map(AerospikeClient *self, as_error *err, - const as_val *val, PyObject **py_val) -{ - return do_val_to_pyobject(self, err, val, py_val, true); -} - -as_status as_list_of_map_to_py_tuple_list(AerospikeClient *self, as_error *err, - const as_list *list, - PyObject **py_list) -{ - PyObject *py_tuple = NULL; - - int size = as_list_size((as_list *)list); - - if (size % 2 != 0) { - return as_error_update(err, AEROSPIKE_ERR_CLIENT, - "Invalid key list of key/value pairs"); - } - - *py_list = PyList_New(0); - if (!*py_list) { - return as_error_update(err, AEROSPIKE_ERR_CLIENT, - "Failed to allocate memory for list."); - } - - for (int i = 0; i < size; i += 2) { - as_val *key = as_list_get(list, i); - as_val *value = as_list_get(list, i + 1); - - if (!key || !value) { - as_error_update(err, AEROSPIKE_ERR_CLIENT, - "Null object found in returned list"); - goto CLEANUP; - } - - PyObject *py_key = NULL; - PyObject *py_value = NULL; - - if (val_to_pyobject(self, err, key, &py_key) != AEROSPIKE_OK) { - goto CLEANUP; - } - if (val_to_pyobject(self, err, value, &py_value) != AEROSPIKE_OK) { - Py_XDECREF(py_key); - goto CLEANUP; - } - py_tuple = PyTuple_New(2); - - if (!py_tuple) { - as_error_update(err, AEROSPIKE_ERR_CLIENT, - "Failed to allocate memory for tuple"); - Py_XDECREF(py_key); - Py_XDECREF(py_value); - goto CLEANUP; - } - - PyTuple_SetItem(py_tuple, 0, py_key); - PyTuple_SetItem(py_tuple, 1, py_value); - - PyList_Append(*py_list, py_tuple); - Py_DECREF(py_tuple); - } - -CLEANUP: - if (err->code != AEROSPIKE_OK) { - Py_DECREF(*py_list); - } - - return err->code; -} - static bool list_to_pyobject_each(as_val *val, void *udata) { if (!val) { @@ -1944,9 +1862,9 @@ as_status map_to_pyobject(AerospikeClient *self, as_error *err, return err->code; } -as_status do_record_to_pyobject(AerospikeClient *self, as_error *err, - const as_record *rec, const as_key *key, - PyObject **obj, bool cnvt_list_to_map) +as_status record_to_pyobject(AerospikeClient *self, as_error *err, + const as_record *rec, const as_key *key, + PyObject **obj) { as_error_reset(err); *obj = NULL; @@ -1970,8 +1888,7 @@ as_status do_record_to_pyobject(AerospikeClient *self, as_error *err, return err->code; } - if (bins_to_pyobject(self, err, rec, &py_rec_bins, cnvt_list_to_map) != - AEROSPIKE_OK) { + if (bins_to_pyobject(self, err, rec, &py_rec_bins) != AEROSPIKE_OK) { Py_CLEAR(py_rec_key); Py_CLEAR(py_rec_meta); return err->code; @@ -2001,62 +1918,6 @@ as_status do_record_to_pyobject(AerospikeClient *self, as_error *err, return err->code; } -as_status record_to_resultpyobject(AerospikeClient *self, as_error *err, - const as_record *rec, PyObject **obj) -{ - as_error_reset(err); - *obj = NULL; - - if (!rec) { - return as_error_update(err, AEROSPIKE_ERR_CLIENT, "record is null"); - } - - PyObject *py_rec = NULL; - PyObject *py_rec_meta = NULL; - PyObject *py_rec_bins = NULL; - - if (metadata_to_pyobject(err, rec, &py_rec_meta) != AEROSPIKE_OK) { - return err->code; - } - - if (bins_to_pyobject(self, err, rec, &py_rec_bins, false) != AEROSPIKE_OK) { - Py_CLEAR(py_rec_meta); - return err->code; - } - - if (!py_rec_meta) { - Py_INCREF(Py_None); - py_rec_meta = Py_None; - } - - if (!py_rec_bins) { - Py_INCREF(Py_None); - py_rec_bins = Py_None; - } - - py_rec = PyTuple_New(2); - PyTuple_SetItem(py_rec, 0, py_rec_meta); - PyTuple_SetItem(py_rec, 1, py_rec_bins); - - *obj = py_rec; - return err->code; -} - -as_status record_to_pyobject(AerospikeClient *self, as_error *err, - const as_record *rec, const as_key *key, - PyObject **obj) -{ - return do_record_to_pyobject(self, err, rec, key, obj, false); -} - -as_status record_to_pyobject_cnvt_list_to_map(AerospikeClient *self, - as_error *err, - const as_record *rec, - const as_key *key, PyObject **obj) -{ - return do_record_to_pyobject(self, err, rec, key, obj, true); -} - as_status key_to_pyobject(as_error *err, const as_key *key, PyObject **obj) { as_error_reset(err); @@ -2159,8 +2020,8 @@ as_status key_to_pyobject(as_error *err, const as_key *key, PyObject **obj) return err->code; } -static bool do_bins_to_pyobject_each(const char *name, const as_val *val, - void *udata, bool cnvt_list_to_map) +static bool bins_to_pyobject_each(const char *name, const as_val *val, + void *udata) { if (!name || !val) { return false; @@ -2171,12 +2032,7 @@ static bool do_bins_to_pyobject_each(const char *name, const as_val *val, PyObject *py_bins = (PyObject *)convd->udata; PyObject *py_val = NULL; - if (cnvt_list_to_map) { - val_to_pyobject_cnvt_list_to_map(convd->client, err, val, &py_val); - } - else { - val_to_pyobject(convd->client, err, val, &py_val); - } + val_to_pyobject(convd->client, err, val, &py_val); if (err->code != AEROSPIKE_OK) { return false; @@ -2190,22 +2046,8 @@ static bool do_bins_to_pyobject_each(const char *name, const as_val *val, return true; } -static bool bins_to_pyobject_each_cnvt_list_to_map(const char *name, - const as_val *val, - void *udata) -{ - return do_bins_to_pyobject_each(name, val, udata, true); -} - -static bool bins_to_pyobject_each(const char *name, const as_val *val, - void *udata) -{ - return do_bins_to_pyobject_each(name, val, udata, false); -} - as_status bins_to_pyobject(AerospikeClient *self, as_error *err, - const as_record *rec, PyObject **py_bins, - bool cnvt_list_to_map) + const as_record *rec, PyObject **py_bins) { as_error_reset(err); @@ -2219,10 +2061,7 @@ as_status bins_to_pyobject(AerospikeClient *self, as_error *err, conversion_data convd = { .err = err, .count = 0, .client = self, .udata = *py_bins}; - as_record_foreach(rec, - cnvt_list_to_map ? bins_to_pyobject_each_cnvt_list_to_map - : bins_to_pyobject_each, - &convd); + as_record_foreach(rec, bins_to_pyobject_each, &convd); if (err->code != AEROSPIKE_OK) { Py_DECREF(*py_bins); @@ -2430,41 +2269,6 @@ void initialize_bin_for_strictypes(AerospikeClient *self, as_error *err, strcpy(binop_bin->name, bin); } -// TODO: dead code -as_status bin_strict_type_checking(AerospikeClient *self, as_error *err, - PyObject *py_bin, char **bin) -{ - as_error_reset(err); - - if (py_bin) { - if (PyUnicode_Check(py_bin)) { - *bin = (char *)PyUnicode_AsUTF8(py_bin); - } - else if (PyByteArray_Check(py_bin)) { - *bin = PyByteArray_AsString(py_bin); - } - else { - as_error_update(err, AEROSPIKE_ERR_PARAM, - "Bin name should be of type string"); - goto CLEANUP; - } - - if (self->strict_types) { - if (strlen(*bin) > AS_BIN_NAME_MAX_LEN) { - as_error_update( - err, AEROSPIKE_ERR_BIN_NAME, - "A bin name should not exceed 15 characters limit"); - } - } - } - -CLEANUP: - if (err->code != AEROSPIKE_OK) { - raise_exception(err); - } - return err->code; -} - /** ******************************************************************************************************* * This function checks for metadata and if present set it into the @@ -2572,120 +2376,6 @@ as_status pyobject_to_index(AerospikeClient *self, as_error *err, return err->code; } -as_status as_batch_read_results_to_pyobject(as_error *err, - AerospikeClient *client, - const as_batch_read *results, - uint32_t size, - PyObject **py_records) -{ - *py_records = NULL; - PyObject *temp_py_recs = PyList_New(0); - - if (!temp_py_recs) { - return as_error_update(err, AEROSPIKE_ERR_CLIENT, - "Failed to allocate memory for batch results"); - } - - // Loop over results array - for (uint32_t i = 0; i < size; i++) { - PyObject *py_rec = NULL; - PyObject *py_key = NULL; - if (results[i].result == AEROSPIKE_OK) { - /* There was a record for the item, but we failed to convert it, probably a deserialize issue, error out */ - record_to_pyobject(client, err, &results[i].record, results[i].key, - &py_rec); - if (!py_rec || err->code != AEROSPIKE_OK) { - Py_XDECREF(temp_py_recs); - return err->code; - } - /* The record wasn't found, build a (key, None, None) tuple */ - } - else { - key_to_pyobject(err, results[i].key, &py_key); - if (!py_key || err->code != AEROSPIKE_OK) { - Py_XDECREF(temp_py_recs); - return err->code; - } - py_rec = Py_BuildValue("OOO", py_key, Py_None, Py_None); - Py_DECREF(py_key); - } - - if (!py_rec) { - /* This means that build value, failed, so we are in trouble*/ - Py_XDECREF(temp_py_recs); - return as_error_update( - err, AEROSPIKE_ERR_CLIENT, - "Failed to allocate memory for record entry"); - } - - if (PyList_Append(temp_py_recs, py_rec) != 0) { - Py_DECREF(py_rec); - Py_DECREF(temp_py_recs); - return as_error_update(err, AEROSPIKE_ERR_CLIENT, - "Failed to add record to results"); - } - Py_DECREF(py_rec); - } - - // Release Python State - *py_records = temp_py_recs; - return AEROSPIKE_OK; -} - -as_status batch_read_records_to_pyobject(AerospikeClient *self, as_error *err, - as_batch_read_records *records, - PyObject **py_recs) -{ - *py_recs = PyList_New(0); - - if (!(*py_recs)) { - return as_error_update(err, AEROSPIKE_ERR_CLIENT, - "Failed to allocate return list of records"); - } - as_vector *list = &records->list; - for (uint32_t i = 0; i < list->size; i++) { - - as_batch_read_record *batch = as_vector_get(list, i); - PyObject *py_rec = NULL; - PyObject *py_key = NULL; - - /* There should be a record, so convert it to a tuple */ - if (batch->result == AEROSPIKE_OK) { - record_to_pyobject(self, err, &batch->record, &batch->key, &py_rec); - if (!py_rec || err->code != AEROSPIKE_OK) { - Py_CLEAR(*py_recs); - return err->code; - } - /* No record, convert to (key, None, None) */ - } - else { - key_to_pyobject(err, &batch->key, &py_key); - if (!py_key || err->code != AEROSPIKE_OK) { - Py_CLEAR(*py_recs); - return err->code; - } - py_rec = Py_BuildValue("OOO", py_key, Py_None, Py_None); - Py_DECREF(py_key); - if (!py_rec) { - as_error_update(err, AEROSPIKE_ERR_CLIENT, - "Failed to create a record tuple"); - Py_CLEAR(*py_recs); - return err->code; - } - } - - if (PyList_Append(*py_recs, py_rec) != 0) { - as_error_update(err, AEROSPIKE_ERR_CLIENT, - "Failed to add record tuple to return list"); - Py_XDECREF(py_rec); - Py_CLEAR(*py_recs); - return err->code; - } - Py_DECREF(py_rec); - } - return AEROSPIKE_OK; -} - /* This fetches a string from a Python String like. If it is a unicode in Python27, we need to convert it to a bytes like object first, and keep track of the intermediate object for later deletion. From e58d71a26953e46386cd1bbb821e6a47b91863bf Mon Sep 17 00:00:00 2001 From: Dominic Pelini Date: Tue, 18 Nov 2025 15:38:46 -0700 Subject: [PATCH 14/36] Update test_query.py --- test/new_tests/test_query.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/new_tests/test_query.py b/test/new_tests/test_query.py index 429634abb4..9e8eaa0970 100644 --- a/test/new_tests/test_query.py +++ b/test/new_tests/test_query.py @@ -1145,7 +1145,6 @@ def test_query_with_invalid_list_cdt_ctx_dict(self): """ Invoke query() with cdt_ctx containing incorrect arguments """ - query = self.as_connection.query("test", "demo") with pytest.raises(e.ParamError) as param_error: From 1b2c9cda5ba4a80f360695c583e2cb084702f572 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 13 Jan 2026 10:17:20 -0800 Subject: [PATCH 15/36] Add error handling --- src/main/client/sec_index.c | 33 +++++++++++++++++++++++---------- src/main/query/where.c | 26 ++++++++++++++------------ 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/src/main/client/sec_index.c b/src/main/client/sec_index.c index 72293da02f..e77956ffd0 100644 --- a/src/main/client/sec_index.c +++ b/src/main/client/sec_index.c @@ -191,6 +191,8 @@ PyObject *AerospikeClient_Index_Expr_Create(AerospikeClient *self, data_type, NULL, expr); } +#define CTX_PARSE_ERROR_MESSAGE "Unable to parse ctx" + /** ******************************************************************************************************* * Creates a cdt index for a bin in the Aerospike DB. @@ -219,11 +221,13 @@ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, PyObject *py_indextype = NULL; PyObject *py_datatype = NULL; PyObject *py_name = NULL; + PyObject *py_ctx = NULL; + // Used to pass ctx into get_cdt_ctx() helper PyObject *py_ctx_dict = NULL; as_cdt_ctx ctx; bool ctx_in_use = false; - bool new_dict_in_use = false; + PyObject *py_obj = NULL; as_index_datatype data_type; as_index_type index_type; @@ -249,10 +253,22 @@ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, goto CLEANUP; } + // TODO: this should be refactored by using a new helper function to parse a ctx list instead of get_cdt_ctx() + // which only parses a dictionary containing a ctx list if (PyList_Check(py_ctx)) { py_ctx_dict = PyDict_New(); - PyDict_SetItemString(py_ctx_dict, "ctx", py_ctx); - new_dict_in_use = true; + + if (!py_ctx_dict) { + as_error_update(&err, AEROSPIKE_ERR_CLIENT, + CTX_PARSE_ERROR_MESSAGE); + goto CLEANUP; + } + int retval = PyDict_SetItemString(py_ctx_dict, "ctx", py_ctx); + if (retval == -1) { + as_error_update(&err, AEROSPIKE_ERR_CLIENT, + CTX_PARSE_ERROR_MESSAGE); + goto CLEANUP2; + } } else { py_ctx_dict = py_ctx; @@ -263,13 +279,10 @@ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, if (get_cdt_ctx(self, &err, &ctx, py_ctx_dict, &ctx_in_use, &static_pool, SERIALIZER_PYTHON) != AEROSPIKE_OK) { - goto CLEANUP; - } - if (new_dict_in_use) { - Py_DECREF(py_ctx_dict); + goto CLEANUP2; } if (!ctx_in_use) { - goto CLEANUP; + goto CLEANUP2; } py_obj = createIndexWithDataAndCollectionType( @@ -278,10 +291,10 @@ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, as_cdt_ctx_destroy(&ctx); - return py_obj; +CLEANUP2: + Py_DECREF(py_ctx_dict); CLEANUP: - if (py_obj == NULL) { raise_exception_base(&err, Py_None, Py_None, Py_None, Py_None, py_name); return NULL; diff --git a/src/main/query/where.c b/src/main/query/where.c index 2afab43e03..87e28176ea 100644 --- a/src/main/query/where.c +++ b/src/main/query/where.c @@ -58,36 +58,37 @@ static int AerospikeQuery_Where_Add(AerospikeQuery *self, PyObject *py_ctx, { as_error err; as_error_init(&err); + as_cdt_ctx *pctx = NULL; bool ctx_in_use = false; - bool new_dict_in_use = false; + // Used to pass ctx into get_cdt_ctx() helper PyObject *py_ctx_dict = NULL; if (py_ctx) { // TODO: does static pool go out of scope? as_static_pool static_pool; memset(&static_pool, 0, sizeof(static_pool)); + pctx = cf_malloc(sizeof(as_cdt_ctx)); memset(pctx, 0, sizeof(as_cdt_ctx)); + if (PyList_Check(py_ctx)) { py_ctx_dict = PyDict_New(); - PyDict_SetItemString(py_ctx_dict, "ctx", py_ctx); - new_dict_in_use = true; + if (!py_ctx_dict) { + goto CLEANUP_CTX_ON_ERROR; + } + int retval = PyDict_SetItemString(py_ctx_dict, "ctx", py_ctx); + if (retval == -1) { + goto CLEANUP_CTX_ON_ERROR; + } } else { py_ctx_dict = py_ctx; } + if (get_cdt_ctx(self->client, &err, pctx, py_ctx_dict, &ctx_in_use, &static_pool, SERIALIZER_PYTHON) != AEROSPIKE_OK) { - if (new_dict_in_use) { - Py_DECREF(py_ctx_dict); - } - cf_free(pctx); - pctx = NULL; - return err.code; - } - if (new_dict_in_use) { - Py_DECREF(py_ctx_dict); + goto CLEANUP_CTX_ON_ERROR; } } @@ -312,6 +313,7 @@ static int AerospikeQuery_Where_Add(AerospikeQuery *self, PyObject *py_ctx, if (pctx) { cf_free(pctx); } + Py_XDECREF(py_ctx_dict); return 1; } From 271e7214d79b0c8042f90f280311a6d21396f627 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 13 Jan 2026 10:23:55 -0800 Subject: [PATCH 16/36] Just make breaking change where index_cdt_create's ctx parameter can longer take in a dictionary to match documentation... --- src/main/client/sec_index.c | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/main/client/sec_index.c b/src/main/client/sec_index.c index e77956ffd0..77c811dd8e 100644 --- a/src/main/client/sec_index.c +++ b/src/main/client/sec_index.c @@ -223,8 +223,6 @@ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, PyObject *py_name = NULL; PyObject *py_ctx = NULL; - // Used to pass ctx into get_cdt_ctx() helper - PyObject *py_ctx_dict = NULL; as_cdt_ctx ctx; bool ctx_in_use = false; @@ -255,23 +253,21 @@ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, // TODO: this should be refactored by using a new helper function to parse a ctx list instead of get_cdt_ctx() // which only parses a dictionary containing a ctx list - if (PyList_Check(py_ctx)) { - py_ctx_dict = PyDict_New(); + if (!PyList_Check(py_ctx)) { + as_error_update(&err, AEROSPIKE_ERR_PARAM, "ctx must be a list"); + goto CLEANUP; + } - if (!py_ctx_dict) { - as_error_update(&err, AEROSPIKE_ERR_CLIENT, - CTX_PARSE_ERROR_MESSAGE); - goto CLEANUP; - } - int retval = PyDict_SetItemString(py_ctx_dict, "ctx", py_ctx); - if (retval == -1) { - as_error_update(&err, AEROSPIKE_ERR_CLIENT, - CTX_PARSE_ERROR_MESSAGE); - goto CLEANUP2; - } + // Used to pass ctx into get_cdt_ctx() helper + PyObject *py_ctx_dict = PyDict_New(); + if (!py_ctx_dict) { + as_error_update(&err, AEROSPIKE_ERR_CLIENT, CTX_PARSE_ERROR_MESSAGE); + goto CLEANUP; } - else { - py_ctx_dict = py_ctx; + int retval = PyDict_SetItemString(py_ctx_dict, "ctx", py_ctx); + if (retval == -1) { + as_error_update(&err, AEROSPIKE_ERR_CLIENT, CTX_PARSE_ERROR_MESSAGE); + goto CLEANUP2; } as_static_pool static_pool; @@ -281,6 +277,7 @@ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, SERIALIZER_PYTHON) != AEROSPIKE_OK) { goto CLEANUP2; } + if (!ctx_in_use) { goto CLEANUP2; } From 45110815b1de63b5db1190622b8c10614ec122d7 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 13 Jan 2026 10:28:05 -0800 Subject: [PATCH 17/36] get_cdt_ctx() already checks if python ctx is a list or not after retrieving it from a dictionary entry --- src/main/client/sec_index.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/client/sec_index.c b/src/main/client/sec_index.c index 77c811dd8e..edba525a86 100644 --- a/src/main/client/sec_index.c +++ b/src/main/client/sec_index.c @@ -253,12 +253,6 @@ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, // TODO: this should be refactored by using a new helper function to parse a ctx list instead of get_cdt_ctx() // which only parses a dictionary containing a ctx list - if (!PyList_Check(py_ctx)) { - as_error_update(&err, AEROSPIKE_ERR_PARAM, "ctx must be a list"); - goto CLEANUP; - } - - // Used to pass ctx into get_cdt_ctx() helper PyObject *py_ctx_dict = PyDict_New(); if (!py_ctx_dict) { as_error_update(&err, AEROSPIKE_ERR_CLIENT, CTX_PARSE_ERROR_MESSAGE); From e9ee8dd7ca21f1ca6c1bdd9f9d4e09515aeffc91 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 13 Jan 2026 10:38:03 -0800 Subject: [PATCH 18/36] Cleanup and add error handling --- src/main/query/where.c | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/main/query/where.c b/src/main/query/where.c index 87e28176ea..40ebb18562 100644 --- a/src/main/query/where.c +++ b/src/main/query/where.c @@ -42,6 +42,8 @@ int64_t pyobject_to_int64(PyObject *py_obj) } } +#define CTX_PARSE_ERROR_MESSAGE "Unable to parse ctx" + // py_bin, py_val1, pyval2 are guaranteed to be non-NULL // The rest of the PyObject parameters can be NULL and are optional. // 3 cases for these optional parameters: @@ -59,33 +61,37 @@ static int AerospikeQuery_Where_Add(AerospikeQuery *self, PyObject *py_ctx, as_error err; as_error_init(&err); + // TODO: does static pool go out of scope? + as_static_pool static_pool; + memset(&static_pool, 0, sizeof(static_pool)); + as_cdt_ctx *pctx = NULL; bool ctx_in_use = false; // Used to pass ctx into get_cdt_ctx() helper + // Declared here to make cleanup logic simpler PyObject *py_ctx_dict = NULL; - if (py_ctx) { - // TODO: does static pool go out of scope? - as_static_pool static_pool; - memset(&static_pool, 0, sizeof(static_pool)); + // Ctx is an optional parameter + if (py_ctx && !Py_IsNone(py_ctx)) { + // If user wanted to pass in an actual ctx - pctx = cf_malloc(sizeof(as_cdt_ctx)); - memset(pctx, 0, sizeof(as_cdt_ctx)); - - if (PyList_Check(py_ctx)) { - py_ctx_dict = PyDict_New(); - if (!py_ctx_dict) { - goto CLEANUP_CTX_ON_ERROR; - } - int retval = PyDict_SetItemString(py_ctx_dict, "ctx", py_ctx); - if (retval == -1) { - goto CLEANUP_CTX_ON_ERROR; - } + // Glue code to pass into get_cdt_ctx() + PyObject *py_ctx_dict = PyDict_New(); + if (!py_ctx_dict) { + as_error_update(&err, AEROSPIKE_ERR_CLIENT, + CTX_PARSE_ERROR_MESSAGE); + goto CLEANUP_CTX_ON_ERROR; } - else { - py_ctx_dict = py_ctx; + int retval = PyDict_SetItemString(py_ctx_dict, "ctx", py_ctx); + if (retval == -1) { + as_error_update(&err, AEROSPIKE_ERR_CLIENT, + CTX_PARSE_ERROR_MESSAGE); + goto CLEANUP_CTX_ON_ERROR; } + pctx = cf_malloc(sizeof(as_cdt_ctx)); + memset(pctx, 0, sizeof(as_cdt_ctx)); + if (get_cdt_ctx(self->client, &err, pctx, py_ctx_dict, &ctx_in_use, &static_pool, SERIALIZER_PYTHON) != AEROSPIKE_OK) { goto CLEANUP_CTX_ON_ERROR; From adc81caac854f36d7d987d6b0672652b20ef4c23 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 13 Jan 2026 10:51:24 -0800 Subject: [PATCH 19/36] We are no longer allowing the old behavior of passing a dict to ctx parameter --- test/new_tests/test_cdt_index.py | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/test/new_tests/test_cdt_index.py b/test/new_tests/test_cdt_index.py index 5c6fe70460..49c7916e7c 100644 --- a/test/new_tests/test_cdt_index.py +++ b/test/new_tests/test_cdt_index.py @@ -112,27 +112,6 @@ def test_pos_cdtindex_with_correct_parameters(self): assert retobj == 0 - def test_pos_cdtindex_dict_with_correct_parameters(self): - """ - Invoke index_cdt_create() with correct arguments - """ - policy = {} - retobj = self.as_connection.index_cdt_create( - "test", - "demo", - "string_list", - aerospike.INDEX_TYPE_LIST, - aerospike.INDEX_STRING, - "test_string_list_cdt_index", - {"ctx": ctx_list_index}, - policy, - ) - - self.as_connection.index_remove("test", "test_string_list_cdt_index", policy) - ensure_dropped_index(self.as_connection, "test", "test_string_list_cdt_index") - - assert retobj == 0 - def test_pos_cdtindex_with_info_command(self): """ Invoke index_cdt_create() with info command From 98b055f8c54056de6e3330249bc8008d52c72661 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 13 Jan 2026 11:02:48 -0800 Subject: [PATCH 20/36] dont need to test exception messages since those aren't considered breaking changes to the api --- test/new_tests/test_query.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/new_tests/test_query.py b/test/new_tests/test_query.py index 12d164ae46..465621a2a6 100644 --- a/test/new_tests/test_query.py +++ b/test/new_tests/test_query.py @@ -1147,10 +1147,6 @@ def test_query_with_invalid_list_cdt_ctx_dict(self): with pytest.raises(e.ParamError) as param_error: query.where(p.range("numeric_map", aerospike.INDEX_TYPE_DEFAULT, 2, 4), ['not a ctx list']) - assert param_error.value.msg == "Failed to add predicate" - assert param_error.value.code == -2 - - def test_query_with_base64_cdt_ctx(self): bs_b4_cdt = self.as_connection.get_cdtctx_base64(ctx_list_index) assert bs_b4_cdt == "khAA" From 7e639223531839c3cc43608411c5c2a88d50f6e9 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 13 Jan 2026 11:05:59 -0800 Subject: [PATCH 21/36] Remove since we no longer allow query.where() to take in a dictionary as ctx argument --- test/new_tests/test_query.py | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/test/new_tests/test_query.py b/test/new_tests/test_query.py index 465621a2a6..a78ad961e2 100644 --- a/test/new_tests/test_query.py +++ b/test/new_tests/test_query.py @@ -1111,33 +1111,6 @@ def callback(input_tuple): assert records assert len(records) == 3 - def test_query_with_map_cdt_ctx_dict(self): - """ - Invoke query() with cdt_ctx and correct arguments - """ - from .test_base_class import TestBaseClass - - if TestBaseClass.major_ver < 6 or (TestBaseClass.major_ver == 6 and TestBaseClass.minor_ver == 0): - pytest.skip("It only applies to >= 6.1 enterprise edition") - - query = self.as_connection.query("test", "demo") - query.select("numeric_map") - - query.where(p.range("numeric_map", aerospike.INDEX_TYPE_DEFAULT, 2, 4), {"ctx": ctx_map_index}) - - records = [] - - def callback(input_tuple): - try: - records.append(input_tuple) - except Exception as ex: - print(ex) - - query.foreach(callback) - - assert records - assert len(records) == 3 - def test_query_with_invalid_list_cdt_ctx_dict(self): """ Invoke query() with cdt_ctx containing incorrect arguments From cb019b6bd2c6bf449ae87ed3064dbe8c2ec605f1 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 13 Jan 2026 11:07:42 -0800 Subject: [PATCH 22/36] rm unused var from context manager --- test/new_tests/test_query.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/new_tests/test_query.py b/test/new_tests/test_query.py index a78ad961e2..fe5de2039a 100644 --- a/test/new_tests/test_query.py +++ b/test/new_tests/test_query.py @@ -1117,7 +1117,7 @@ def test_query_with_invalid_list_cdt_ctx_dict(self): """ query = self.as_connection.query("test", "demo") - with pytest.raises(e.ParamError) as param_error: + with pytest.raises(e.ParamError): query.where(p.range("numeric_map", aerospike.INDEX_TYPE_DEFAULT, 2, 4), ['not a ctx list']) def test_query_with_base64_cdt_ctx(self): From a235322a4eb0877a41d7775c4230f82f2f752a3f Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 13 Jan 2026 11:59:48 -0800 Subject: [PATCH 23/36] Fix test regressions. If createIndexWithDataAndCollectionType fails, then index_cdt_create should not raise an exception with its own as_error because it may be initialized to AEROSPIKE_OK --- src/main/client/sec_index.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/main/client/sec_index.c b/src/main/client/sec_index.c index edba525a86..f6646082cf 100644 --- a/src/main/client/sec_index.c +++ b/src/main/client/sec_index.c @@ -225,6 +225,7 @@ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, PyObject *py_ctx = NULL; as_cdt_ctx ctx; bool ctx_in_use = false; + PyObject *py_ctx_dict = NULL; PyObject *py_obj = NULL; as_index_datatype data_type; @@ -253,15 +254,15 @@ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, // TODO: this should be refactored by using a new helper function to parse a ctx list instead of get_cdt_ctx() // which only parses a dictionary containing a ctx list - PyObject *py_ctx_dict = PyDict_New(); if (!py_ctx_dict) { as_error_update(&err, AEROSPIKE_ERR_CLIENT, CTX_PARSE_ERROR_MESSAGE); goto CLEANUP; } int retval = PyDict_SetItemString(py_ctx_dict, "ctx", py_ctx); if (retval == -1) { + Py_DECREF(py_ctx_dict); as_error_update(&err, AEROSPIKE_ERR_CLIENT, CTX_PARSE_ERROR_MESSAGE); - goto CLEANUP2; + goto CLEANUP; } as_static_pool static_pool; @@ -269,11 +270,11 @@ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, if (get_cdt_ctx(self, &err, &ctx, py_ctx_dict, &ctx_in_use, &static_pool, SERIALIZER_PYTHON) != AEROSPIKE_OK) { - goto CLEANUP2; + goto CLEANUP; } if (!ctx_in_use) { - goto CLEANUP2; + goto CLEANUP; } py_obj = createIndexWithDataAndCollectionType( @@ -281,11 +282,17 @@ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, &ctx, NULL); as_cdt_ctx_destroy(&ctx); - -CLEANUP2: Py_DECREF(py_ctx_dict); + return py_obj; + CLEANUP: + // This codepath only runs if the code in this helper function failed + // but *not* in createIndexWithDataAndCollectionType, which does not take in an as_error object. + // We want createIndexWithDataAndCollectionType to be responsible for setting its own as_error object + // and raising an exception instead of here. + Py_XDECREF(py_ctx_dict); + if (py_obj == NULL) { raise_exception_base(&err, Py_None, Py_None, Py_None, Py_None, py_name); return NULL; From e6719db0905325eeaba16377e930001c2c0ac47f Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 13 Jan 2026 12:00:58 -0800 Subject: [PATCH 24/36] Not a helper --- src/main/client/sec_index.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/client/sec_index.c b/src/main/client/sec_index.c index f6646082cf..a8371212d3 100644 --- a/src/main/client/sec_index.c +++ b/src/main/client/sec_index.c @@ -287,7 +287,7 @@ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, return py_obj; CLEANUP: - // This codepath only runs if the code in this helper function failed + // This codepath only runs if the code in this function failed // but *not* in createIndexWithDataAndCollectionType, which does not take in an as_error object. // We want createIndexWithDataAndCollectionType to be responsible for setting its own as_error object // and raising an exception instead of here. From c53c5b2a48be56694a4686808a8c1f2d70482e21 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 13 Jan 2026 12:03:10 -0800 Subject: [PATCH 25/36] fix dup var declaration --- src/main/query/where.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/query/where.c b/src/main/query/where.c index 40ebb18562..4a90c00822 100644 --- a/src/main/query/where.c +++ b/src/main/query/where.c @@ -76,7 +76,7 @@ static int AerospikeQuery_Where_Add(AerospikeQuery *self, PyObject *py_ctx, // If user wanted to pass in an actual ctx // Glue code to pass into get_cdt_ctx() - PyObject *py_ctx_dict = PyDict_New(); + py_ctx_dict = PyDict_New(); if (!py_ctx_dict) { as_error_update(&err, AEROSPIKE_ERR_CLIENT, CTX_PARSE_ERROR_MESSAGE); From 54404250364ddbfd0d70c6a0fe5a538fbb7731da Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 13 Jan 2026 12:38:16 -0800 Subject: [PATCH 26/36] fix regression --- src/main/client/sec_index.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/client/sec_index.c b/src/main/client/sec_index.c index a8371212d3..74cc4a4942 100644 --- a/src/main/client/sec_index.c +++ b/src/main/client/sec_index.c @@ -254,6 +254,7 @@ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, // TODO: this should be refactored by using a new helper function to parse a ctx list instead of get_cdt_ctx() // which only parses a dictionary containing a ctx list + py_ctx_dict = PyDict_New(); if (!py_ctx_dict) { as_error_update(&err, AEROSPIKE_ERR_CLIENT, CTX_PARSE_ERROR_MESSAGE); goto CLEANUP; From c80f5236c2a309d65425941eaecbb07bb06f03e7 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 13 Jan 2026 15:20:13 -0800 Subject: [PATCH 27/36] Just combine cleanup steps into one codepath to improve code coverage and reduce repetition --- src/main/client/sec_index.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/main/client/sec_index.c b/src/main/client/sec_index.c index 74cc4a4942..01632c3c59 100644 --- a/src/main/client/sec_index.c +++ b/src/main/client/sec_index.c @@ -282,19 +282,11 @@ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, self, py_policy, py_ns, py_set, py_bin, py_name, index_type, data_type, &ctx, NULL); - as_cdt_ctx_destroy(&ctx); - Py_DECREF(py_ctx_dict); - - return py_obj; - CLEANUP: - // This codepath only runs if the code in this function failed - // but *not* in createIndexWithDataAndCollectionType, which does not take in an as_error object. - // We want createIndexWithDataAndCollectionType to be responsible for setting its own as_error object - // and raising an exception instead of here. + as_cdt_ctx_destroy(&ctx); Py_XDECREF(py_ctx_dict); - if (py_obj == NULL) { + if (err.code != AEROSPIKE_OK) { raise_exception_base(&err, Py_None, Py_None, Py_None, Py_None, py_name); return NULL; } From 1bc07cd1668137ef8cc7f67986306f52f93003e8 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 13 Jan 2026 15:23:07 -0800 Subject: [PATCH 28/36] Error message not set here. Was test coverage missing before? --- src/main/client/sec_index.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/client/sec_index.c b/src/main/client/sec_index.c index 01632c3c59..0e0270da16 100644 --- a/src/main/client/sec_index.c +++ b/src/main/client/sec_index.c @@ -275,6 +275,8 @@ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, } if (!ctx_in_use) { + as_error_update(&err, AEROSPIKE_ERR_PARAM, + "Ctx must be a valid list of cdt_ctx"); goto CLEANUP; } From 286c943de22bdcc7c1dd05fa3659cb31b7bc21e8 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 13 Jan 2026 15:33:58 -0800 Subject: [PATCH 29/36] Fix potentially freeing uninitialized cdt_ctx --- src/main/client/sec_index.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/client/sec_index.c b/src/main/client/sec_index.c index 0e0270da16..7dd6713cf1 100644 --- a/src/main/client/sec_index.c +++ b/src/main/client/sec_index.c @@ -275,17 +275,20 @@ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, } if (!ctx_in_use) { - as_error_update(&err, AEROSPIKE_ERR_PARAM, - "Ctx must be a valid list of cdt_ctx"); + as_error_update( + &err, AEROSPIKE_ERR_PARAM, + "Ctx must be a valid list of cdt_ctx. It is not optional"); goto CLEANUP; } + // Even if this call fails, the err object here will not be set, so we don't raise an exception twice py_obj = createIndexWithDataAndCollectionType( self, py_policy, py_ns, py_set, py_bin, py_name, index_type, data_type, &ctx, NULL); -CLEANUP: as_cdt_ctx_destroy(&ctx); + +CLEANUP: Py_XDECREF(py_ctx_dict); if (err.code != AEROSPIKE_OK) { From d55f7766b17af9afbabf1ec1c12d577589c40ea7 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 14 Jan 2026 11:11:18 -0800 Subject: [PATCH 30/36] Add test case to cover invalid ctx --- test/new_tests/test_cdt_index.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/new_tests/test_cdt_index.py b/test/new_tests/test_cdt_index.py index 49c7916e7c..bb252a758e 100644 --- a/test/new_tests/test_cdt_index.py +++ b/test/new_tests/test_cdt_index.py @@ -726,3 +726,16 @@ def test_neg_cdtindex_with_no_paramters(self): self.as_connection.index_cdt_create() assert "argument 'ns' (pos 1)" in str(typeError.value) + + def test_neg_cdtindex_with_invalid_ctx(self): + with pytest.raises(TypeError): + self.as_connection.index_cdt_create( + "test", + "demo", + "string_list", + aerospike.INDEX_TYPE_LIST, + aerospike.INDEX_STRING, + "test_string_list_cdt_index", + # Ctx must be a list + {"ctx": 1}, + ) From e41ce661fe20478c940928f9b619f955463e3ed6 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 14 Jan 2026 11:13:24 -0800 Subject: [PATCH 31/36] improve comment --- src/main/client/sec_index.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/client/sec_index.c b/src/main/client/sec_index.c index 7dd6713cf1..df300baf58 100644 --- a/src/main/client/sec_index.c +++ b/src/main/client/sec_index.c @@ -281,7 +281,8 @@ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, goto CLEANUP; } - // Even if this call fails, the err object here will not be set, so we don't raise an exception twice + // Even if this call fails, it will raise its own exception + // and the err object here will not be set. We don't raise an exception twice py_obj = createIndexWithDataAndCollectionType( self, py_policy, py_ns, py_set, py_bin, py_name, index_type, data_type, &ctx, NULL); From f41dc113db5814953ef195bd79b4ad34bc439ea5 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 14 Jan 2026 11:19:19 -0800 Subject: [PATCH 32/36] This should slightly improve performance when invalid input types are passed --- src/main/query/where.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/query/where.c b/src/main/query/where.c index 4a90c00822..f9eb0e37d5 100644 --- a/src/main/query/where.c +++ b/src/main/query/where.c @@ -80,13 +80,13 @@ static int AerospikeQuery_Where_Add(AerospikeQuery *self, PyObject *py_ctx, if (!py_ctx_dict) { as_error_update(&err, AEROSPIKE_ERR_CLIENT, CTX_PARSE_ERROR_MESSAGE); - goto CLEANUP_CTX_ON_ERROR; + goto error; } int retval = PyDict_SetItemString(py_ctx_dict, "ctx", py_ctx); if (retval == -1) { as_error_update(&err, AEROSPIKE_ERR_CLIENT, CTX_PARSE_ERROR_MESSAGE); - goto CLEANUP_CTX_ON_ERROR; + goto CLEANUP_PY_CTX_DICT_ON_ERROR; } pctx = cf_malloc(sizeof(as_cdt_ctx)); @@ -94,7 +94,7 @@ static int AerospikeQuery_Where_Add(AerospikeQuery *self, PyObject *py_ctx, if (get_cdt_ctx(self->client, &err, pctx, py_ctx_dict, &ctx_in_use, &static_pool, SERIALIZER_PYTHON) != AEROSPIKE_OK) { - goto CLEANUP_CTX_ON_ERROR; + goto CLEANUP_AS_CTX_ON_ERROR; } } @@ -103,7 +103,7 @@ static int AerospikeQuery_Where_Add(AerospikeQuery *self, PyObject *py_ctx, as_status status = as_exp_new_from_pyobject(self->client, py_expr, &exp_list, &err, true); if (status != AEROSPIKE_OK) { - goto CLEANUP_CTX_ON_ERROR; + goto CLEANUP_AS_CTX_ON_ERROR; } } @@ -311,7 +311,7 @@ static int AerospikeQuery_Where_Add(AerospikeQuery *self, PyObject *py_ctx, as_exp_destroy(exp_list); } -CLEANUP_CTX_ON_ERROR: +CLEANUP_AS_CTX_ON_ERROR: // The ctx ends up not being used by as_query if (ctx_in_use) { as_cdt_ctx_destroy(pctx); @@ -319,8 +319,11 @@ static int AerospikeQuery_Where_Add(AerospikeQuery *self, PyObject *py_ctx, if (pctx) { cf_free(pctx); } + +CLEANUP_PY_CTX_DICT_ON_ERROR: Py_XDECREF(py_ctx_dict); +error: return 1; } From 6fc83a5cbe3411cd4b9f58c54d734d8e776777b4 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 14 Jan 2026 11:23:16 -0800 Subject: [PATCH 33/36] Forgot to add test case where None passed as ctx arg --- test/new_tests/test_cdt_index.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test/new_tests/test_cdt_index.py b/test/new_tests/test_cdt_index.py index bb252a758e..99123d0625 100644 --- a/test/new_tests/test_cdt_index.py +++ b/test/new_tests/test_cdt_index.py @@ -727,7 +727,15 @@ def test_neg_cdtindex_with_no_paramters(self): assert "argument 'ns' (pos 1)" in str(typeError.value) - def test_neg_cdtindex_with_invalid_ctx(self): + @pytest.mark.parametrize( + "ctx", + [ + None, + # Invalid type + {"ctx": 1} + ] + ) + def test_neg_cdtindex_with_invalid_ctx(self, ctx): with pytest.raises(TypeError): self.as_connection.index_cdt_create( "test", @@ -737,5 +745,5 @@ def test_neg_cdtindex_with_invalid_ctx(self): aerospike.INDEX_STRING, "test_string_list_cdt_index", # Ctx must be a list - {"ctx": 1}, + ctx ) From 1dc3c60ffc17a534ba716ca08e85c712806ddbe4 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 14 Jan 2026 11:39:55 -0800 Subject: [PATCH 34/36] fix test... --- test/new_tests/test_cdt_index.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/new_tests/test_cdt_index.py b/test/new_tests/test_cdt_index.py index 99123d0625..b894b3d647 100644 --- a/test/new_tests/test_cdt_index.py +++ b/test/new_tests/test_cdt_index.py @@ -736,7 +736,7 @@ def test_neg_cdtindex_with_no_paramters(self): ] ) def test_neg_cdtindex_with_invalid_ctx(self, ctx): - with pytest.raises(TypeError): + with pytest.raises(e.ParamError): self.as_connection.index_cdt_create( "test", "demo", From ed7ba8a3b8c3f91340af014b2114374df2b68f88 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 14 Jan 2026 18:24:28 -0800 Subject: [PATCH 35/36] Add test case where we explicitly pass in None to query.where --- test/new_tests/test_query.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/new_tests/test_query.py b/test/new_tests/test_query.py index fe5de2039a..c65cbada91 100644 --- a/test/new_tests/test_query.py +++ b/test/new_tests/test_query.py @@ -312,7 +312,8 @@ def test_query_with_correct_parameters_hi(self): """ query = self.as_connection.query("test", "demo") query.select("name", "test_age") - query.where(p.equals("test_age", 1)) + # Here we explicitly test that ctx accepts None + query.where(p.equals("test_age", 1), None) records = [] From ffdbd22e0e116755769438febe0f5f297f692be1 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 14 Jan 2026 18:35:04 -0800 Subject: [PATCH 36/36] clean up dead code. --- src/main/client/sec_index.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/main/client/sec_index.c b/src/main/client/sec_index.c index df300baf58..1b22db2257 100644 --- a/src/main/client/sec_index.c +++ b/src/main/client/sec_index.c @@ -274,13 +274,6 @@ PyObject *AerospikeClient_Index_Cdt_Create(AerospikeClient *self, goto CLEANUP; } - if (!ctx_in_use) { - as_error_update( - &err, AEROSPIKE_ERR_PARAM, - "Ctx must be a valid list of cdt_ctx. It is not optional"); - goto CLEANUP; - } - // Even if this call fails, it will raise its own exception // and the err object here will not be set. We don't raise an exception twice py_obj = createIndexWithDataAndCollectionType(