From 8022b74deee7c1c75f082d9a4af6a832090f8f42 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 6 Oct 2025 11:54:53 -0700 Subject: [PATCH 001/131] WIP API --- .gitmodules | 2 +- aerospike-client-c | 2 +- aerospike_helpers/cdt_ctx.py | 25 ++++++++++++++++++++++ aerospike_helpers/expressions/base.py | 14 ++++++++++++ aerospike_helpers/expressions/resources.py | 2 ++ src/main/aerospike.c | 7 ++++++ src/main/convert_expressions.c | 3 +++ test/new_tests/conftest.py | 5 ++--- 8 files changed, 55 insertions(+), 5 deletions(-) diff --git a/.gitmodules b/.gitmodules index 136ba68cbe..aa9d1f81a3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,4 +2,4 @@ path = aerospike-client-c # url = git@github.com:aerospike/aerospike-client-c.git url = https://github.com/aerospike/aerospike-client-c.git - branch = stage + branch = client-3753-path-expressions-integration diff --git a/aerospike-client-c b/aerospike-client-c index ab09106dee..e3b4322b4b 160000 --- a/aerospike-client-c +++ b/aerospike-client-c @@ -1 +1 @@ -Subproject commit ab09106deede83b5f163c6db208fdeb787b45a8a +Subproject commit e3b4322b4b72ca3891d4fd3566ee59bf1ccaae8c diff --git a/aerospike_helpers/cdt_ctx.py b/aerospike_helpers/cdt_ctx.py index 6aa51230b1..a2de271a65 100644 --- a/aerospike_helpers/cdt_ctx.py +++ b/aerospike_helpers/cdt_ctx.py @@ -102,6 +102,7 @@ client.close() """ import aerospike +from aerospike_helpers.expressions.resources import TypeExpression def index_type_string(index_type): @@ -300,3 +301,27 @@ def cdt_ctx_map_key_create(key: any, order: int = 0) -> _cdt_ctx: :class:`~aerospike_helpers.cdt_ctx._cdt_ctx` """ return _cdt_ctx(id=aerospike.CDT_CTX_MAP_KEY_CREATE, value=key, extra_args={CDT_CTX_ORDER_KEY: order}) + +def cdt_ctx_all() -> _cdt_ctx: + # TODO: vague docstring + """ + The cdt_ctx object selects all. + + Returns: + :class:`~aerospike_helpers.cdt_ctx._cdt_ctx` + """ + # TODO missing id in c client + return _cdt_ctx() + +def cdt_ctx_exp(expression: TypeExpression) -> _cdt_ctx: + # TODO: expr needs to be compiled? + """ + The cdt_ctx applies an expression to select ctx. + + Args: + expression: compiled aerospike expression + + Returns: + :class:`~aerospike_helpers.cdt_ctx._cdt_ctx` + """ + return _cdt_ctx(id=aerospike.CDT_CTX_EXP) diff --git a/aerospike_helpers/expressions/base.py b/aerospike_helpers/expressions/base.py index b3dee86fb9..64da92f38d 100644 --- a/aerospike_helpers/expressions/base.py +++ b/aerospike_helpers/expressions/base.py @@ -1069,3 +1069,17 @@ def __init__(self, var_name: str): exp.LT(exp.Var("x"), 10))).compile() """ self._fixed = {_Keys.VALUE_KEY: var_name} + +class VarBuiltInMap(_BaseExpr): + """ + Retrieve expression value from a built-in variable. + """ + _op = _ExprOp._AS_EXP_CODE_VAR_BUILTIN + + def __init__(self, var_id: int): + """Args: + `var_id` (int): Variable id. + + :return: (value stored in variable) + """ + self._fixed = {_Keys.VALUE_KEY: var_id} diff --git a/aerospike_helpers/expressions/resources.py b/aerospike_helpers/expressions/resources.py index 1c83f674af..9636f23af1 100644 --- a/aerospike_helpers/expressions/resources.py +++ b/aerospike_helpers/expressions/resources.py @@ -81,6 +81,8 @@ class _ExprOp: # TODO replace this with an enum BIN_TYPE = 82 BIN_EXISTS = 83 + _AS_EXP_CODE_VAR_BUILTIN = 122 + COND = 123 VAR = 124 LET = 125 diff --git a/src/main/aerospike.c b/src/main/aerospike.c index e101d40492..2df3c16bd3 100644 --- a/src/main/aerospike.c +++ b/src/main/aerospike.c @@ -431,6 +431,13 @@ static struct module_constant_name_to_value module_constants[] = { {"CDT_CTX_MAP_KEY", .value.integer = AS_CDT_CTX_MAP_KEY}, {"CDT_CTX_MAP_VALUE", .value.integer = AS_CDT_CTX_MAP_VALUE}, {"CDT_CTX_MAP_KEY_CREATE", .value.integer = CDT_CTX_MAP_KEY_CREATE}, + {"CDT_CTX_EXP", .value.integer = AS_CDT_CTX_EXP}, + + /* Expression variable built-in type */ + // TODO: missing docs + {"EXP_BUILTIN_KEY", .value.integer = AS_EXP_BUILTIN_KEY}, + {"EXP_BUILTIN_VALUE", .value.integer = AS_EXP_BUILTIN_VALUE}, + {"EXP_BUILTIN_INDEX", .value.integer = AS_EXP_BUILTIN_INDEX}, /* HLL constants 3.11.0 */ {"OP_HLL_ADD", .value.integer = OP_HLL_ADD}, diff --git a/src/main/convert_expressions.c b/src/main/convert_expressions.c index 3252758e45..ef3b9a5b67 100644 --- a/src/main/convert_expressions.c +++ b/src/main/convert_expressions.c @@ -209,6 +209,8 @@ static as_status get_expr_size(int *size_to_alloc, int *intermediate_exprs_size, static const int EXPR_SIZES[] = { [BIN] = EXP_SZ(as_exp_bin_int(0)), [_AS_EXP_CODE_AS_VAL] = EXP_SZ(as_exp_val(NULL)), + // TODO: have generic var_builtin? + [_AS_EXP_CODE_VAR_BUILTIN] = EXP_SZ(as_exp_var_builtin_str(0)), [VAL] = EXP_SZ(as_exp_val( NULL)), // NOTE if I don't count vals I don't need to subtract from other ops // MUST count these for expressions with var args. [EQ] = EXP_SZ( @@ -639,6 +641,7 @@ add_expr_macros(AerospikeClient *self, as_static_pool *static_pool, APPEND_ARRAY(0, BIN_EXPR()); break; case VAL: + case _AS_EXP_CODE_VAR_BUILTIN: case _AS_EXP_CODE_AS_VAL:; as_exp_entry tmp_expr; if (get_exp_val_from_pyval( diff --git a/test/new_tests/conftest.py b/test/new_tests/conftest.py index f0a13b3a3e..c95fa5d58c 100644 --- a/test/new_tests/conftest.py +++ b/test/new_tests/conftest.py @@ -8,8 +8,7 @@ from . import invalid_data from .test_base_class import TestBaseClass -aerospike = pytest.importorskip("aerospike") - +import aerospike # Comment this out because nowhere in the repository is using it ''' @@ -72,7 +71,7 @@ def wait_for_port(address, port, interval=0.1, timeout=60): @pytest.fixture(scope="class") -def as_connection(request): +def as_connection(request) -> aerospike.Client: config = TestBaseClass.get_connection_config() lua_user_path = os.path.join(sys.exec_prefix, "aerospike", "usr-lua") lua_info = {"user_path": lua_user_path} From fa886c2cc60f8f37d1a0144381c1b50b82f24e50 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 13 Oct 2025 08:53:31 -0700 Subject: [PATCH 002/131] get latest c cl --- aerospike-client-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aerospike-client-c b/aerospike-client-c index e3b4322b4b..a24f8c47b3 160000 --- a/aerospike-client-c +++ b/aerospike-client-c @@ -1 +1 @@ -Subproject commit e3b4322b4b72ca3891d4fd3566ee59bf1ccaae8c +Subproject commit a24f8c47b365022164e53e380f6541ca2d601344 From c5c0c37e642d0f0eb2759b70b2a6b71c6d116268 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 13 Oct 2025 16:23:02 -0700 Subject: [PATCH 003/131] Add operate.cdt_select() test case --- aerospike_helpers/expressions/base.py | 1 + test/new_tests/test_operate_helpers.py | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/aerospike_helpers/expressions/base.py b/aerospike_helpers/expressions/base.py index 64da92f38d..12e214d951 100644 --- a/aerospike_helpers/expressions/base.py +++ b/aerospike_helpers/expressions/base.py @@ -1070,6 +1070,7 @@ def __init__(self, var_name: str): """ self._fixed = {_Keys.VALUE_KEY: var_name} + class VarBuiltInMap(_BaseExpr): """ Retrieve expression value from a built-in variable. diff --git a/test/new_tests/test_operate_helpers.py b/test/new_tests/test_operate_helpers.py index c33201dd0c..e3711062df 100644 --- a/test/new_tests/test_operate_helpers.py +++ b/test/new_tests/test_operate_helpers.py @@ -2,6 +2,7 @@ import pytest from .test_base_class import TestBaseClass from aerospike_helpers.operations import list_operations, operations +from aerospike_helpers import cdt_ctx import aerospike from aerospike import exception as e @@ -202,7 +203,20 @@ def teardown(): [operations.write("write_bin", False), operations.read("write_bin")], {"write_bin": 0}, ), - ], + ( + ("test", "demo", "existing_key"), + [ + operations.cdt_select( + "dict", + ctx=[ + # TODO: rename? + cdt_ctx.cdt_ctx_all(), + ] + ), + ], + {"dict": [1]} + ) + ] ) def test_pos_operate_with_correct_paramters(self, key, llist, expected): """ From 88ce202c8fe1e3b6d8c31652b42e02694de7b7b9 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 13 Oct 2025 16:39:57 -0700 Subject: [PATCH 004/131] WIP operations.cdt_select() implementation. flags not supported yet --- aerospike_helpers/operations/operations.py | 12 ++++++++++++ src/main/aerospike.c | 1 + src/main/client/operate.c | 11 +++++++++++ test/new_tests/test_operate_helpers.py | 2 +- 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/aerospike_helpers/operations/operations.py b/aerospike_helpers/operations/operations.py index c09419e15e..c8668f3d1a 100644 --- a/aerospike_helpers/operations/operations.py +++ b/aerospike_helpers/operations/operations.py @@ -131,3 +131,15 @@ def touch(ttl: Optional[int] = None): warnings.warn("TTL should be specified in the meta dictionary for operate", DeprecationWarning) op_dict["val"] = ttl return op_dict + + +# TODO: default flags. flags not implemented +def cdt_select(name: str, ctx: list, flags: int): + """ + Create CDT select operation. + + Returns: + A dictionary to be passed to operate or operate_ordered. + """ + op_dict = {"op": aerospike.OPERATOR_CDT_SELECT, "bin": name, "ctx": ctx, "flags": flags} + return op_dict diff --git a/src/main/aerospike.c b/src/main/aerospike.c index 2df3c16bd3..a628484303 100644 --- a/src/main/aerospike.c +++ b/src/main/aerospike.c @@ -113,6 +113,7 @@ static struct module_constant_name_to_value module_constants[] = { {"OPERATOR_PREPEND", .value.integer = AS_OPERATOR_PREPEND}, {"OPERATOR_TOUCH", .value.integer = AS_OPERATOR_TOUCH}, {"OPERATOR_DELETE", .value.integer = AS_OPERATOR_DELETE}, + {"OPERATOR_CDT_READ", .value.integer = AS_OPERATOR_CDT_READ}, {"AUTH_INTERNAL", .value.integer = AS_AUTH_INTERNAL}, {"AUTH_EXTERNAL", .value.integer = AS_AUTH_EXTERNAL}, diff --git a/src/main/client/operate.c b/src/main/client/operate.c index 2083a0f835..f2b78a6062 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -542,7 +542,13 @@ as_status add_op(AerospikeClient *self, as_error *err, PyObject *py_val, goto CLEANUP; } + bool operation_succeeded = true; switch (operation) { + case AS_OPERATOR_CDT_READ: + // TODO: flags + operation_succeeded = + as_operations_cdt_select(ops, bin, ctx_ref, AS_CDT_SELECT_TREE); + break; case AS_OPERATOR_APPEND: if (PyUnicode_Check(py_value)) { py_ustr1 = PyUnicode_AsUTF8String(py_value); @@ -806,6 +812,11 @@ as_status add_op(AerospikeClient *self, as_error *err, PyObject *py_val, } } + if (operation_succeeded == false) { + as_error_update(err, AEROSPIKE_ERR_CLIENT, + "Unable to add an operation"); + } + CLEANUP: if (ctx_in_use) { as_cdt_ctx_destroy(&ctx); diff --git a/test/new_tests/test_operate_helpers.py b/test/new_tests/test_operate_helpers.py index e3711062df..672a086c1d 100644 --- a/test/new_tests/test_operate_helpers.py +++ b/test/new_tests/test_operate_helpers.py @@ -207,7 +207,7 @@ def teardown(): ("test", "demo", "existing_key"), [ operations.cdt_select( - "dict", + name="dict", ctx=[ # TODO: rename? cdt_ctx.cdt_ctx_all(), From 3b2e9573fd74a6e73f8cb0af6f75e7e5ad8a8aab Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 13 Oct 2025 16:47:00 -0700 Subject: [PATCH 005/131] Workaround: surround type hint with quotes to prevent circular references --- aerospike_helpers/cdt_ctx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aerospike_helpers/cdt_ctx.py b/aerospike_helpers/cdt_ctx.py index a2de271a65..72deba9625 100644 --- a/aerospike_helpers/cdt_ctx.py +++ b/aerospike_helpers/cdt_ctx.py @@ -313,7 +313,7 @@ def cdt_ctx_all() -> _cdt_ctx: # TODO missing id in c client return _cdt_ctx() -def cdt_ctx_exp(expression: TypeExpression) -> _cdt_ctx: +def cdt_ctx_exp(expression: "TypeExpression") -> _cdt_ctx: # TODO: expr needs to be compiled? """ The cdt_ctx applies an expression to select ctx. From 721e4de65d05bc238bf7a6dee3e54c5d55601e8a Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 13 Oct 2025 16:47:44 -0700 Subject: [PATCH 006/131] rm culprit import --- aerospike_helpers/cdt_ctx.py | 1 - 1 file changed, 1 deletion(-) diff --git a/aerospike_helpers/cdt_ctx.py b/aerospike_helpers/cdt_ctx.py index 72deba9625..144e850607 100644 --- a/aerospike_helpers/cdt_ctx.py +++ b/aerospike_helpers/cdt_ctx.py @@ -102,7 +102,6 @@ client.close() """ import aerospike -from aerospike_helpers.expressions.resources import TypeExpression def index_type_string(index_type): From 2fa464703227c7a8c10a358ec5d42d88c9e3811c Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 13 Oct 2025 17:02:58 -0700 Subject: [PATCH 007/131] First rough draft implementation of operation cdt_apply(). negative path not done --- aerospike_helpers/operations/operations.py | 16 +++++++++++++++- src/main/aerospike.c | 2 ++ src/main/client/operate.c | 17 +++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/aerospike_helpers/operations/operations.py b/aerospike_helpers/operations/operations.py index c8668f3d1a..f191e91125 100644 --- a/aerospike_helpers/operations/operations.py +++ b/aerospike_helpers/operations/operations.py @@ -24,6 +24,8 @@ import aerospike from typing import Optional +from aerospike_helpers.expressions.resources import TypeExpression + def read(bin_name): """Create a read operation dictionary. @@ -134,6 +136,7 @@ def touch(ttl: Optional[int] = None): # TODO: default flags. flags not implemented +# TODO: use constant for return value def cdt_select(name: str, ctx: list, flags: int): """ Create CDT select operation. @@ -141,5 +144,16 @@ def cdt_select(name: str, ctx: list, flags: int): Returns: A dictionary to be passed to operate or operate_ordered. """ - op_dict = {"op": aerospike.OPERATOR_CDT_SELECT, "bin": name, "ctx": ctx, "flags": flags} + op_dict = {"op": aerospike.OPERATOR_CDT_READ, "bin": name, "ctx": ctx, "flags": flags} + return op_dict + + +def cdt_apply(name: str, ctx: list, expr: TypeExpression, flags: int): + """ + Create CDT apply operation. + + Returns: + A dictionary to be passed to operate or operate_ordered. + """ + op_dict = {"op": aerospike.OPERATOR_CDT_MODIFY, "bin": name, "ctx": ctx, "expr": expr, "flags": flags} return op_dict diff --git a/src/main/aerospike.c b/src/main/aerospike.c index a628484303..d86110f171 100644 --- a/src/main/aerospike.c +++ b/src/main/aerospike.c @@ -113,7 +113,9 @@ static struct module_constant_name_to_value module_constants[] = { {"OPERATOR_PREPEND", .value.integer = AS_OPERATOR_PREPEND}, {"OPERATOR_TOUCH", .value.integer = AS_OPERATOR_TOUCH}, {"OPERATOR_DELETE", .value.integer = AS_OPERATOR_DELETE}, + // TODO: should not be public, since app developers should use aerospike_helpers {"OPERATOR_CDT_READ", .value.integer = AS_OPERATOR_CDT_READ}, + {"OPERATOR_CDT_MODIFY", .value.integer = AS_OPERATOR_CDT_MODIFY}, {"AUTH_INTERNAL", .value.integer = AS_AUTH_INTERNAL}, {"AUTH_EXTERNAL", .value.integer = AS_AUTH_EXTERNAL}, diff --git a/src/main/client/operate.c b/src/main/client/operate.c index f2b78a6062..c5e47a56c2 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -327,6 +327,7 @@ as_status add_op(AerospikeClient *self, as_error *err, PyObject *py_val, PyObject *py_ustr = NULL; PyObject *py_ustr1 = NULL; PyObject *py_bin = NULL; + as_exp *mod_exp = NULL; as_map_policy map_policy; as_map_policy_init(&map_policy); @@ -414,6 +415,13 @@ as_status add_op(AerospikeClient *self, as_error *err, PyObject *py_val, CONVERT_PY_CTX_TO_AS_CTX(); ctx_ref = (ctx_in_use ? &ctx : NULL); } + else if (!strcmp(name, "expr")) { + as_status status = + as_exp_new_from_pyobject(self, value, &mod_exp, err, false); + if (status != AEROSPIKE_OK) { + goto CLEANUP; + } + } else if (strcmp(name, "map_order") == 0) { py_map_order = value; } @@ -549,6 +557,12 @@ as_status add_op(AerospikeClient *self, as_error *err, PyObject *py_val, operation_succeeded = as_operations_cdt_select(ops, bin, ctx_ref, AS_CDT_SELECT_TREE); break; + case AS_OPERATOR_CDT_MODIFY: + // TODO: flags + operation_succeeded = as_operations_cdt_apply( + ops, bin, ctx_ref, mod_exp, AS_CDT_SELECT_TREE); + break; + case AS_OPERATOR_APPEND: if (PyUnicode_Check(py_value)) { py_ustr1 = PyUnicode_AsUTF8String(py_value); @@ -818,6 +832,9 @@ as_status add_op(AerospikeClient *self, as_error *err, PyObject *py_val, } CLEANUP: + if (mod_exp) { + as_exp_destroy(mod_exp); + } if (ctx_in_use) { as_cdt_ctx_destroy(&ctx); } From 61dd90c6db758a3eeaf2fc637a64ba0c8bb373ac Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 13 Oct 2025 17:28:45 -0700 Subject: [PATCH 008/131] prefix with underscore to mark constants as private --- aerospike_helpers/operations/operations.py | 4 ++-- src/main/aerospike.c | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/aerospike_helpers/operations/operations.py b/aerospike_helpers/operations/operations.py index f191e91125..c440cddbf5 100644 --- a/aerospike_helpers/operations/operations.py +++ b/aerospike_helpers/operations/operations.py @@ -144,7 +144,7 @@ def cdt_select(name: str, ctx: list, flags: int): Returns: A dictionary to be passed to operate or operate_ordered. """ - op_dict = {"op": aerospike.OPERATOR_CDT_READ, "bin": name, "ctx": ctx, "flags": flags} + op_dict = {"op": aerospike._AS_OPERATOR_CDT_READ, "bin": name, "ctx": ctx, "flags": flags} return op_dict @@ -155,5 +155,5 @@ def cdt_apply(name: str, ctx: list, expr: TypeExpression, flags: int): Returns: A dictionary to be passed to operate or operate_ordered. """ - op_dict = {"op": aerospike.OPERATOR_CDT_MODIFY, "bin": name, "ctx": ctx, "expr": expr, "flags": flags} + op_dict = {"op": aerospike._AS_OPERATOR_CDT_MODIFY, "bin": name, "ctx": ctx, "expr": expr, "flags": flags} return op_dict diff --git a/src/main/aerospike.c b/src/main/aerospike.c index d86110f171..c00d7a3b62 100644 --- a/src/main/aerospike.c +++ b/src/main/aerospike.c @@ -113,9 +113,8 @@ static struct module_constant_name_to_value module_constants[] = { {"OPERATOR_PREPEND", .value.integer = AS_OPERATOR_PREPEND}, {"OPERATOR_TOUCH", .value.integer = AS_OPERATOR_TOUCH}, {"OPERATOR_DELETE", .value.integer = AS_OPERATOR_DELETE}, - // TODO: should not be public, since app developers should use aerospike_helpers - {"OPERATOR_CDT_READ", .value.integer = AS_OPERATOR_CDT_READ}, - {"OPERATOR_CDT_MODIFY", .value.integer = AS_OPERATOR_CDT_MODIFY}, + {"_OPERATOR_CDT_READ", .value.integer = AS_OPERATOR_CDT_READ}, + {"_OPERATOR_CDT_MODIFY", .value.integer = AS_OPERATOR_CDT_MODIFY}, {"AUTH_INTERNAL", .value.integer = AS_AUTH_INTERNAL}, {"AUTH_EXTERNAL", .value.integer = AS_AUTH_EXTERNAL}, From 95198041ca66d85bf273a9ad81fc47d47ae6af1b Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 13 Oct 2025 17:30:37 -0700 Subject: [PATCH 009/131] prefix pyobject var name with py_ to make easier to read --- src/main/client/operate.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/client/operate.c b/src/main/client/operate.c index c5e47a56c2..b803602d85 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -332,7 +332,7 @@ as_status add_op(AerospikeClient *self, as_error *err, PyObject *py_val, as_map_policy map_policy; as_map_policy_init(&map_policy); - PyObject *key_op = NULL, *value = NULL; + PyObject *key_op = NULL, *py_value = NULL; PyObject *py_value = NULL; PyObject *py_key = NULL; PyObject *py_index = NULL; @@ -377,7 +377,7 @@ as_status add_op(AerospikeClient *self, as_error *err, PyObject *py_val, operation, SERIALIZER_PYTHON); } - while (PyDict_Next(py_val, &pos, &key_op, &value)) { + while (PyDict_Next(py_val, &pos, &key_op, &py_value)) { if (!PyUnicode_Check(key_op)) { return as_error_update(err, AEROSPIKE_ERR_CLIENT, "An operation key must be a string."); @@ -388,25 +388,25 @@ as_status add_op(AerospikeClient *self, as_error *err, PyObject *py_val, continue; } else if (!strcmp(name, "bin")) { - py_bin = value; + py_bin = py_value; } else if (!strcmp(name, "index")) { - py_index = value; + py_index = py_value; } else if (!strcmp(name, "val")) { - py_value = value; + py_value = py_value; } else if (!strcmp(name, "key")) { - py_key = value; + py_key = py_value; } else if (!strcmp(name, "range")) { - py_range = value; + py_range = py_value; } else if (!strcmp(name, "map_policy")) { - py_map_policy = value; + py_map_policy = py_value; } else if (!strcmp(name, "return_type")) { - py_return_type = value; + py_return_type = py_value; } else if (strcmp(name, "inverted") == 0) { continue; @@ -416,17 +416,17 @@ as_status add_op(AerospikeClient *self, as_error *err, PyObject *py_val, ctx_ref = (ctx_in_use ? &ctx : NULL); } else if (!strcmp(name, "expr")) { - as_status status = - as_exp_new_from_pyobject(self, value, &mod_exp, err, false); + as_status status = as_exp_new_from_pyobject( + self, py_value, &mod_exp, err, false); if (status != AEROSPIKE_OK) { goto CLEANUP; } } else if (strcmp(name, "map_order") == 0) { - py_map_order = value; + py_map_order = py_value; } else if (strcmp(name, "persist_index") == 0) { - py_persist_index = value; + py_persist_index = py_value; } else { as_error_update( From 93fadc3ed620be9f8ad015211de3ca83db3748f0 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 13 Oct 2025 17:31:08 -0700 Subject: [PATCH 010/131] rm dup definition --- src/main/client/operate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/client/operate.c b/src/main/client/operate.c index b803602d85..65273823ee 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -332,7 +332,7 @@ as_status add_op(AerospikeClient *self, as_error *err, PyObject *py_val, as_map_policy map_policy; as_map_policy_init(&map_policy); - PyObject *key_op = NULL, *py_value = NULL; + PyObject *key_op = NULL; PyObject *py_value = NULL; PyObject *py_key = NULL; PyObject *py_index = NULL; From 137707d9fd8fb417c06af10a51b48a5b159bb517 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 13 Oct 2025 17:33:13 -0700 Subject: [PATCH 011/131] Revert "rm dup definition" This reverts commit 93fadc3ed620be9f8ad015211de3ca83db3748f0. --- src/main/client/operate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/client/operate.c b/src/main/client/operate.c index 65273823ee..b803602d85 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -332,7 +332,7 @@ as_status add_op(AerospikeClient *self, as_error *err, PyObject *py_val, as_map_policy map_policy; as_map_policy_init(&map_policy); - PyObject *key_op = NULL; + PyObject *key_op = NULL, *py_value = NULL; PyObject *py_value = NULL; PyObject *py_key = NULL; PyObject *py_index = NULL; From 0349bf46bf9d38bcd30c15cfda2dad5df7dd7af9 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 13 Oct 2025 17:33:31 -0700 Subject: [PATCH 012/131] Revert "prefix pyobject var name with py_ to make easier to read" This reverts commit 95198041ca66d85bf273a9ad81fc47d47ae6af1b. --- src/main/client/operate.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/client/operate.c b/src/main/client/operate.c index b803602d85..c5e47a56c2 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -332,7 +332,7 @@ as_status add_op(AerospikeClient *self, as_error *err, PyObject *py_val, as_map_policy map_policy; as_map_policy_init(&map_policy); - PyObject *key_op = NULL, *py_value = NULL; + PyObject *key_op = NULL, *value = NULL; PyObject *py_value = NULL; PyObject *py_key = NULL; PyObject *py_index = NULL; @@ -377,7 +377,7 @@ as_status add_op(AerospikeClient *self, as_error *err, PyObject *py_val, operation, SERIALIZER_PYTHON); } - while (PyDict_Next(py_val, &pos, &key_op, &py_value)) { + while (PyDict_Next(py_val, &pos, &key_op, &value)) { if (!PyUnicode_Check(key_op)) { return as_error_update(err, AEROSPIKE_ERR_CLIENT, "An operation key must be a string."); @@ -388,25 +388,25 @@ as_status add_op(AerospikeClient *self, as_error *err, PyObject *py_val, continue; } else if (!strcmp(name, "bin")) { - py_bin = py_value; + py_bin = value; } else if (!strcmp(name, "index")) { - py_index = py_value; + py_index = value; } else if (!strcmp(name, "val")) { - py_value = py_value; + py_value = value; } else if (!strcmp(name, "key")) { - py_key = py_value; + py_key = value; } else if (!strcmp(name, "range")) { - py_range = py_value; + py_range = value; } else if (!strcmp(name, "map_policy")) { - py_map_policy = py_value; + py_map_policy = value; } else if (!strcmp(name, "return_type")) { - py_return_type = py_value; + py_return_type = value; } else if (strcmp(name, "inverted") == 0) { continue; @@ -416,17 +416,17 @@ as_status add_op(AerospikeClient *self, as_error *err, PyObject *py_val, ctx_ref = (ctx_in_use ? &ctx : NULL); } else if (!strcmp(name, "expr")) { - as_status status = as_exp_new_from_pyobject( - self, py_value, &mod_exp, err, false); + as_status status = + as_exp_new_from_pyobject(self, value, &mod_exp, err, false); if (status != AEROSPIKE_OK) { goto CLEANUP; } } else if (strcmp(name, "map_order") == 0) { - py_map_order = py_value; + py_map_order = value; } else if (strcmp(name, "persist_index") == 0) { - py_persist_index = py_value; + py_persist_index = value; } else { as_error_update( From 482543f00120bed0a7c84d0915d3c90a0cf16dab Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 15 Oct 2025 10:59:08 -0700 Subject: [PATCH 013/131] implement as_cdt_select_flags --- aerospike_helpers/operations/operations.py | 4 +- src/main/aerospike.c | 11 ++- src/main/client/operate.c | 102 ++++++++++++++------- 3 files changed, 82 insertions(+), 35 deletions(-) diff --git a/aerospike_helpers/operations/operations.py b/aerospike_helpers/operations/operations.py index c440cddbf5..aa0834b730 100644 --- a/aerospike_helpers/operations/operations.py +++ b/aerospike_helpers/operations/operations.py @@ -135,9 +135,7 @@ def touch(ttl: Optional[int] = None): return op_dict -# TODO: default flags. flags not implemented -# TODO: use constant for return value -def cdt_select(name: str, ctx: list, flags: int): +def cdt_select(name: str, ctx: list, flags: int = aerospike.CDT_SELECT_TREE): """ Create CDT select operation. diff --git a/src/main/aerospike.c b/src/main/aerospike.c index 3cb69275ed..8b0d08a6bc 100644 --- a/src/main/aerospike.c +++ b/src/main/aerospike.c @@ -531,7 +531,16 @@ static struct module_constant_name_to_value module_constants[] = { {"TXN_STATE_ABORTED", .value.integer = AS_TXN_STATE_ABORTED}, {"JOB_SCAN", .is_str_value = true, .value.string = "scan"}, - {"JOB_QUERY", .is_str_value = true, .value.string = "query"}}; + {"JOB_QUERY", .is_str_value = true, .value.string = "query"}, + + {"CDT_SELECT_TREE", .value.integer = AS_CDT_SELECT_TREE}, + {"CDT_SELECT_LEAF_LIST_VALUE", + .value.integer = AS_CDT_SELECT_LEAF_LIST_VALUE}, + {"CDT_SELECT_LEAF_MAP_VALUE", + .value.integer = AS_CDT_SELECT_LEAF_MAP_VALUE}, + {"CDT_SELECT_LEAF_MAP_KEY", .value.integer = AS_CDT_SELECT_LEAF_MAP_KEY}, + {"CDT_SELECT_NO_FAIL", .value.integer = AS_CDT_SELECT_NO_FAIL}, +}; struct submodule_name_to_creation_method { const char *name; diff --git a/src/main/client/operate.c b/src/main/client/operate.c index 2c0b227b30..c7c90e0bb8 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -35,6 +35,7 @@ #include "cdt_map_operations.h" #include "bit_operations.h" #include "hll_operations.h" +#include "pythoncapi_compat.h" #include "expression_operations.h" #include @@ -306,9 +307,10 @@ bool opRequiresKey(int op) op == OP_MAP_GET_BY_KEY_RANGE); } -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 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_val *put_val = NULL; as_val *put_key = NULL; @@ -345,39 +347,42 @@ as_status add_op(AerospikeClient *self, as_error *err, PyObject *py_val, Py_ssize_t pos = 0; - if (get_operation(err, py_val, &operation) != AEROSPIKE_OK) { + if (get_operation(err, py_operation_dict, &operation) != AEROSPIKE_OK) { return err->code; } /* Handle the list operations with a helper in the cdt_list_operate.c file */ if (isListOp(operation)) { return add_new_list_op( - self, err, py_val, unicodeStrVector, static_pool, ops, operation, - ret_type, + self, err, py_operation_dict, unicodeStrVector, static_pool, ops, + operation, ret_type, SERIALIZER_PYTHON); //This hardcoding matches current behavior } if (isNewMapOp(operation)) { - return add_new_map_op(self, err, py_val, unicodeStrVector, static_pool, - ops, operation, ret_type, SERIALIZER_PYTHON); + return add_new_map_op(self, err, py_operation_dict, unicodeStrVector, + static_pool, ops, operation, ret_type, + SERIALIZER_PYTHON); } if (isBitOp(operation)) { - return add_new_bit_op(self, err, py_val, unicodeStrVector, static_pool, - ops, operation, ret_type, SERIALIZER_PYTHON); + return add_new_bit_op(self, err, py_operation_dict, unicodeStrVector, + static_pool, ops, operation, ret_type, + SERIALIZER_PYTHON); } if (isHllOp(operation)) { - return add_new_hll_op(self, err, py_val, unicodeStrVector, static_pool, - ops, operation, ret_type, SERIALIZER_PYTHON); + return add_new_hll_op(self, err, py_operation_dict, unicodeStrVector, + static_pool, ops, operation, ret_type, + SERIALIZER_PYTHON); } if (isExprOp(operation)) { - return add_new_expr_op(self, err, py_val, unicodeStrVector, ops, - operation, SERIALIZER_PYTHON); + return add_new_expr_op(self, err, py_operation_dict, unicodeStrVector, + ops, operation, SERIALIZER_PYTHON); } - while (PyDict_Next(py_val, &pos, &key_op, &value)) { + while (PyDict_Next(py_operation_dict, &pos, &key_op, &value)) { if (!PyUnicode_Check(key_op)) { return as_error_update(err, AEROSPIKE_ERR_CLIENT, "An operation key must be a string."); @@ -415,13 +420,6 @@ as_status add_op(AerospikeClient *self, as_error *err, PyObject *py_val, CONVERT_PY_CTX_TO_AS_CTX(); ctx_ref = (ctx_in_use ? &ctx : NULL); } - else if (!strcmp(name, "expr")) { - as_status status = - as_exp_new_from_pyobject(self, value, &mod_exp, err, false); - if (status != AEROSPIKE_OK) { - goto CLEANUP; - } - } else if (strcmp(name, "map_order") == 0) { py_map_order = value; } @@ -519,7 +517,8 @@ as_status add_op(AerospikeClient *self, as_error *err, PyObject *py_val, } /* Add the inverted flag to the return type if it's present */ - if (invertIfSpecified(err, py_val, &return_type) != AEROSPIKE_OK) { + if (invertIfSpecified(err, py_operation_dict, &return_type) != + AEROSPIKE_OK) { goto CLEANUP; } @@ -553,16 +552,57 @@ as_status add_op(AerospikeClient *self, as_error *err, PyObject *py_val, bool operation_succeeded = true; switch (operation) { case AS_OPERATOR_CDT_READ: - // TODO: flags - operation_succeeded = - as_operations_cdt_select(ops, bin, ctx_ref, AS_CDT_SELECT_TREE); - break; case AS_OPERATOR_CDT_MODIFY: - // TODO: flags - operation_succeeded = as_operations_cdt_apply( - ops, bin, ctx_ref, mod_exp, AS_CDT_SELECT_TREE); - break; + // TODO: set module constant for flags str + PyObject *py_flags = NULL; + // TODO: already a retval var previously? + int retval = + PyDict_GetItemStringRef(py_operation_dict, "flags", &py_flags); + if (retval == 0) { + as_error_update(err, AEROSPIKE_ERR_PARAM, + "CDT operation is missing a flags argument"); + goto CLEANUP; + } + else if (retval == -1) { + as_error_update(err, AEROSPIKE_ERR_CLIENT, "Internal error"); + goto CLEANUP; + } + + uint32_t flags = convert_pyobject_to_uint32_t(py_flags); + if (PyErr_Occurred()) { + as_error_update(err, AEROSPIKE_ERR_PARAM, + "CDT operation's flags argument is invalid"); + goto CLEANUP; + } + if (operation == AS_OPERATOR_CDT_READ) { + operation_succeeded = + as_operations_cdt_select(ops, bin, ctx_ref, flags); + } + else { + PyObject *py_expr = NULL; + int retval = + PyDict_GetItemStringRef(py_operation_dict, "expr", &py_expr); + if (retval == 0) { + as_error_update(err, AEROSPIKE_ERR_PARAM, + "CDT operation is missing a flags argument"); + goto CLEANUP; + } + else if (retval == -1) { + as_error_update(err, AEROSPIKE_ERR_CLIENT, "Internal error"); + goto CLEANUP; + } + as_status status = + as_exp_new_from_pyobject(self, value, &mod_exp, err, false); + if (status != AEROSPIKE_OK) { + goto CLEANUP; + } + + operation_succeeded = + as_operations_cdt_apply(ops, bin, ctx_ref, mod_exp, flags); + } + + break; case AS_OPERATOR_APPEND: if (PyUnicode_Check(py_value)) { py_ustr1 = PyUnicode_AsUTF8String(py_value); From 2ad1734c31e9fd465768474cdf73e16f391082a0 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 15 Oct 2025 11:01:12 -0700 Subject: [PATCH 014/131] fix macro --- src/main/client/operate.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/client/operate.c b/src/main/client/operate.c index c7c90e0bb8..fb32f8c963 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -107,8 +107,8 @@ static inline bool isExprOp(int op); } #define CONVERT_PY_CTX_TO_AS_CTX() \ - if (get_cdt_ctx(self, err, &ctx, py_val, &ctx_in_use, static_pool, \ - SERIALIZER_PYTHON) != AEROSPIKE_OK) { \ + if (get_cdt_ctx(self, err, &ctx, py_operation_dict, &ctx_in_use, \ + static_pool, SERIALIZER_PYTHON) != AEROSPIKE_OK) { \ return err->code; \ } From 94dbae04173785152c069a251d93bf275458def8 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 15 Oct 2025 11:05:01 -0700 Subject: [PATCH 015/131] fix --- src/main/aerospike.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/aerospike.c b/src/main/aerospike.c index 8b0d08a6bc..71816f7c63 100644 --- a/src/main/aerospike.c +++ b/src/main/aerospike.c @@ -113,8 +113,8 @@ static struct module_constant_name_to_value module_constants[] = { {"OPERATOR_PREPEND", .value.integer = AS_OPERATOR_PREPEND}, {"OPERATOR_TOUCH", .value.integer = AS_OPERATOR_TOUCH}, {"OPERATOR_DELETE", .value.integer = AS_OPERATOR_DELETE}, - {"_OPERATOR_CDT_READ", .value.integer = AS_OPERATOR_CDT_READ}, - {"_OPERATOR_CDT_MODIFY", .value.integer = AS_OPERATOR_CDT_MODIFY}, + {"_AS_OPERATOR_CDT_READ", .value.integer = AS_OPERATOR_CDT_READ}, + {"_AS_OPERATOR_CDT_MODIFY", .value.integer = AS_OPERATOR_CDT_MODIFY}, {"AUTH_INTERNAL", .value.integer = AS_AUTH_INTERNAL}, {"AUTH_EXTERNAL", .value.integer = AS_AUTH_EXTERNAL}, From ce911884e02687378a0f8954b6aa591f6d935766 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 15 Oct 2025 11:16:26 -0700 Subject: [PATCH 016/131] Add some of the missing stubs --- aerospike-stubs/aerospike.pyi | 7 +++++++ test/new_tests/test_operate_helpers.py | 1 - 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/aerospike-stubs/aerospike.pyi b/aerospike-stubs/aerospike.pyi index ec951bef08..c386b4f10a 100644 --- a/aerospike-stubs/aerospike.pyi +++ b/aerospike-stubs/aerospike.pyi @@ -309,6 +309,13 @@ TXN_STATE_VERIFIED: Literal[1] TXN_STATE_COMMITTED: Literal[2] TXN_STATE_ABORTED: Literal[3] +CDT_SELECT_TREE: Literal[0] +CDT_SELECT_LEAF_LIST_VALUE: Literal[1] +CDT_SELECT_LEAF_MAP_VALUE: Literal[1] +CDT_SELECT_LEAF_MAP_KEY: Literal[2] +CDT_SELECT_NO_FAIL: Literal[16] + + @final class CDTInfinite: def __init__(self, *args, **kwargs) -> None: ... diff --git a/test/new_tests/test_operate_helpers.py b/test/new_tests/test_operate_helpers.py index c9068b101b..3ec050dfb7 100644 --- a/test/new_tests/test_operate_helpers.py +++ b/test/new_tests/test_operate_helpers.py @@ -209,7 +209,6 @@ def teardown(): operations.cdt_select( name="dict", ctx=[ - # TODO: rename? cdt_ctx.cdt_ctx_all(), ] ), From eaee8951380e1d56b6551db7f345e5cabe2c4426 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 15 Oct 2025 11:26:05 -0700 Subject: [PATCH 017/131] Op id 3 is now valid. fix test --- src/main/client/operate.c | 1 + test/new_tests/test_operate.py | 4 ++-- test/new_tests/test_operate_ordered.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/client/operate.c b/src/main/client/operate.c index fb32f8c963..6b9340743f 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -549,6 +549,7 @@ as_status add_op(AerospikeClient *self, as_error *err, goto CLEANUP; } + // For backwards compatibility, we set this to true bool operation_succeeded = true; switch (operation) { case AS_OPERATOR_CDT_READ: diff --git a/test/new_tests/test_operate.py b/test/new_tests/test_operate.py index 02c04b6216..3c5a9550b8 100644 --- a/test/new_tests/test_operate.py +++ b/test/new_tests/test_operate.py @@ -705,7 +705,7 @@ def test_pos_operate_with_command_invalid_nostricttypes(self): llist = [ {"op": aerospike.OPERATOR_PREPEND, "bin": "name", "val": "ram"}, - {"op": 3, "bin": "age", "val": 3}, + {"op": 999, "bin": "age", "val": 3}, {"op": aerospike.OPERATOR_READ, "bin": "name"}, ] @@ -1097,7 +1097,7 @@ def test_neg_operate_with_command_invalid(self): llist = [ {"op": aerospike.OPERATOR_PREPEND, "bin": "name", "val": "ram"}, - {"op": 3, "bin": "age", "val": 3}, + {"op": 999, "bin": "age", "val": 3}, {"op": aerospike.OPERATOR_READ, "bin": "name"}, ] diff --git a/test/new_tests/test_operate_ordered.py b/test/new_tests/test_operate_ordered.py index 4812c937ec..67a10299b0 100644 --- a/test/new_tests/test_operate_ordered.py +++ b/test/new_tests/test_operate_ordered.py @@ -572,7 +572,7 @@ def test_pos_operate_ordered_with_command_invalid_nostricttypes(self): llist = [ {"op": aerospike.OPERATOR_PREPEND, "bin": "name", "val": "ram"}, - {"op": 3, "bin": "age", "val": 3}, + {"op": 999, "bin": "age", "val": 3}, {"op": aerospike.OPERATOR_READ, "bin": "name"}, ] @@ -698,7 +698,7 @@ def test_neg_operate_ordered_with_command_invalid(self): """ key = ("test", "demo", 1) - llist = [{"op": 3, "bin": "age", "val": 3}, {"op": aerospike.OPERATOR_READ, "bin": "name"}] + llist = [{"op": 999, "bin": "age", "val": 3}, {"op": aerospike.OPERATOR_READ, "bin": "name"}] try: key, _, _ = self.as_connection.operate_ordered(key, llist) From e4d3df9e17f81b534394010ef74a6a5464673daf Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 15 Oct 2025 11:45:08 -0700 Subject: [PATCH 018/131] Document cdt select flags --- doc/aerospike.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/doc/aerospike.rst b/doc/aerospike.rst index 5065e814fe..540b53dc17 100644 --- a/doc/aerospike.rst +++ b/doc/aerospike.rst @@ -1792,3 +1792,26 @@ Transaction State .. data:: TXN_STATE_COMMITTED .. data:: TXN_STATE_ABORTED + +.. _cdt_select_flags: + +CDT Select Flags +---------------- + +.. data:: CDT_SELECT_TREE + + Return a tree from the root (bin) level to the bottom of the tree, with only non-filtered out nodes + +.. data:: CDT_SELECT_LEAF_LIST_VALUE +.. data:: CDT_SELECT_LEAF_MAP_VALUE + + Return the list of the values of the nodes finally selected by the context + + +.. data:: CDT_SELECT_LEAF_MAP_KEY + + For final selected nodes which are elements of maps, return the appropiate map key + +.. data:: CDT_SELECT_NO_FAIL + + If the expression in the context hits an invalid type (eg selects as an integer when the value is a string), do not fail the operation, just ignore those elements. From 1407c762d6b1a90e1cac478179028535b1092c38 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 15 Oct 2025 11:49:32 -0700 Subject: [PATCH 019/131] make less ambiguous --- doc/aerospike.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/aerospike.rst b/doc/aerospike.rst index 540b53dc17..4639495e7b 100644 --- a/doc/aerospike.rst +++ b/doc/aerospike.rst @@ -1802,11 +1802,15 @@ CDT Select Flags Return a tree from the root (bin) level to the bottom of the tree, with only non-filtered out nodes +.. |CDT_SELECT_VALUE_DOCSTRING| replace:: Return the list of the values of the nodes finally selected by the context + .. data:: CDT_SELECT_LEAF_LIST_VALUE -.. data:: CDT_SELECT_LEAF_MAP_VALUE - Return the list of the values of the nodes finally selected by the context + |CDT_SELECT_VALUE_DOCSTRING| + +.. data:: CDT_SELECT_LEAF_MAP_VALUE + |CDT_SELECT_VALUE_DOCSTRING| .. data:: CDT_SELECT_LEAF_MAP_KEY From 7774423fcf3919f18536299f20c452d06ea9e3da Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 15 Oct 2025 15:08:52 -0700 Subject: [PATCH 020/131] Move outside of if block --- src/main/conversions.c | 220 ++++++++++++++++++++--------------------- 1 file changed, 108 insertions(+), 112 deletions(-) diff --git a/src/main/conversions.c b/src/main/conversions.c index 0ced92424b..86bea8ee79 100644 --- a/src/main/conversions.c +++ b/src/main/conversions.c @@ -2760,132 +2760,128 @@ as_status get_cdt_ctx(AerospikeClient *self, as_error *err, as_cdt_ctx *cdt_ctx, PyObject *py_value = NULL; PyObject *py_extra_args = NULL; - if (PyList_Check(py_ctx)) { - Py_ssize_t py_list_size = PyList_Size(py_ctx); - as_cdt_ctx_init(cdt_ctx, (int)py_list_size); + if (!PyList_Check(py_ctx)) { + status = as_error_update(err, AEROSPIKE_ERR_PARAM, + "Failed to convert %s", CTX_KEY); + goto CLEANUP4; + } - for (int i = 0; i < py_list_size; i++) { - PyObject *py_val = PyList_GetItem(py_ctx, (Py_ssize_t)i); + Py_ssize_t py_list_size = PyList_Size(py_ctx); + as_cdt_ctx_init(cdt_ctx, (int)py_list_size); - py_id = PyObject_GetAttrString(py_val, "id"); - if (PyErr_Occurred()) { - as_cdt_ctx_destroy(cdt_ctx); - status = as_error_update(err, AEROSPIKE_ERR_PARAM, - "Failed to convert %s, id", CTX_KEY); - goto CLEANUP4; - } + for (int i = 0; i < py_list_size; i++) { + PyObject *py_val = PyList_GetItem(py_ctx, (Py_ssize_t)i); - py_value = PyObject_GetAttrString(py_val, "value"); - if (PyErr_Occurred()) { - as_cdt_ctx_destroy(cdt_ctx); - status = - as_error_update(err, AEROSPIKE_ERR_PARAM, - "Failed to convert %s, value", CTX_KEY); - goto CLEANUP3; - } + py_id = PyObject_GetAttrString(py_val, "id"); + if (PyErr_Occurred()) { + as_cdt_ctx_destroy(cdt_ctx); + status = as_error_update(err, AEROSPIKE_ERR_PARAM, + "Failed to convert %s, id", CTX_KEY); + goto CLEANUP4; + } - py_extra_args = PyObject_GetAttrString(py_val, "extra_args"); - if (PyErr_Occurred()) { - as_cdt_ctx_destroy(cdt_ctx); - status = as_error_update(err, AEROSPIKE_ERR_PARAM, - "Failed to convert %s", CTX_KEY); - goto CLEANUP2; - } + py_value = PyObject_GetAttrString(py_val, "value"); + if (PyErr_Occurred()) { + as_cdt_ctx_destroy(cdt_ctx); + status = as_error_update(err, AEROSPIKE_ERR_PARAM, + "Failed to convert %s, value", CTX_KEY); + goto CLEANUP3; + } + + py_extra_args = PyObject_GetAttrString(py_val, "extra_args"); + if (PyErr_Occurred()) { + as_cdt_ctx_destroy(cdt_ctx); + status = as_error_update(err, AEROSPIKE_ERR_PARAM, + "Failed to convert %s", CTX_KEY); + goto CLEANUP2; + } - uint64_t item_type = PyLong_AsUnsignedLongLong(py_id); + uint64_t item_type = PyLong_AsUnsignedLongLong(py_id); + if (PyErr_Occurred()) { + as_cdt_ctx_destroy(cdt_ctx); + status = as_error_update(err, AEROSPIKE_ERR_PARAM, + "Failed to convert %s, id to uint64_t", + CTX_KEY); + goto CLEANUP1; + } + + // add an as_cdt_ctx with value to cdt_ctx + if (requires_int(item_type)) { + int_val = PyLong_AsLong(py_value); if (PyErr_Occurred()) { as_cdt_ctx_destroy(cdt_ctx); status = as_error_update(err, AEROSPIKE_ERR_PARAM, - "Failed to convert %s, id to uint64_t", + "Failed to convert %s, value to long", CTX_KEY); goto CLEANUP1; } - - // add an as_cdt_ctx with value to cdt_ctx - if (requires_int(item_type)) { - int_val = PyLong_AsLong(py_value); - if (PyErr_Occurred()) { - as_cdt_ctx_destroy(cdt_ctx); - status = as_error_update( - err, AEROSPIKE_ERR_PARAM, - "Failed to convert %s, value to long", CTX_KEY); - goto CLEANUP1; - } - switch (item_type) { - case AS_CDT_CTX_LIST_INDEX: - as_cdt_ctx_add_list_index(cdt_ctx, int_val); - break; - case AS_CDT_CTX_LIST_RANK: - as_cdt_ctx_add_list_rank(cdt_ctx, int_val); - break; - case AS_CDT_CTX_MAP_INDEX: - as_cdt_ctx_add_map_index(cdt_ctx, int_val); - break; - case AS_CDT_CTX_MAP_RANK: - as_cdt_ctx_add_map_rank(cdt_ctx, int_val); - break; - case CDT_CTX_LIST_INDEX_CREATE:; - int list_order = 0; - int pad = 0; - get_int_from_py_dict(err, CDT_CTX_ORDER_KEY, py_extra_args, - &list_order); - get_int_from_py_dict(err, CDT_CTX_PAD_KEY, py_extra_args, - &pad); - as_cdt_ctx_add_list_index_create(cdt_ctx, int_val, - list_order, pad); - break; - default: - as_cdt_ctx_destroy(cdt_ctx); - status = as_error_update( - err, AEROSPIKE_ERR_PARAM, - "Failed to convert, unknown ctx operation %s", CTX_KEY); - goto CLEANUP1; - } + switch (item_type) { + case AS_CDT_CTX_LIST_INDEX: + as_cdt_ctx_add_list_index(cdt_ctx, int_val); + break; + case AS_CDT_CTX_LIST_RANK: + as_cdt_ctx_add_list_rank(cdt_ctx, int_val); + break; + case AS_CDT_CTX_MAP_INDEX: + as_cdt_ctx_add_map_index(cdt_ctx, int_val); + break; + case AS_CDT_CTX_MAP_RANK: + as_cdt_ctx_add_map_rank(cdt_ctx, int_val); + break; + case CDT_CTX_LIST_INDEX_CREATE:; + int list_order = 0; + int pad = 0; + get_int_from_py_dict(err, CDT_CTX_ORDER_KEY, py_extra_args, + &list_order); + get_int_from_py_dict(err, CDT_CTX_PAD_KEY, py_extra_args, &pad); + as_cdt_ctx_add_list_index_create(cdt_ctx, int_val, list_order, + pad); + break; + default: + as_cdt_ctx_destroy(cdt_ctx); + status = as_error_update( + err, AEROSPIKE_ERR_PARAM, + "Failed to convert, unknown ctx operation %s", CTX_KEY); + goto CLEANUP1; } - else { - if (as_val_new_from_pyobject(self, err, py_value, &val, - static_pool, - serializer_type) != AEROSPIKE_OK) { - as_cdt_ctx_destroy(cdt_ctx); - status = as_error_update( - err, AEROSPIKE_ERR_PARAM, - "Failed to convert %s, value to as_val", CTX_KEY); - goto CLEANUP1; - } - switch (item_type) { - case AS_CDT_CTX_LIST_VALUE: - as_cdt_ctx_add_list_value(cdt_ctx, val); - break; - case AS_CDT_CTX_MAP_KEY: - as_cdt_ctx_add_map_key(cdt_ctx, val); - break; - case AS_CDT_CTX_MAP_VALUE: - as_cdt_ctx_add_map_value(cdt_ctx, val); - break; - case CDT_CTX_MAP_KEY_CREATE:; - int map_order = 0; - get_int_from_py_dict(err, CDT_CTX_ORDER_KEY, py_extra_args, - &map_order); - as_cdt_ctx_add_map_key_create(cdt_ctx, val, map_order); - break; - default: - as_cdt_ctx_destroy(cdt_ctx); - status = as_error_update( - err, AEROSPIKE_ERR_PARAM, - "Failed to convert, unknown ctx operation %s", CTX_KEY); - goto CLEANUP1; - } + } + else { + if (as_val_new_from_pyobject(self, err, py_value, &val, static_pool, + serializer_type) != AEROSPIKE_OK) { + as_cdt_ctx_destroy(cdt_ctx); + status = as_error_update( + err, AEROSPIKE_ERR_PARAM, + "Failed to convert %s, value to as_val", CTX_KEY); + goto CLEANUP1; + } + switch (item_type) { + case AS_CDT_CTX_LIST_VALUE: + as_cdt_ctx_add_list_value(cdt_ctx, val); + break; + case AS_CDT_CTX_MAP_KEY: + as_cdt_ctx_add_map_key(cdt_ctx, val); + break; + case AS_CDT_CTX_MAP_VALUE: + as_cdt_ctx_add_map_value(cdt_ctx, val); + break; + case CDT_CTX_MAP_KEY_CREATE:; + int map_order = 0; + get_int_from_py_dict(err, CDT_CTX_ORDER_KEY, py_extra_args, + &map_order); + as_cdt_ctx_add_map_key_create(cdt_ctx, val, map_order); + break; + default: + as_cdt_ctx_destroy(cdt_ctx); + status = as_error_update( + err, AEROSPIKE_ERR_PARAM, + "Failed to convert, unknown ctx operation %s", CTX_KEY); + goto CLEANUP1; } - - Py_DECREF(py_id); - Py_DECREF(py_value); - Py_DECREF(py_extra_args); } - } - else { - status = as_error_update(err, AEROSPIKE_ERR_PARAM, - "Failed to convert %s", CTX_KEY); - goto CLEANUP4; + + Py_DECREF(py_id); + Py_DECREF(py_value); + Py_DECREF(py_extra_args); } *ctx_in_use = true; From e0f890d891a4a83212eac17044928146cf9eb3fb Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 15 Oct 2025 15:19:16 -0700 Subject: [PATCH 021/131] Implement cdt_select ctx --- aerospike_helpers/cdt_ctx.py | 5 ++--- src/main/aerospike.c | 2 +- src/main/conversions.c | 6 ++++++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/aerospike_helpers/cdt_ctx.py b/aerospike_helpers/cdt_ctx.py index 144e850607..890e0975a0 100644 --- a/aerospike_helpers/cdt_ctx.py +++ b/aerospike_helpers/cdt_ctx.py @@ -309,8 +309,7 @@ def cdt_ctx_all() -> _cdt_ctx: Returns: :class:`~aerospike_helpers.cdt_ctx._cdt_ctx` """ - # TODO missing id in c client - return _cdt_ctx() + return _cdt_ctx(id=aerospike._CDT_CTX_EXP) def cdt_ctx_exp(expression: "TypeExpression") -> _cdt_ctx: # TODO: expr needs to be compiled? @@ -323,4 +322,4 @@ def cdt_ctx_exp(expression: "TypeExpression") -> _cdt_ctx: Returns: :class:`~aerospike_helpers.cdt_ctx._cdt_ctx` """ - return _cdt_ctx(id=aerospike.CDT_CTX_EXP) + return _cdt_ctx(id=aerospike._CDT_CTX_EXP) diff --git a/src/main/aerospike.c b/src/main/aerospike.c index 71816f7c63..36b1624835 100644 --- a/src/main/aerospike.c +++ b/src/main/aerospike.c @@ -433,7 +433,7 @@ static struct module_constant_name_to_value module_constants[] = { {"CDT_CTX_MAP_KEY", .value.integer = AS_CDT_CTX_MAP_KEY}, {"CDT_CTX_MAP_VALUE", .value.integer = AS_CDT_CTX_MAP_VALUE}, {"CDT_CTX_MAP_KEY_CREATE", .value.integer = CDT_CTX_MAP_KEY_CREATE}, - {"CDT_CTX_EXP", .value.integer = AS_CDT_CTX_EXP}, + {"_CDT_CTX_EXP", .value.integer = AS_CDT_CTX_EXP}, /* Expression variable built-in type */ // TODO: missing docs diff --git a/src/main/conversions.c b/src/main/conversions.c index 86bea8ee79..7afb7cbc34 100644 --- a/src/main/conversions.c +++ b/src/main/conversions.c @@ -2767,6 +2767,7 @@ as_status get_cdt_ctx(AerospikeClient *self, as_error *err, as_cdt_ctx *cdt_ctx, } Py_ssize_t py_list_size = PyList_Size(py_ctx); + // TODO: refactor *_destroy() method... as_cdt_ctx_init(cdt_ctx, (int)py_list_size); for (int i = 0; i < py_list_size; i++) { @@ -2845,6 +2846,10 @@ as_status get_cdt_ctx(AerospikeClient *self, as_error *err, as_cdt_ctx *cdt_ctx, goto CLEANUP1; } } + else if (item_type == AS_CDT_CTX_EXP) { + // TODO: needs other api with exp + as_cdt_ctx_add_all(cdt_ctx); + } else { if (as_val_new_from_pyobject(self, err, py_value, &val, static_pool, serializer_type) != AEROSPIKE_OK) { @@ -2854,6 +2859,7 @@ as_status get_cdt_ctx(AerospikeClient *self, as_error *err, as_cdt_ctx *cdt_ctx, "Failed to convert %s, value to as_val", CTX_KEY); goto CLEANUP1; } + switch (item_type) { case AS_CDT_CTX_LIST_VALUE: as_cdt_ctx_add_list_value(cdt_ctx, val); From 6fd8036e58c7fe94c817148f232c740e2459dfc2 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 15 Oct 2025 15:29:44 -0700 Subject: [PATCH 022/131] finish test for cdt_select operation --- test/new_tests/test_operate_helpers.py | 39 ++++++++++++++++++++------ 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/test/new_tests/test_operate_helpers.py b/test/new_tests/test_operate_helpers.py index 3ec050dfb7..cda4ee5309 100644 --- a/test/new_tests/test_operate_helpers.py +++ b/test/new_tests/test_operate_helpers.py @@ -204,15 +204,6 @@ def teardown(): {"write_bin": 0}, ), ( - ("test", "demo", "existing_key"), - [ - operations.cdt_select( - name="dict", - ctx=[ - cdt_ctx.cdt_ctx_all(), - ] - ), - ], {"dict": [1]} ) ] @@ -227,6 +218,36 @@ def test_pos_operate_with_correct_paramters(self, key, llist, expected): assert bins == expected self.as_connection.remove(key) + @pytest.fixture + def insert_record(self): + self.key = ("test", "demo", 1) + self.map_bin_name = "map_bin" + bins = { + self.map_bin_name: { + "a": 1, + "ab": { + "bb": 12 + }, + "b": 2 + } + } + self.as_connection.put(self.key, bins=bins) + yield + self.as_connection.remove() + + def test_cdt_select(self, insert_record): + ops = [ + operations.cdt_select( + name="bin", + ctx=[ + cdt_ctx.cdt_ctx_all(), + ] + ) + ] + _, _, bins = self.as_connection.operate(ops) + # TODO: list order + assert bins[self.map_bin_name] == list(bins[self.map_bin_name].values()) + @pytest.mark.parametrize( "key, llist, expected", [ From c9ee120c405a7120020c43c10a57a78db5f3c21d Mon Sep 17 00:00:00 2001 From: juliannguyen4 <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 15 Oct 2025 22:46:42 +0000 Subject: [PATCH 023/131] Fix test and code. need to run against server rc --- src/main/client/operate.c | 11 ++++++++++- test/new_tests/test_operate_helpers.py | 9 +++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/main/client/operate.c b/src/main/client/operate.c index 6b9340743f..4ffa2c9eda 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -264,6 +264,7 @@ bool opRequiresIndex(int op) op == OP_MAP_REMOVE_BY_INDEX_RANGE || op == OP_LIST_INCREMENT); } +// TODO: Can be optimized with bitwise operations? bool opRequiresValue(int op) { return (op != AS_OPERATOR_READ && op != AS_OPERATOR_TOUCH && @@ -275,7 +276,8 @@ bool opRequiresValue(int op) op != OP_MAP_REMOVE_BY_RANK && op != OP_MAP_GET_BY_KEY && op != OP_MAP_GET_BY_INDEX && op != OP_MAP_GET_BY_KEY_RANGE && op != OP_MAP_GET_BY_RANK && op != AS_OPERATOR_DELETE && - op != OP_MAP_CREATE && op != OP_LIST_CREATE); + op != OP_MAP_CREATE && op != OP_LIST_CREATE && + op != AS_OPERATOR_CDT_READ && op != AS_OPERATOR_CDT_MODIFY); } bool opRequiresRange(int op) @@ -426,6 +428,13 @@ as_status add_op(AerospikeClient *self, as_error *err, else if (strcmp(name, "persist_index") == 0) { py_persist_index = value; } + // Use set instead of this? + else if (strcmp(name, "expr") == 0) { + continue; + } + else if (strcmp(name, "flags") == 0) { + continue; + } else { as_error_update( err, AEROSPIKE_ERR_PARAM, diff --git a/test/new_tests/test_operate_helpers.py b/test/new_tests/test_operate_helpers.py index cda4ee5309..efa05ab374 100644 --- a/test/new_tests/test_operate_helpers.py +++ b/test/new_tests/test_operate_helpers.py @@ -203,9 +203,6 @@ def teardown(): [operations.write("write_bin", False), operations.read("write_bin")], {"write_bin": 0}, ), - ( - {"dict": [1]} - ) ] ) def test_pos_operate_with_correct_paramters(self, key, llist, expected): @@ -233,18 +230,18 @@ def insert_record(self): } self.as_connection.put(self.key, bins=bins) yield - self.as_connection.remove() + self.as_connection.remove(self.key) def test_cdt_select(self, insert_record): ops = [ operations.cdt_select( - name="bin", + name=self.map_bin_name, ctx=[ cdt_ctx.cdt_ctx_all(), ] ) ] - _, _, bins = self.as_connection.operate(ops) + _, _, bins = self.as_connection.operate(self.key, ops) # TODO: list order assert bins[self.map_bin_name] == list(bins[self.map_bin_name].values()) From 4abaf19ecfd11c3f28b757e2d913542a617325ad Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 15 Oct 2025 16:03:07 -0700 Subject: [PATCH 024/131] Follow prd examples --- test/new_tests/test_operate_helpers.py | 102 ++++++++++++++++++++----- 1 file changed, 84 insertions(+), 18 deletions(-) diff --git a/test/new_tests/test_operate_helpers.py b/test/new_tests/test_operate_helpers.py index efa05ab374..b092c5da02 100644 --- a/test/new_tests/test_operate_helpers.py +++ b/test/new_tests/test_operate_helpers.py @@ -215,35 +215,101 @@ def test_pos_operate_with_correct_paramters(self, key, llist, expected): assert bins == expected self.as_connection.remove(key) - @pytest.fixture - def insert_record(self): - self.key = ("test", "demo", 1) - self.map_bin_name = "map_bin" - bins = { - self.map_bin_name: { + MAP_BIN_NAME = "map_bin" + LIST_BIN_NAME = "list_bin" + BINS_FOR_CDT_SELECT_TEST = { + MAP_BIN_NAME: { + "a": 1, + "ab": { + "bb": 12 + }, + "b": 2 + }, + LIST_BIN_NAME: [ + { "a": 1, "ab": { - "bb": 12 + "aa": 11, + "ab": 13, + "bb": 12 }, "b": 2 + }, + { + "c": 3, + "cd": { + "cc": 9 + }, + "d": 4 } - } - self.as_connection.put(self.key, bins=bins) + ] + } + @pytest.fixture + def insert_record(self): + self.key = ("test", "demo", 1) + self.as_connection.put(self.key, bins=self.BINS_FOR_CDT_SELECT_TEST) yield self.as_connection.remove(self.key) - def test_cdt_select(self, insert_record): - ops = [ - operations.cdt_select( - name=self.map_bin_name, - ctx=[ - cdt_ctx.cdt_ctx_all(), - ] + @pytest.mark.parametrize( + # TODO: ids + "op, expected_bins", + [ + ( + operations.cdt_select( + name=LIST_BIN_NAME, + ctx=[ + cdt_ctx.cdt_ctx_all(), + ] + ), + { + LIST_BIN_NAME: BINS_FOR_CDT_SELECT_TEST[LIST_BIN_NAME] + } + ), + ( + operations.cdt_select( + name=MAP_BIN_NAME, + ctx=[ + cdt_ctx.cdt_ctx_all(), + ] + ), + { + MAP_BIN_NAME: BINS_FOR_CDT_SELECT_TEST[MAP_BIN_NAME] + } + ), + ( + operations.cdt_select( + name=MAP_BIN_NAME, + ctx=[ + cdt_ctx.cdt_ctx_all(), + cdt_ctx.cdt_ctx_all() + ] + ), + { + LIST_BIN_NAME: [ + 1, + { + "aa": 11, + "ab": 13, + "bb": 12 + }, + 2, + 3, + { + "cc": 9 + }, + 4 + ] + } ) ] + ) + def test_cdt_select(self, insert_record, op, expected_bins): + ops = [ + op + ] _, _, bins = self.as_connection.operate(self.key, ops) - # TODO: list order - assert bins[self.map_bin_name] == list(bins[self.map_bin_name].values()) + assert bins == expected_bins @pytest.mark.parametrize( "key, llist, expected", From f498bf85b954c906fc600bcfdbea5391c25b4c83 Mon Sep 17 00:00:00 2001 From: juliannguyen4 <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 15 Oct 2025 23:20:07 +0000 Subject: [PATCH 025/131] fix test case --- test/new_tests/test_operate_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/new_tests/test_operate_helpers.py b/test/new_tests/test_operate_helpers.py index b092c5da02..5182db538b 100644 --- a/test/new_tests/test_operate_helpers.py +++ b/test/new_tests/test_operate_helpers.py @@ -279,7 +279,7 @@ def insert_record(self): ), ( operations.cdt_select( - name=MAP_BIN_NAME, + name=LIST_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all(), cdt_ctx.cdt_ctx_all() From 217010ab7fda04ca217749491fbb9ce74e743251 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 15 Oct 2025 16:26:00 -0700 Subject: [PATCH 026/131] Add neg test case --- test/new_tests/test_operate_helpers.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/new_tests/test_operate_helpers.py b/test/new_tests/test_operate_helpers.py index 5182db538b..64470cfddb 100644 --- a/test/new_tests/test_operate_helpers.py +++ b/test/new_tests/test_operate_helpers.py @@ -311,6 +311,26 @@ def test_cdt_select(self, insert_record, op, expected_bins): _, _, bins = self.as_connection.operate(self.key, ops) assert bins == expected_bins + @pytest.mark.parametrize( + "op, context", + [ + ( + operations.cdt_select( + name=MAP_BIN_NAME, + ctx=[], + ), + # TODO: vague + pytest.raises(e.AerospikeError) + ), + ] + ) + def test_cdt_select_negative_cases(self, insert_record, op, context): + ops = [ + op + ] + with context: + self.as_connection.operate(self.key, ops) + @pytest.mark.parametrize( "key, llist, expected", [ From 7618495959ea48b3e7aefd50157197c11f207fb3 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 15 Oct 2025 16:31:17 -0700 Subject: [PATCH 027/131] Stub for testing cdt select flags --- test/new_tests/test_operate_helpers.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/test/new_tests/test_operate_helpers.py b/test/new_tests/test_operate_helpers.py index 64470cfddb..33e55183bb 100644 --- a/test/new_tests/test_operate_helpers.py +++ b/test/new_tests/test_operate_helpers.py @@ -304,13 +304,34 @@ def insert_record(self): ) ] ) - def test_cdt_select(self, insert_record, op, expected_bins): + def test_cdt_select_basic_functionality(self, insert_record, op, expected_bins): ops = [ op ] _, _, bins = self.as_connection.operate(self.key, ops) assert bins == expected_bins + @pytest.mark.parametrize( + "flags", [ + aerospike.CDT_SELECT_TREE, + # TODO: combine? + aerospike.CDT_SELECT_LEAF_LIST_VALUE, + aerospike.CDT_SELECT_LEAF_MAP_VALUE, + aerospike.CDT_SELECT_LEAF_MAP_KEY, + aerospike.CDT_SELECT_NO_FAIL + ] + ) + def test_cdt_select_flags(self, insert_record, flags, expected_bins): + ops = [ + operations.cdt_select( + # TODO: not done + flags=flags + ) + ] + _, _, bins = self.as_connection.operate(self.key, ops) + assert bins == expected_bins + + @pytest.mark.parametrize( "op, context", [ From 5679f3742df0bd6f7c42268085dec8e947d02a58 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 15 Oct 2025 16:44:46 -0700 Subject: [PATCH 028/131] Move test cases to separate file because the patch is already large enough. --- test/new_tests/test_cdt_select.py | 161 +++++++++++++++++++++++++ test/new_tests/test_operate_helpers.py | 137 --------------------- 2 files changed, 161 insertions(+), 137 deletions(-) create mode 100644 test/new_tests/test_cdt_select.py diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py new file mode 100644 index 0000000000..71b33f4985 --- /dev/null +++ b/test/new_tests/test_cdt_select.py @@ -0,0 +1,161 @@ +import pytest + +import aerospike +from aerospike_helpers.operations import operations +from aerospike_helpers.expressions.base import GE, VarBuiltInMap +from aerospike_helpers import cdt_ctx +from aerospike import exception as e + +class TestCDTSelectOperations: + MAP_BIN_NAME = "map_bin" + LIST_BIN_NAME = "list_bin" + BINS_FOR_CDT_SELECT_TEST = { + MAP_BIN_NAME: { + "a": 1, + "ab": { + "bb": 12 + }, + "b": 2 + }, + LIST_BIN_NAME: [ + { + "a": 1, + "ab": { + "aa": 11, + "ab": 13, + "bb": 12 + }, + "b": 2 + }, + { + "c": 3, + "cd": { + "cc": 9 + }, + "d": 4 + } + ] + } + @pytest.fixture(autouse=True) + def insert_record(self): + self.key = ("test", "demo", 1) + self.as_connection.put(self.key, bins=self.BINS_FOR_CDT_SELECT_TEST) + yield + self.as_connection.remove(self.key) + + @pytest.mark.parametrize( + # TODO: ids + "op, expected_bins", + [ + ( + operations.cdt_select( + name=LIST_BIN_NAME, + ctx=[ + cdt_ctx.cdt_ctx_all(), + ] + ), + { + LIST_BIN_NAME: BINS_FOR_CDT_SELECT_TEST[LIST_BIN_NAME] + } + ), + ( + operations.cdt_select( + name=MAP_BIN_NAME, + ctx=[ + cdt_ctx.cdt_ctx_all(), + ] + ), + { + MAP_BIN_NAME: BINS_FOR_CDT_SELECT_TEST[MAP_BIN_NAME] + } + ), + ( + operations.cdt_select( + name=LIST_BIN_NAME, + ctx=[ + cdt_ctx.cdt_ctx_all(), + cdt_ctx.cdt_ctx_all() + ] + ), + { + LIST_BIN_NAME: [ + 1, + { + "aa": 11, + "ab": 13, + "bb": 12 + }, + 2, + 3, + { + "cc": 9 + }, + 4 + ] + } + ) + ] + ) + def test_cdt_select_basic_functionality(self, op, expected_bins): + ops = [ + op + ] + _, _, bins = self.as_connection.operate(self.key, ops) + assert bins == expected_bins + + @pytest.mark.parametrize( + "flags", [ + aerospike.CDT_SELECT_TREE, + # TODO: combine? + aerospike.CDT_SELECT_LEAF_LIST_VALUE, + # TODO: bad naming? + aerospike.CDT_SELECT_LEAF_MAP_VALUE, + aerospike.CDT_SELECT_LEAF_MAP_KEY, + aerospike.CDT_SELECT_NO_FAIL + ] + ) + def test_cdt_select_flags(self, flags, expected_bins): + ops = [ + operations.cdt_select( + # TODO: not done + flags=flags + ) + ] + _, _, bins = self.as_connection.operate(self.key, ops) + assert bins == expected_bins + + @pytest.mark.parametrize( + "op, context", + [ + ( + operations.cdt_select( + name=MAP_BIN_NAME, + ctx=[], + ), + # TODO: vague + pytest.raises(e.AerospikeError) + ), + ] + ) + def test_cdt_select_negative_cases(self, op, context): + ops = [ + op + ] + with context: + self.as_connection.operate(self.key, ops) + + def test_cdt_apply(self): + expr = GE( + VarBuiltInMap(aerospike.EXP_BUILTIN_VALUE), + 15 + ).compile() + ops = [ + operations.cdt_apply( + name=self.LIST_BIN_NAME, + ctx=[ + cdt_ctx.cdt_ctx_all(), + ], + expr=expr + ) + ] + self.as_connection.operate(self.key, ops) diff --git a/test/new_tests/test_operate_helpers.py b/test/new_tests/test_operate_helpers.py index 33e55183bb..d61a2e2a28 100644 --- a/test/new_tests/test_operate_helpers.py +++ b/test/new_tests/test_operate_helpers.py @@ -2,7 +2,6 @@ import pytest from .test_base_class import TestBaseClass from aerospike_helpers.operations import list_operations, operations -from aerospike_helpers import cdt_ctx import aerospike from aerospike import exception as e @@ -215,142 +214,6 @@ def test_pos_operate_with_correct_paramters(self, key, llist, expected): assert bins == expected self.as_connection.remove(key) - MAP_BIN_NAME = "map_bin" - LIST_BIN_NAME = "list_bin" - BINS_FOR_CDT_SELECT_TEST = { - MAP_BIN_NAME: { - "a": 1, - "ab": { - "bb": 12 - }, - "b": 2 - }, - LIST_BIN_NAME: [ - { - "a": 1, - "ab": { - "aa": 11, - "ab": 13, - "bb": 12 - }, - "b": 2 - }, - { - "c": 3, - "cd": { - "cc": 9 - }, - "d": 4 - } - ] - } - @pytest.fixture - def insert_record(self): - self.key = ("test", "demo", 1) - self.as_connection.put(self.key, bins=self.BINS_FOR_CDT_SELECT_TEST) - yield - self.as_connection.remove(self.key) - - @pytest.mark.parametrize( - # TODO: ids - "op, expected_bins", - [ - ( - operations.cdt_select( - name=LIST_BIN_NAME, - ctx=[ - cdt_ctx.cdt_ctx_all(), - ] - ), - { - LIST_BIN_NAME: BINS_FOR_CDT_SELECT_TEST[LIST_BIN_NAME] - } - ), - ( - operations.cdt_select( - name=MAP_BIN_NAME, - ctx=[ - cdt_ctx.cdt_ctx_all(), - ] - ), - { - MAP_BIN_NAME: BINS_FOR_CDT_SELECT_TEST[MAP_BIN_NAME] - } - ), - ( - operations.cdt_select( - name=LIST_BIN_NAME, - ctx=[ - cdt_ctx.cdt_ctx_all(), - cdt_ctx.cdt_ctx_all() - ] - ), - { - LIST_BIN_NAME: [ - 1, - { - "aa": 11, - "ab": 13, - "bb": 12 - }, - 2, - 3, - { - "cc": 9 - }, - 4 - ] - } - ) - ] - ) - def test_cdt_select_basic_functionality(self, insert_record, op, expected_bins): - ops = [ - op - ] - _, _, bins = self.as_connection.operate(self.key, ops) - assert bins == expected_bins - - @pytest.mark.parametrize( - "flags", [ - aerospike.CDT_SELECT_TREE, - # TODO: combine? - aerospike.CDT_SELECT_LEAF_LIST_VALUE, - aerospike.CDT_SELECT_LEAF_MAP_VALUE, - aerospike.CDT_SELECT_LEAF_MAP_KEY, - aerospike.CDT_SELECT_NO_FAIL - ] - ) - def test_cdt_select_flags(self, insert_record, flags, expected_bins): - ops = [ - operations.cdt_select( - # TODO: not done - flags=flags - ) - ] - _, _, bins = self.as_connection.operate(self.key, ops) - assert bins == expected_bins - - - @pytest.mark.parametrize( - "op, context", - [ - ( - operations.cdt_select( - name=MAP_BIN_NAME, - ctx=[], - ), - # TODO: vague - pytest.raises(e.AerospikeError) - ), - ] - ) - def test_cdt_select_negative_cases(self, insert_record, op, context): - ops = [ - op - ] - with context: - self.as_connection.operate(self.key, ops) @pytest.mark.parametrize( "key, llist, expected", From ccebaefe799983ed8a1ac9f360dc754e5531bfe6 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 15 Oct 2025 19:37:56 -0700 Subject: [PATCH 029/131] Finish cdt_apply op test --- src/main/aerospike.c | 7 +++-- test/new_tests/test_cdt_select.py | 44 +++++++++++++++++++++++-------- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/src/main/aerospike.c b/src/main/aerospike.c index 36b1624835..4a41e8f862 100644 --- a/src/main/aerospike.c +++ b/src/main/aerospike.c @@ -435,8 +435,11 @@ static struct module_constant_name_to_value module_constants[] = { {"CDT_CTX_MAP_KEY_CREATE", .value.integer = CDT_CTX_MAP_KEY_CREATE}, {"_CDT_CTX_EXP", .value.integer = AS_CDT_CTX_EXP}, - /* Expression variable built-in type */ - // TODO: missing docs + /* + When doing a cdt select/apply operation, and applying an expression on each + iterated object, this lets us choose a specific value over each iterated + object. + */ {"EXP_BUILTIN_KEY", .value.integer = AS_EXP_BUILTIN_KEY}, {"EXP_BUILTIN_VALUE", .value.integer = AS_EXP_BUILTIN_VALUE}, {"EXP_BUILTIN_INDEX", .value.integer = AS_EXP_BUILTIN_INDEX}, diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 71b33f4985..ecf5787173 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -9,6 +9,7 @@ class TestCDTSelectOperations: MAP_BIN_NAME = "map_bin" LIST_BIN_NAME = "list_bin" + MAP_OF_NESTED_MAPS_BIN_NAME = "map_of_maps_bin" BINS_FOR_CDT_SELECT_TEST = { MAP_BIN_NAME: { "a": 1, @@ -34,7 +35,21 @@ class TestCDTSelectOperations: }, "d": 4 } - ] + ], + MAP_OF_NESTED_MAPS_BIN_NAME: { + "Day1": { + "book": 14.990000, + "ferry": 5.000000, + }, + "Day2": { + "food": 34.000000, + "game": 12.990000, + }, + "Day3": { + "plants": 19.990000, + "stickers": 2.000000 + } + } } @pytest.fixture(autouse=True) def insert_record(self): @@ -47,7 +62,7 @@ def insert_record(self): # TODO: ids "op, expected_bins", [ - ( + pytest.param( operations.cdt_select( name=LIST_BIN_NAME, ctx=[ @@ -56,9 +71,10 @@ def insert_record(self): ), { LIST_BIN_NAME: BINS_FOR_CDT_SELECT_TEST[LIST_BIN_NAME] - } + }, + id="select_all_children_once_in_list" ), - ( + pytest.param( operations.cdt_select( name=MAP_BIN_NAME, ctx=[ @@ -67,9 +83,10 @@ def insert_record(self): ), { MAP_BIN_NAME: BINS_FOR_CDT_SELECT_TEST[MAP_BIN_NAME] - } + }, + id="select_all_children_once_in_map" ), - ( + pytest.param( operations.cdt_select( name=LIST_BIN_NAME, ctx=[ @@ -92,7 +109,8 @@ def insert_record(self): }, 4 ] - } + }, + id="select_all_children_twice_in_list" ) ] ) @@ -144,18 +162,22 @@ def test_cdt_select_negative_cases(self, op, context): with context: self.as_connection.operate(self.key, ops) - def test_cdt_apply(self): + def test_cdt_apply_basic_functionality(self): expr = GE( VarBuiltInMap(aerospike.EXP_BUILTIN_VALUE), - 15 + 20 ).compile() ops = [ operations.cdt_apply( - name=self.LIST_BIN_NAME, + name=self.MAP_OF_NESTED_MAPS_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all(), + cdt_ctx.cdt_ctx_all() ], expr=expr ) ] - self.as_connection.operate(self.key, ops) + _, _, bins = self.as_connection.operate(self.key, ops) + assert bins[self.MAP_OF_NESTED_MAPS_BIN_NAME] == [ + self.BINS_FOR_CDT_SELECT_TEST[self.MAP_OF_NESTED_MAPS_BIN_NAME]["Day2"]["food"] + ] From b7bdb917cf43489aedd1f1dc2dbe8cd820126247 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 15 Oct 2025 20:06:24 -0700 Subject: [PATCH 030/131] Finish cdt_select + ctx that selects all children with filter. Does not modify bin --- aerospike_helpers/cdt_ctx.py | 3 ++- src/main/aerospike.c | 4 ++- src/main/client/operate.c | 2 +- src/main/conversions.c | 43 ++++++++++++++++++++++++------- test/new_tests/test_cdt_select.py | 39 +++++++++++++++------------- 5 files changed, 60 insertions(+), 31 deletions(-) diff --git a/aerospike_helpers/cdt_ctx.py b/aerospike_helpers/cdt_ctx.py index 890e0975a0..28a1970bc8 100644 --- a/aerospike_helpers/cdt_ctx.py +++ b/aerospike_helpers/cdt_ctx.py @@ -322,4 +322,5 @@ def cdt_ctx_exp(expression: "TypeExpression") -> _cdt_ctx: Returns: :class:`~aerospike_helpers.cdt_ctx._cdt_ctx` """ - return _cdt_ctx(id=aerospike._CDT_CTX_EXP) + # TODO: make the same as helper that takes in expr value from dict + return _cdt_ctx(id=aerospike._CDT_CTX_EXP, extra_args={aerospike._CDT_CTX_EXP_EXPR_KEY: expression}) diff --git a/src/main/aerospike.c b/src/main/aerospike.c index 4a41e8f862..96a5e0bee8 100644 --- a/src/main/aerospike.c +++ b/src/main/aerospike.c @@ -543,7 +543,9 @@ static struct module_constant_name_to_value module_constants[] = { .value.integer = AS_CDT_SELECT_LEAF_MAP_VALUE}, {"CDT_SELECT_LEAF_MAP_KEY", .value.integer = AS_CDT_SELECT_LEAF_MAP_KEY}, {"CDT_SELECT_NO_FAIL", .value.integer = AS_CDT_SELECT_NO_FAIL}, -}; + + // TODO: move all internal constants used by aerospike_helpers to this loc + {"_CDT_CTX_EXP_EXPR_KEY", .value.string = "expr"}}; struct submodule_name_to_creation_method { const char *name; diff --git a/src/main/client/operate.c b/src/main/client/operate.c index 4ffa2c9eda..7e8e98cf92 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -589,7 +589,7 @@ as_status add_op(AerospikeClient *self, as_error *err, operation_succeeded = as_operations_cdt_select(ops, bin, ctx_ref, flags); } - else { + else if (operation == AS_OPERATOR_CDT_MODIFY) { PyObject *py_expr = NULL; int retval = PyDict_GetItemStringRef(py_operation_dict, "expr", &py_expr); diff --git a/src/main/conversions.c b/src/main/conversions.c index 7afb7cbc34..aee88c799a 100644 --- a/src/main/conversions.c +++ b/src/main/conversions.c @@ -39,6 +39,7 @@ #include #include +#include "pythoncapi_compat.h" #include "conversions.h" #include "geo.h" #include "policy.h" @@ -2746,9 +2747,9 @@ 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_list = PyDict_GetItemString(op_dict, CTX_KEY); - if (!py_ctx) { + if (!py_ctx_list) { return AEROSPIKE_OK; } @@ -2760,20 +2761,20 @@ as_status get_cdt_ctx(AerospikeClient *self, as_error *err, as_cdt_ctx *cdt_ctx, PyObject *py_value = NULL; PyObject *py_extra_args = NULL; - if (!PyList_Check(py_ctx)) { + if (!PyList_Check(py_ctx_list)) { status = as_error_update(err, AEROSPIKE_ERR_PARAM, "Failed to convert %s", CTX_KEY); goto CLEANUP4; } - Py_ssize_t py_list_size = PyList_Size(py_ctx); + Py_ssize_t py_list_size = PyList_Size(py_ctx_list); // TODO: refactor *_destroy() method... as_cdt_ctx_init(cdt_ctx, (int)py_list_size); for (int i = 0; i < py_list_size; i++) { - PyObject *py_val = PyList_GetItem(py_ctx, (Py_ssize_t)i); + PyObject *py_cdt_ctx = PyList_GetItem(py_ctx_list, (Py_ssize_t)i); - py_id = PyObject_GetAttrString(py_val, "id"); + py_id = PyObject_GetAttrString(py_cdt_ctx, "id"); if (PyErr_Occurred()) { as_cdt_ctx_destroy(cdt_ctx); status = as_error_update(err, AEROSPIKE_ERR_PARAM, @@ -2781,7 +2782,7 @@ as_status get_cdt_ctx(AerospikeClient *self, as_error *err, as_cdt_ctx *cdt_ctx, goto CLEANUP4; } - py_value = PyObject_GetAttrString(py_val, "value"); + py_value = PyObject_GetAttrString(py_cdt_ctx, "value"); if (PyErr_Occurred()) { as_cdt_ctx_destroy(cdt_ctx); status = as_error_update(err, AEROSPIKE_ERR_PARAM, @@ -2789,7 +2790,7 @@ as_status get_cdt_ctx(AerospikeClient *self, as_error *err, as_cdt_ctx *cdt_ctx, goto CLEANUP3; } - py_extra_args = PyObject_GetAttrString(py_val, "extra_args"); + py_extra_args = PyObject_GetAttrString(py_cdt_ctx, "extra_args"); if (PyErr_Occurred()) { as_cdt_ctx_destroy(cdt_ctx); status = as_error_update(err, AEROSPIKE_ERR_PARAM, @@ -2847,8 +2848,30 @@ as_status get_cdt_ctx(AerospikeClient *self, as_error *err, as_cdt_ctx *cdt_ctx, } } else if (item_type == AS_CDT_CTX_EXP) { - // TODO: needs other api with exp - as_cdt_ctx_add_all(cdt_ctx); + if (Py_IsNone(py_extra_args)) { + as_cdt_ctx_add_all(cdt_ctx); + } + else { + // TODO: replace with aerospike._CDT_CTX_EXP_EXPR_KEY + PyObject *py_expr = + PyObject_GetAttrString(py_extra_args, AS_EXPR_KEY); + if (!py_expr) { + status = as_error_update(err, AEROSPIKE_ERR_PARAM, + "Invalid cdt_ctx_exp"); + goto CLEANUP1; + } + + as_exp *expr = NULL; + as_exp_new_from_pyobject(self, py_expr, &expr, err, false); + Py_DECREF(py_expr); + if (err->code != AEROSPIKE_OK) { + goto CLEANUP1; + } + + // This C client call memcpy's the expr's contents + as_cdt_ctx_add_exp(cdt_ctx, expr); + as_exp_destroy(expr); + } } else { if (as_val_new_from_pyobject(self, err, py_value, &val, static_pool, diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index ecf5787173..6d1788bfc0 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -121,6 +121,26 @@ def test_cdt_select_basic_functionality(self, op, expected_bins): _, _, bins = self.as_connection.operate(self.key, ops) assert bins == expected_bins + def test_cdt_select_with_filter(self): + expr = GE( + VarBuiltInMap(aerospike.EXP_BUILTIN_VALUE), + 20 + ).compile() + ops = [ + operations.cdt_apply( + name=self.MAP_OF_NESTED_MAPS_BIN_NAME, + ctx=[ + cdt_ctx.cdt_ctx_all(), + cdt_ctx.cdt_ctx_all() + ], + expr=expr + ) + ] + _, _, bins = self.as_connection.operate(self.key, ops) + assert bins[self.MAP_OF_NESTED_MAPS_BIN_NAME] == [ + self.BINS_FOR_CDT_SELECT_TEST[self.MAP_OF_NESTED_MAPS_BIN_NAME]["Day2"]["food"] + ] + @pytest.mark.parametrize( "flags", [ aerospike.CDT_SELECT_TREE, @@ -163,21 +183,4 @@ def test_cdt_select_negative_cases(self, op, context): self.as_connection.operate(self.key, ops) def test_cdt_apply_basic_functionality(self): - expr = GE( - VarBuiltInMap(aerospike.EXP_BUILTIN_VALUE), - 20 - ).compile() - ops = [ - operations.cdt_apply( - name=self.MAP_OF_NESTED_MAPS_BIN_NAME, - ctx=[ - cdt_ctx.cdt_ctx_all(), - cdt_ctx.cdt_ctx_all() - ], - expr=expr - ) - ] - _, _, bins = self.as_connection.operate(self.key, ops) - assert bins[self.MAP_OF_NESTED_MAPS_BIN_NAME] == [ - self.BINS_FOR_CDT_SELECT_TEST[self.MAP_OF_NESTED_MAPS_BIN_NAME]["Day2"]["food"] - ] + pass From 55c1293cb3868d5f86c5d499233a57c1ed5922ee Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 15 Oct 2025 20:14:33 -0700 Subject: [PATCH 031/131] Properly implement VarBuiltInMap. test before implementing other expr types --- aerospike_helpers/expressions/base.py | 3 +++ aerospike_helpers/expressions/resources.py | 1 + test/new_tests/test_cdt_select.py | 1 + 3 files changed, 5 insertions(+) diff --git a/aerospike_helpers/expressions/base.py b/aerospike_helpers/expressions/base.py index 12e214d951..97fb07e4fc 100644 --- a/aerospike_helpers/expressions/base.py +++ b/aerospike_helpers/expressions/base.py @@ -1077,6 +1077,7 @@ class VarBuiltInMap(_BaseExpr): """ _op = _ExprOp._AS_EXP_CODE_VAR_BUILTIN + # TODO: document to be certain constants? def __init__(self, var_id: int): """Args: `var_id` (int): Variable id. @@ -1084,3 +1085,5 @@ def __init__(self, var_id: int): :return: (value stored in variable) """ self._fixed = {_Keys.VALUE_KEY: var_id} + # TODO: needs to be an expr? + self._children = (ResultType.MAP, var_id) diff --git a/aerospike_helpers/expressions/resources.py b/aerospike_helpers/expressions/resources.py index 9636f23af1..650573d52c 100644 --- a/aerospike_helpers/expressions/resources.py +++ b/aerospike_helpers/expressions/resources.py @@ -114,6 +114,7 @@ class ReturnType: MAP_RETURN_INVERTED = 0x10000 +# These enum constants must match the values for C client's as_exp_type class ResultType: """ Flags used to indicate expression value_type. diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 6d1788bfc0..3da73048b9 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -162,6 +162,7 @@ def test_cdt_select_flags(self, flags, expected_bins): _, _, bins = self.as_connection.operate(self.key, ops) assert bins == expected_bins + # TODO: negative case where cdt_select gets a var type not expected @pytest.mark.parametrize( "op, context", [ From ef3bc91b98a4fa9b2203830affc568ce6ef87705 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 16 Oct 2025 08:31:14 -0700 Subject: [PATCH 032/131] fix test --- test/new_tests/test_cdt_select.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 3da73048b9..f8f514d552 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -6,6 +6,8 @@ from aerospike_helpers import cdt_ctx from aerospike import exception as e + +@pytest.mark.usefixtures("as_connection") class TestCDTSelectOperations: MAP_BIN_NAME = "map_bin" LIST_BIN_NAME = "list_bin" From 3eba73eb0219b681130cb85d624ea2a7381a9524 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 16 Oct 2025 12:25:53 -0700 Subject: [PATCH 033/131] Just add one class for all the VarBuiltIn expressions --- aerospike-stubs/aerospike.pyi | 3 +++ aerospike_helpers/expressions/base.py | 7 ++++--- doc/aerospike.rst | 9 +++++++++ test/new_tests/test_cdt_select.py | 5 +++-- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/aerospike-stubs/aerospike.pyi b/aerospike-stubs/aerospike.pyi index c386b4f10a..db28e2ff88 100644 --- a/aerospike-stubs/aerospike.pyi +++ b/aerospike-stubs/aerospike.pyi @@ -315,6 +315,9 @@ CDT_SELECT_LEAF_MAP_VALUE: Literal[1] CDT_SELECT_LEAF_MAP_KEY: Literal[2] CDT_SELECT_NO_FAIL: Literal[16] +EXP_BUILTIN_KEY: Literal[0] +EXP_BUILTIN_VALUE: Literal[1] +EXP_BUILTIN_INDEX: Literal[2] @final class CDTInfinite: diff --git a/aerospike_helpers/expressions/base.py b/aerospike_helpers/expressions/base.py index 97fb07e4fc..61a23d4c73 100644 --- a/aerospike_helpers/expressions/base.py +++ b/aerospike_helpers/expressions/base.py @@ -1071,14 +1071,15 @@ def __init__(self, var_name: str): self._fixed = {_Keys.VALUE_KEY: var_name} -class VarBuiltInMap(_BaseExpr): +# TODO: forbid unsupported types +class VarBuiltIn(_BaseExpr): """ Retrieve expression value from a built-in variable. """ _op = _ExprOp._AS_EXP_CODE_VAR_BUILTIN # TODO: document to be certain constants? - def __init__(self, var_id: int): + def __init__(self, var_id: int, var_type: ResultType): """Args: `var_id` (int): Variable id. @@ -1086,4 +1087,4 @@ def __init__(self, var_id: int): """ self._fixed = {_Keys.VALUE_KEY: var_id} # TODO: needs to be an expr? - self._children = (ResultType.MAP, var_id) + self._children = (var_id, var_type) diff --git a/doc/aerospike.rst b/doc/aerospike.rst index 4639495e7b..6bf246c654 100644 --- a/doc/aerospike.rst +++ b/doc/aerospike.rst @@ -1819,3 +1819,12 @@ CDT Select Flags .. data:: CDT_SELECT_NO_FAIL If the expression in the context hits an invalid type (eg selects as an integer when the value is a string), do not fail the operation, just ignore those elements. + +Expression Variable Built-in Types +---------------------------------- + +.. data:: EXP_BUILTIN_KEY + +.. data:: EXP_BUILTIN_VALUE + +.. data:: EXP_BUILTIN_INDEX diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index f8f514d552..4492eac8ca 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -2,7 +2,8 @@ import aerospike from aerospike_helpers.operations import operations -from aerospike_helpers.expressions.base import GE, VarBuiltInMap +from aerospike_helpers.expressions.resources import ResultType +from aerospike_helpers.expressions.base import GE, VarBuiltIn from aerospike_helpers import cdt_ctx from aerospike import exception as e @@ -125,7 +126,7 @@ def test_cdt_select_basic_functionality(self, op, expected_bins): def test_cdt_select_with_filter(self): expr = GE( - VarBuiltInMap(aerospike.EXP_BUILTIN_VALUE), + VarBuiltIn(aerospike.EXP_BUILTIN_VALUE, ResultType.FLOAT), 20 ).compile() ops = [ From a01797ebcdb1a6730a39cf973c94ea0b2fbe0406 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 16 Oct 2025 15:59:24 -0700 Subject: [PATCH 034/131] Just finish expression cdt_select() and _apply() later. update c client --- aerospike-client-c | 2 +- aerospike_helpers/expressions/base.py | 39 ++++++++++++++++++++++ aerospike_helpers/expressions/resources.py | 1 + src/main/convert_expressions.c | 6 +++- test/new_tests/test_cdt_select.py | 6 ++++ 5 files changed, 52 insertions(+), 2 deletions(-) diff --git a/aerospike-client-c b/aerospike-client-c index a24f8c47b3..b558b27c13 160000 --- a/aerospike-client-c +++ b/aerospike-client-c @@ -1 +1 @@ -Subproject commit a24f8c47b365022164e53e380f6541ca2d601344 +Subproject commit b558b27c139db5ddb3a8d8746e0292f6d3dff4cd diff --git a/aerospike_helpers/expressions/base.py b/aerospike_helpers/expressions/base.py index 61a23d4c73..0905f332a1 100644 --- a/aerospike_helpers/expressions/base.py +++ b/aerospike_helpers/expressions/base.py @@ -32,6 +32,7 @@ from aerospike_helpers.expressions.resources import _ExprOp from aerospike_helpers.expressions.resources import ResultType from aerospike_helpers.expressions.resources import _Keys +from aerospike_helpers.expressions.list import TypeCTX TypeComparisonArg = Union[_BaseExpr, Any] TypeGeo = Union[_BaseExpr, aerospike.GeoJSON] @@ -1088,3 +1089,41 @@ def __init__(self, var_id: int, var_type: ResultType): self._fixed = {_Keys.VALUE_KEY: var_id} # TODO: needs to be an expr? self._children = (var_id, var_type) + + +class CDTSelect(_BaseExpr): + """ + """ + _op = _ExprOp._AS_EXP_CODE_CALL + + # TODO: document to be certain constants? + # TODO: result_type not needed? + # TODO: why return type needed? + def __init__(self, ctx: TypeCTX, return_type: ResultType, flags: int, bin: str): + """Args: + `ctx`: TODO + + :return: TODO + """ + self._fixed = {_Keys.BIN_KEY: bin} + # TODO: needs to be an expr? + self._children = (flags) + + +class CDTApply(_BaseExpr): + """ + asdf + """ + _op = _ExprOp._AS_EXP_CODE_CALL + + # TODO: document to be certain constants? + # TODO: why return type needed? this returns the whole bin after being modified? + def __init__(self, ctx: TypeCTX, return_type: ResultType, flags: int, bin: str): + """Args: + `ctx`: TODO + + :return: TODO + """ + self._fixed = {_Keys.BIN_KEY: bin} + # TODO: needs to be an expr? + self._children = (flags) diff --git a/aerospike_helpers/expressions/resources.py b/aerospike_helpers/expressions/resources.py index 650573d52c..46448eab97 100644 --- a/aerospike_helpers/expressions/resources.py +++ b/aerospike_helpers/expressions/resources.py @@ -88,6 +88,7 @@ class _ExprOp: # TODO replace this with an enum LET = 125 DEF = 126 + _AS_EXP_CODE_CALL = 127 _AS_EXP_CODE_AS_VAL = 128 # virtual ops _AS_EXP_CODE_CALL_VOP_START = 139 diff --git a/src/main/convert_expressions.c b/src/main/convert_expressions.c index 9568e4f69e..0b0de9c1c9 100644 --- a/src/main/convert_expressions.c +++ b/src/main/convert_expressions.c @@ -454,6 +454,7 @@ static as_status get_expr_size(int *size_to_alloc, int *intermediate_exprs_size, return AEROSPIKE_OK; } +// TODO: reuse helper from conversions.c /* * get_exp_val_from_pyval * Converts a python value into an expression value. @@ -640,8 +641,11 @@ add_expr_macros(AerospikeClient *self, as_static_pool *static_pool, APPEND_ARRAY(0, BIN_EXPR()); break; - case VAL: case _AS_EXP_CODE_VAR_BUILTIN: + // TODO: Not done + APPEND_ARRAY(0, as_exp_var_builtin_float(0)); + break; + case VAL: case _AS_EXP_CODE_AS_VAL:; as_exp_entry tmp_expr; if (get_exp_val_from_pyval( diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 4492eac8ca..237e18d32b 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -144,6 +144,12 @@ def test_cdt_select_with_filter(self): self.BINS_FOR_CDT_SELECT_TEST[self.MAP_OF_NESTED_MAPS_BIN_NAME]["Day2"]["food"] ] + def test_ctx_exp(self): + expr = + ctx=[ + cdt_ctx.cdt_ctx_exp(expression=expr) + ] + @pytest.mark.parametrize( "flags", [ aerospike.CDT_SELECT_TREE, From f924d8569fea540c3d5968245be5ab251c286aa1 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 16 Oct 2025 16:13:17 -0700 Subject: [PATCH 035/131] Fix circular import --- aerospike_helpers/cdt_ctx.py | 10 +--------- aerospike_helpers/expressions/base.py | 6 +++--- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/aerospike_helpers/cdt_ctx.py b/aerospike_helpers/cdt_ctx.py index 28a1970bc8..b5f4704428 100644 --- a/aerospike_helpers/cdt_ctx.py +++ b/aerospike_helpers/cdt_ctx.py @@ -302,25 +302,17 @@ def cdt_ctx_map_key_create(key: any, order: int = 0) -> _cdt_ctx: return _cdt_ctx(id=aerospike.CDT_CTX_MAP_KEY_CREATE, value=key, extra_args={CDT_CTX_ORDER_KEY: order}) def cdt_ctx_all() -> _cdt_ctx: - # TODO: vague docstring """ The cdt_ctx object selects all. - - Returns: - :class:`~aerospike_helpers.cdt_ctx._cdt_ctx` """ return _cdt_ctx(id=aerospike._CDT_CTX_EXP) def cdt_ctx_exp(expression: "TypeExpression") -> _cdt_ctx: - # TODO: expr needs to be compiled? """ - The cdt_ctx applies an expression to select ctx. + Select and filter using an expression. Args: expression: compiled aerospike expression - - Returns: - :class:`~aerospike_helpers.cdt_ctx._cdt_ctx` """ # TODO: make the same as helper that takes in expr value from dict return _cdt_ctx(id=aerospike._CDT_CTX_EXP, extra_args={aerospike._CDT_CTX_EXP_EXPR_KEY: expression}) diff --git a/aerospike_helpers/expressions/base.py b/aerospike_helpers/expressions/base.py index 0905f332a1..56e9406b7d 100644 --- a/aerospike_helpers/expressions/base.py +++ b/aerospike_helpers/expressions/base.py @@ -32,7 +32,7 @@ from aerospike_helpers.expressions.resources import _ExprOp from aerospike_helpers.expressions.resources import ResultType from aerospike_helpers.expressions.resources import _Keys -from aerospike_helpers.expressions.list import TypeCTX +from aerospike_helpers.cdt_ctx import _cdt_ctx TypeComparisonArg = Union[_BaseExpr, Any] TypeGeo = Union[_BaseExpr, aerospike.GeoJSON] @@ -1099,7 +1099,7 @@ class CDTSelect(_BaseExpr): # TODO: document to be certain constants? # TODO: result_type not needed? # TODO: why return type needed? - def __init__(self, ctx: TypeCTX, return_type: ResultType, flags: int, bin: str): + def __init__(self, ctx: _cdt_ctx, return_type: ResultType, flags: int, bin: str): """Args: `ctx`: TODO @@ -1118,7 +1118,7 @@ class CDTApply(_BaseExpr): # TODO: document to be certain constants? # TODO: why return type needed? this returns the whole bin after being modified? - def __init__(self, ctx: TypeCTX, return_type: ResultType, flags: int, bin: str): + def __init__(self, ctx: _cdt_ctx, return_type: ResultType, flags: int, bin: str): """Args: `ctx`: TODO From 0dcca1f958c6a8b41f046cdb0a6f565619d39336 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 16 Oct 2025 16:28:06 -0700 Subject: [PATCH 036/131] Finish cdt modify operation test --- test/new_tests/test_cdt_select.py | 37 ++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 237e18d32b..82d9097c97 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -4,6 +4,7 @@ from aerospike_helpers.operations import operations from aerospike_helpers.expressions.resources import ResultType from aerospike_helpers.expressions.base import GE, VarBuiltIn +from aerospike_helpers.expressions.arithmetic import Sub from aerospike_helpers import cdt_ctx from aerospike import exception as e @@ -130,25 +131,42 @@ def test_cdt_select_with_filter(self): 20 ).compile() ops = [ - operations.cdt_apply( + operations.cdt_select( name=self.MAP_OF_NESTED_MAPS_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all(), - cdt_ctx.cdt_ctx_all() - ], - expr=expr + cdt_ctx.cdt_ctx_exp(expression=expr) + ] ) ] _, _, bins = self.as_connection.operate(self.key, ops) + assert bins[self.MAP_OF_NESTED_MAPS_BIN_NAME] == [ self.BINS_FOR_CDT_SELECT_TEST[self.MAP_OF_NESTED_MAPS_BIN_NAME]["Day2"]["food"] ] - def test_ctx_exp(self): - expr = - ctx=[ - cdt_ctx.cdt_ctx_exp(expression=expr) + def test_cdt_modify(self): + mod_expr = Sub(VarBuiltIn(aerospike.EXP_BUILTIN_VALUE, ResultType.INTEGER), 5).compile() + ops = [ + operations.cdt_apply( + name=self.MAP_OF_NESTED_MAPS_BIN_NAME, + ctx=[ + cdt_ctx.cdt_ctx_all(), + cdt_ctx.cdt_ctx_all() + ], + expr=mod_expr + ), + operations.cdt_select( + name=self.MAP_OF_NESTED_MAPS_BIN_NAME, + ctx=[ + cdt_ctx.cdt_ctx_all(), + cdt_ctx.cdt_ctx_all() + ] + ), ] + _, _, bins = self.as_connection.operate(self.key, ops) + assert bins[self.MAP_OF_NESTED_MAPS_BIN_NAME] == [14.990000, 5.0000, 34.000000, 12.990000, 19.990000, 2.000000] + @pytest.mark.parametrize( "flags", [ @@ -191,6 +209,3 @@ def test_cdt_select_negative_cases(self, op, context): ] with context: self.as_connection.operate(self.key, ops) - - def test_cdt_apply_basic_functionality(self): - pass From d5d7c460a7948d8f8273d9357542ae3641b4631a Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 16 Oct 2025 21:19:15 -0700 Subject: [PATCH 037/131] Fix cdt_apply op --- aerospike_helpers/operations/operations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aerospike_helpers/operations/operations.py b/aerospike_helpers/operations/operations.py index aa0834b730..a2c55ddc95 100644 --- a/aerospike_helpers/operations/operations.py +++ b/aerospike_helpers/operations/operations.py @@ -146,7 +146,7 @@ def cdt_select(name: str, ctx: list, flags: int = aerospike.CDT_SELECT_TREE): return op_dict -def cdt_apply(name: str, ctx: list, expr: TypeExpression, flags: int): +def cdt_apply(name: str, ctx: list, expr: TypeExpression, flags: int = aerospike.CDT_SELECT_TREE): """ Create CDT apply operation. From f5482d4c8cb1cdaaa45e36c2fb410ec70e28b337 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 16 Oct 2025 21:21:01 -0700 Subject: [PATCH 038/131] Fix tests --- test/new_tests/test_cdt_select.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 82d9097c97..6c978bc0e3 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -7,6 +7,8 @@ from aerospike_helpers.expressions.arithmetic import Sub from aerospike_helpers import cdt_ctx from aerospike import exception as e +from contextlib import nullcontext +from .test_base_class import TestBaseClass @pytest.mark.usefixtures("as_connection") @@ -119,11 +121,18 @@ def insert_record(self): ] ) def test_cdt_select_basic_functionality(self, op, expected_bins): + if (TestBaseClass.major_ver, TestBaseClass.minor_ver, TestBaseClass.patch_ver) >= (8, 1, 1): + expected_context = nullcontext() + else: + # TODO: expected behavior? + expected_context = pytest.raises(e.InvalidRequest) + ops = [ op ] - _, _, bins = self.as_connection.operate(self.key, ops) - assert bins == expected_bins + with expected_context: + _, _, bins = self.as_connection.operate(self.key, ops) + assert bins == expected_bins def test_cdt_select_with_filter(self): expr = GE( From 0984e905de778f1b3fa723f6a8cc2978a4e4c949 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 16 Oct 2025 21:31:38 -0700 Subject: [PATCH 039/131] Add neg test case where expression VarBuiltIn expects the wrong type --- test/new_tests/test_cdt_select.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 6c978bc0e3..4f3a29fd51 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -3,7 +3,7 @@ import aerospike from aerospike_helpers.operations import operations from aerospike_helpers.expressions.resources import ResultType -from aerospike_helpers.expressions.base import GE, VarBuiltIn +from aerospike_helpers.expressions.base import GE, VarBuiltIn, Eq from aerospike_helpers.expressions.arithmetic import Sub from aerospike_helpers import cdt_ctx from aerospike import exception as e @@ -198,18 +198,33 @@ def test_cdt_select_flags(self, flags, expected_bins): _, _, bins = self.as_connection.operate(self.key, ops) assert bins == expected_bins + # TODO: set default for BUILTIN + EXPR_ON_DIFFERENT_ITERATED_TYPE = Eq(VarBuiltIn(aerospike.EXP_BUILTIN_VALUE, ResultType.STRING), "a").compile() + # TODO: negative case where cdt_select gets a var type not expected @pytest.mark.parametrize( "op, context", [ - ( + pytest.param( operations.cdt_select( name=MAP_BIN_NAME, ctx=[], ), # TODO: vague - pytest.raises(e.AerospikeError) + pytest.raises(e.AerospikeError), + id="empty_ctx" ), + pytest.param( + operations.cdt_select( + name=MAP_BIN_NAME, + ctx=[ + cdt_ctx.cdt_ctx_all(), + cdt_ctx.cdt_ctx_exp(expression=EXPR_ON_DIFFERENT_ITERATED_TYPE) + ] + ), + pytest.raises(e.AerospikeError), + id="iterate_on_unexpected_type" + ) ] ) def test_cdt_select_negative_cases(self, op, context): @@ -218,3 +233,6 @@ def test_cdt_select_negative_cases(self, op, context): ] with context: self.as_connection.operate(self.key, ops) + + def test_cdt_select_no_fail(self): + pass From 3905793c70101251c031729968b53e7fc9e3485d Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 16 Oct 2025 21:34:34 -0700 Subject: [PATCH 040/131] Do cdt select op no fail test --- test/new_tests/test_cdt_select.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 4f3a29fd51..883c111602 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -235,4 +235,16 @@ def test_cdt_select_negative_cases(self, op, context): self.as_connection.operate(self.key, ops) def test_cdt_select_no_fail(self): - pass + ops = [ + operations.cdt_select( + name=self.MAP_BIN_NAME, + ctx=[ + cdt_ctx.cdt_ctx_all(), + cdt_ctx.cdt_ctx_exp(expression=self.EXPR_ON_DIFFERENT_ITERATED_TYPE) + ], + flags=aerospike.CDT_SELECT_NO_FAIL + ), + ] + _, _, bins = self.as_connection.operate(self.key, ops) + + assert bins[self.MAP_BIN_NAME] == [] From 1b548ab214d1cb82ccbf8a2afd0862c94bee40e2 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 17 Oct 2025 08:36:17 -0700 Subject: [PATCH 041/131] fix test --- test/new_tests/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/new_tests/conftest.py b/test/new_tests/conftest.py index 9a210a563c..0eea8f0993 100644 --- a/test/new_tests/conftest.py +++ b/test/new_tests/conftest.py @@ -107,6 +107,7 @@ def as_connection(request) -> aerospike.Client: request.cls.skip_old_server = False TestBaseClass.major_ver = int(versionlist[0]) TestBaseClass.minor_ver = int(versionlist[1]) + TestBaseClass.patch_ver = int(versionlist[2]) request.cls.as_connection = as_client From c1f14dbec2215ca80deb5de528d5eb9396a5e6e3 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 17 Oct 2025 09:18:36 -0700 Subject: [PATCH 042/131] _cdt_ctx.extra_args is a dict. not class instance --- src/main/conversions.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/conversions.c b/src/main/conversions.c index aee88c799a..0aa58b3d8e 100644 --- a/src/main/conversions.c +++ b/src/main/conversions.c @@ -2853,9 +2853,10 @@ as_status get_cdt_ctx(AerospikeClient *self, as_error *err, as_cdt_ctx *cdt_ctx, } else { // TODO: replace with aerospike._CDT_CTX_EXP_EXPR_KEY - PyObject *py_expr = - PyObject_GetAttrString(py_extra_args, AS_EXPR_KEY); - if (!py_expr) { + PyObject *py_expr = NULL; + int retval = PyDict_GetItemStringRef(py_extra_args, AS_EXPR_KEY, + &py_expr); + if (retval != 1) { status = as_error_update(err, AEROSPIKE_ERR_PARAM, "Invalid cdt_ctx_exp"); goto CLEANUP1; From 17b20b1ff4802f2b38e3c724e5ee030b5e7d2e51 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 17 Oct 2025 09:30:27 -0700 Subject: [PATCH 043/131] fix --- src/main/aerospike.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/aerospike.c b/src/main/aerospike.c index 96a5e0bee8..90caea2d3c 100644 --- a/src/main/aerospike.c +++ b/src/main/aerospike.c @@ -545,7 +545,7 @@ static struct module_constant_name_to_value module_constants[] = { {"CDT_SELECT_NO_FAIL", .value.integer = AS_CDT_SELECT_NO_FAIL}, // TODO: move all internal constants used by aerospike_helpers to this loc - {"_CDT_CTX_EXP_EXPR_KEY", .value.string = "expr"}}; + {"_CDT_CTX_EXP_EXPR_KEY", .is_str_value = true, .value.string = "expr"}}; struct submodule_name_to_creation_method { const char *name; From f429500b2d1733829ec7f97b4db3bfc3009bc206 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 17 Oct 2025 09:39:11 -0700 Subject: [PATCH 044/131] cdt_apply op uses 'expr' dictionary key, not 'value' --- src/main/client/operate.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/client/operate.c b/src/main/client/operate.c index 7e8e98cf92..2dd0376ca5 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -602,8 +602,9 @@ as_status add_op(AerospikeClient *self, as_error *err, as_error_update(err, AEROSPIKE_ERR_CLIENT, "Internal error"); goto CLEANUP; } + as_status status = - as_exp_new_from_pyobject(self, value, &mod_exp, err, false); + as_exp_new_from_pyobject(self, py_expr, &mod_exp, err, false); if (status != AEROSPIKE_OK) { goto CLEANUP; } From 106e9e2723ae9a858af9ad2432d06206f55a4ad8 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 17 Oct 2025 14:56:09 -0700 Subject: [PATCH 045/131] Move no_fail flag test to positive test case --- test/new_tests/test_cdt_select.py | 49 +++++++++++++++++++------------ 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 883c111602..feda0e897c 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -64,6 +64,8 @@ def insert_record(self): yield self.as_connection.remove(self.key) + EXPR_ON_DIFFERENT_ITERATED_TYPE = Eq(VarBuiltIn(aerospike.EXP_BUILTIN_VALUE, ResultType.STRING), "a").compile() + @pytest.mark.parametrize( # TODO: ids "op, expected_bins", @@ -117,6 +119,33 @@ def insert_record(self): ] }, id="select_all_children_twice_in_list" + ), + pytest.param( + operations.cdt_select( + name=MAP_BIN_NAME, + ctx=[ + cdt_ctx.cdt_ctx_all(), + cdt_ctx.cdt_ctx_exp(expression=EXPR_ON_DIFFERENT_ITERATED_TYPE) + ], + flags=aerospike.CDT_SELECT_NO_FAIL + ), + { + LIST_BIN_NAME: [ + 1, + { + "aa": 11, + "ab": 13, + "bb": 12 + }, + 2, + 3, + { + "cc": 9 + }, + 4 + ] + }, + id="cdt_select_no_fail" ) ] ) @@ -124,8 +153,8 @@ def test_cdt_select_basic_functionality(self, op, expected_bins): if (TestBaseClass.major_ver, TestBaseClass.minor_ver, TestBaseClass.patch_ver) >= (8, 1, 1): expected_context = nullcontext() else: - # TODO: expected behavior? - expected_context = pytest.raises(e.InvalidRequest) + # InvalidRequest, BinIncompatibleTypes are exceptions that have been raised + expected_context = pytest.raises(e.ServerError) ops = [ op @@ -199,7 +228,6 @@ def test_cdt_select_flags(self, flags, expected_bins): assert bins == expected_bins # TODO: set default for BUILTIN - EXPR_ON_DIFFERENT_ITERATED_TYPE = Eq(VarBuiltIn(aerospike.EXP_BUILTIN_VALUE, ResultType.STRING), "a").compile() # TODO: negative case where cdt_select gets a var type not expected @pytest.mark.parametrize( @@ -233,18 +261,3 @@ def test_cdt_select_negative_cases(self, op, context): ] with context: self.as_connection.operate(self.key, ops) - - def test_cdt_select_no_fail(self): - ops = [ - operations.cdt_select( - name=self.MAP_BIN_NAME, - ctx=[ - cdt_ctx.cdt_ctx_all(), - cdt_ctx.cdt_ctx_exp(expression=self.EXPR_ON_DIFFERENT_ITERATED_TYPE) - ], - flags=aerospike.CDT_SELECT_NO_FAIL - ), - ] - _, _, bins = self.as_connection.operate(self.key, ops) - - assert bins[self.MAP_BIN_NAME] == [] From b9acba8b1ad27a4b3cf5187bebabb0747ad2338b Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 17 Oct 2025 15:04:55 -0700 Subject: [PATCH 046/131] make tests run with expected results for servers less than 8.1.1 --- test/new_tests/test_cdt_select.py | 38 +++++++++++++++++++------------ 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index feda0e897c..1eb1cc0b4d 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -13,6 +13,16 @@ @pytest.mark.usefixtures("as_connection") class TestCDTSelectOperations: + + @pytest.fixture(scope="class") + def setup_class(cls, as_connection): + if (TestBaseClass.major_ver, TestBaseClass.minor_ver, TestBaseClass.patch_ver) >= (8, 1, 1): + cls.expected_context_for_pos_tests = nullcontext() + else: + # InvalidRequest, BinIncompatibleTypes are exceptions that have been raised + cls.expected_context_for_pos_tests = pytest.raises(e.ServerError) + + MAP_BIN_NAME = "map_bin" LIST_BIN_NAME = "list_bin" MAP_OF_NESTED_MAPS_BIN_NAME = "map_of_maps_bin" @@ -150,16 +160,10 @@ def insert_record(self): ] ) def test_cdt_select_basic_functionality(self, op, expected_bins): - if (TestBaseClass.major_ver, TestBaseClass.minor_ver, TestBaseClass.patch_ver) >= (8, 1, 1): - expected_context = nullcontext() - else: - # InvalidRequest, BinIncompatibleTypes are exceptions that have been raised - expected_context = pytest.raises(e.ServerError) - ops = [ op ] - with expected_context: + with self.expected_context_for_pos_tests: _, _, bins = self.as_connection.operate(self.key, ops) assert bins == expected_bins @@ -177,11 +181,12 @@ def test_cdt_select_with_filter(self): ] ) ] - _, _, bins = self.as_connection.operate(self.key, ops) + with self.expected_context_for_pos_tests: + _, _, bins = self.as_connection.operate(self.key, ops) - assert bins[self.MAP_OF_NESTED_MAPS_BIN_NAME] == [ - self.BINS_FOR_CDT_SELECT_TEST[self.MAP_OF_NESTED_MAPS_BIN_NAME]["Day2"]["food"] - ] + assert bins[self.MAP_OF_NESTED_MAPS_BIN_NAME] == [ + self.BINS_FOR_CDT_SELECT_TEST[self.MAP_OF_NESTED_MAPS_BIN_NAME]["Day2"]["food"] + ] def test_cdt_modify(self): mod_expr = Sub(VarBuiltIn(aerospike.EXP_BUILTIN_VALUE, ResultType.INTEGER), 5).compile() @@ -202,8 +207,10 @@ def test_cdt_modify(self): ] ), ] - _, _, bins = self.as_connection.operate(self.key, ops) - assert bins[self.MAP_OF_NESTED_MAPS_BIN_NAME] == [14.990000, 5.0000, 34.000000, 12.990000, 19.990000, 2.000000] + with self.expected_context_for_pos_tests: + _, _, bins = self.as_connection.operate(self.key, ops) + + assert bins[self.MAP_OF_NESTED_MAPS_BIN_NAME] == [14.990000, 5.0000, 34.000000, 12.990000, 19.990000, 2.000000] @pytest.mark.parametrize( @@ -224,8 +231,9 @@ def test_cdt_select_flags(self, flags, expected_bins): flags=flags ) ] - _, _, bins = self.as_connection.operate(self.key, ops) - assert bins == expected_bins + with self.expected_context_for_pos_tests: + _, _, bins = self.as_connection.operate(self.key, ops) + assert bins == expected_bins # TODO: set default for BUILTIN From 231a4a516936e5355564eb2ecd4e9d9436644276 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 17 Oct 2025 15:07:30 -0700 Subject: [PATCH 047/131] fix test --- test/new_tests/test_cdt_select.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 1eb1cc0b4d..38bc34c5d0 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -140,20 +140,7 @@ def insert_record(self): flags=aerospike.CDT_SELECT_NO_FAIL ), { - LIST_BIN_NAME: [ - 1, - { - "aa": 11, - "ab": 13, - "bb": 12 - }, - 2, - 3, - { - "cc": 9 - }, - 4 - ] + MAP_BIN_NAME: [] }, id="cdt_select_no_fail" ) From 5152d91d08adc2f9678264e7c425efc5af1b602f Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 17 Oct 2025 15:30:11 -0700 Subject: [PATCH 048/131] fixture probably not being used --- test/new_tests/test_cdt_select.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 38bc34c5d0..73b63dc4e3 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -14,7 +14,7 @@ @pytest.mark.usefixtures("as_connection") class TestCDTSelectOperations: - @pytest.fixture(scope="class") + @pytest.fixture(scope="class", autouse=True) def setup_class(cls, as_connection): if (TestBaseClass.major_ver, TestBaseClass.minor_ver, TestBaseClass.patch_ver) >= (8, 1, 1): cls.expected_context_for_pos_tests = nullcontext() From 07b493f5454abb43f52984a941b1ea0e8643c7a6 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 20 Oct 2025 11:30:19 -0700 Subject: [PATCH 049/131] todo. wip --- aerospike_helpers/expressions/base.py | 31 +++++++++++++++++----- aerospike_helpers/expressions/resources.py | 2 ++ src/main/convert_expressions.c | 4 +++ 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/aerospike_helpers/expressions/base.py b/aerospike_helpers/expressions/base.py index 56e9406b7d..b1a6f51eba 100644 --- a/aerospike_helpers/expressions/base.py +++ b/aerospike_helpers/expressions/base.py @@ -1105,9 +1105,18 @@ def __init__(self, ctx: _cdt_ctx, return_type: ResultType, flags: int, bin: str) :return: TODO """ - self._fixed = {_Keys.BIN_KEY: bin} - # TODO: needs to be an expr? - self._children = (flags) + ''' + ''' + self._fixed = { + _Keys.RETURN_TYPE_KEY: return_type, + _Keys.CTX_KEY: ctx, + _Keys.CDT_SELECT_FLAGS_KEY: flags, + _Keys.BIN_KEY: bin + } + + # TODO + # if ctx is not None: + # self._fixed[_Keys.CTX_KEY] = ctx class CDTApply(_BaseExpr): @@ -1118,12 +1127,20 @@ class CDTApply(_BaseExpr): # TODO: document to be certain constants? # TODO: why return type needed? this returns the whole bin after being modified? - def __init__(self, ctx: _cdt_ctx, return_type: ResultType, flags: int, bin: str): + def __init__(self, ctx: _cdt_ctx, return_type: ResultType, mod_exp, flags: int, bin: str): """Args: `ctx`: TODO :return: TODO """ - self._fixed = {_Keys.BIN_KEY: bin} - # TODO: needs to be an expr? - self._children = (flags) + self._fixed = { + _Keys.RETURN_TYPE_KEY: return_type, + _Keys.CTX_KEY: ctx, + _Keys.CDT_SELECT_FLAGS_KEY: flags, + _Keys.BIN_KEY: bin, + _Keys.CDT_SELECT_MOD_EXP_KEY: mod_exp + } + + # TODO + # if ctx is not None: + # self._fixed[_Keys.CTX_KEY] = ctx diff --git a/aerospike_helpers/expressions/resources.py b/aerospike_helpers/expressions/resources.py index 46448eab97..1dd10a0fa3 100644 --- a/aerospike_helpers/expressions/resources.py +++ b/aerospike_helpers/expressions/resources.py @@ -17,6 +17,8 @@ class _Keys: MAP_POLICY_KEY = "map_policy" LIST_ORDER_KEY = "list_order" REGEX_OPTIONS_KEY = "regex_options" + CDT_SELECT_FLAGS_KEY = "flags" + CDT_SELECT_MOD_EXP_KEY = "mod_exp" class _ExprOp: # TODO replace this with an enum diff --git a/src/main/convert_expressions.c b/src/main/convert_expressions.c index 0b0de9c1c9..22ec9162a2 100644 --- a/src/main/convert_expressions.c +++ b/src/main/convert_expressions.c @@ -207,6 +207,8 @@ static as_status get_expr_size(int *size_to_alloc, int *intermediate_exprs_size, { static const int EXPR_SIZES[] = { + // TODO: can also be cdt_apply() + [_AS_EXP_CODE_CALL] = EXP_SZ(as_exp_cdt_select(NULL, 0, 0, NIL)), [BIN] = EXP_SZ(as_exp_bin_int(0)), [_AS_EXP_CODE_AS_VAL] = EXP_SZ(as_exp_val(NULL)), // TODO: have generic var_builtin? @@ -1617,6 +1619,8 @@ add_expr_macros(AerospikeClient *self, as_static_pool *static_pool, case UNKNOWN: APPEND_ARRAY(0, as_exp_unknown()); break; + case _AS_EXP_CODE_CALL: + break; default: return as_error_update(err, AEROSPIKE_ERR_PARAM, "Unrecognised expression op type."); From ad96ee632cc8371104ef5a417e92f1cb71c68663 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 20 Oct 2025 17:01:20 -0700 Subject: [PATCH 050/131] fix --- aerospike_helpers/expressions/base.py | 4 +--- src/main/convert_expressions.c | 26 ++++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/aerospike_helpers/expressions/base.py b/aerospike_helpers/expressions/base.py index b1a6f51eba..4aa517e2e7 100644 --- a/aerospike_helpers/expressions/base.py +++ b/aerospike_helpers/expressions/base.py @@ -1086,9 +1086,7 @@ def __init__(self, var_id: int, var_type: ResultType): :return: (value stored in variable) """ - self._fixed = {_Keys.VALUE_KEY: var_id} - # TODO: needs to be an expr? - self._children = (var_id, var_type) + self._fixed = {_Keys.VALUE_KEY: var_id, _Keys.VALUE_TYPE_KEY: var_type} class CDTSelect(_BaseExpr): diff --git a/src/main/convert_expressions.c b/src/main/convert_expressions.c index 22ec9162a2..519666710d 100644 --- a/src/main/convert_expressions.c +++ b/src/main/convert_expressions.c @@ -644,8 +644,30 @@ add_expr_macros(AerospikeClient *self, as_static_pool *static_pool, APPEND_ARRAY(0, BIN_EXPR()); break; case _AS_EXP_CODE_VAR_BUILTIN: - // TODO: Not done - APPEND_ARRAY(0, as_exp_var_builtin_float(0)); + // TODO: replace with new expr from Sam + if (get_int64_t(err, AS_PY_VAL_KEY, temp_expr->pydict, &lval1) != + AEROSPIKE_OK) { + return err->code; + } + + switch (lval1) { + case AS_EXP_TYPE_MAP: + APPEND_ARRAY(2, as_exp_var_builtin_map(0)); + break; + case AS_EXP_TYPE_LIST: + APPEND_ARRAY(2, as_exp_var_builtin_list(0)); + break; + case AS_EXP_TYPE_STR: + APPEND_ARRAY(2, as_exp_var_builtin_str(0)); + break; + case AS_EXP_TYPE_INT: + APPEND_ARRAY(2, as_exp_var_builtin_int(0)); + break; + case AS_EXP_TYPE_FLOAT: + APPEND_ARRAY(2, as_exp_var_builtin_float(0)); + break; + } + break; case VAL: case _AS_EXP_CODE_AS_VAL:; From 931094bcc75ccca7a1b6ed8b43e87aecaaec5b9b Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 20 Oct 2025 17:24:04 -0700 Subject: [PATCH 051/131] finish --- aerospike_helpers/expressions/base.py | 4 +- aerospike_helpers/expressions/resources.py | 4 +- src/main/convert_expressions.c | 43 ++++++++++++++++++++-- 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/aerospike_helpers/expressions/base.py b/aerospike_helpers/expressions/base.py index 4aa517e2e7..dd2e8f2478 100644 --- a/aerospike_helpers/expressions/base.py +++ b/aerospike_helpers/expressions/base.py @@ -1092,7 +1092,7 @@ def __init__(self, var_id: int, var_type: ResultType): class CDTSelect(_BaseExpr): """ """ - _op = _ExprOp._AS_EXP_CODE_CALL + _op = _ExprOp._AS_EXP_CODE_CALL_SELECT # TODO: document to be certain constants? # TODO: result_type not needed? @@ -1121,7 +1121,7 @@ class CDTApply(_BaseExpr): """ asdf """ - _op = _ExprOp._AS_EXP_CODE_CALL + _op = _ExprOp._AS_EXP_CODE_CALL_APPLY # TODO: document to be certain constants? # TODO: why return type needed? this returns the whole bin after being modified? diff --git a/aerospike_helpers/expressions/resources.py b/aerospike_helpers/expressions/resources.py index 1dd10a0fa3..71236a5b37 100644 --- a/aerospike_helpers/expressions/resources.py +++ b/aerospike_helpers/expressions/resources.py @@ -90,8 +90,10 @@ class _ExprOp: # TODO replace this with an enum LET = 125 DEF = 126 - _AS_EXP_CODE_CALL = 127 + _AS_EXP_CODE_CALL_SELECT = 127 _AS_EXP_CODE_AS_VAL = 128 + _AS_EXP_CODE_CALL_APPLY = 129 + # virtual ops _AS_EXP_CODE_CALL_VOP_START = 139 _AS_EXP_CODE_CDT_LIST_CRMOD = 140 diff --git a/src/main/convert_expressions.c b/src/main/convert_expressions.c index 519666710d..9f4e168d1e 100644 --- a/src/main/convert_expressions.c +++ b/src/main/convert_expressions.c @@ -100,8 +100,9 @@ enum expr_ops { VAR = 124, LET = 125, DEF = 126, + AS_EXP_CODE_CALL_SELECT = 127, + AS_EXP_CODE_CALL_APPLY = 129, - CALL = 127, LIST_MOD = 139, VAL = 200 }; @@ -208,7 +209,9 @@ static as_status get_expr_size(int *size_to_alloc, int *intermediate_exprs_size, static const int EXPR_SIZES[] = { // TODO: can also be cdt_apply() - [_AS_EXP_CODE_CALL] = EXP_SZ(as_exp_cdt_select(NULL, 0, 0, NIL)), + [AS_EXP_CODE_CALL_SELECT] = EXP_SZ(as_exp_cdt_select(NULL, 0, 0, NIL)), + [AS_EXP_CODE_CALL_APPLY] = + EXP_SZ(as_exp_cdt_apply(NULL, 0, NULL, 0, NIL)), [BIN] = EXP_SZ(as_exp_bin_int(0)), [_AS_EXP_CODE_AS_VAL] = EXP_SZ(as_exp_val(NULL)), // TODO: have generic var_builtin? @@ -1641,7 +1644,41 @@ add_expr_macros(AerospikeClient *self, as_static_pool *static_pool, case UNKNOWN: APPEND_ARRAY(0, as_exp_unknown()); break; - case _AS_EXP_CODE_CALL: + case AS_EXP_CODE_CALL_SELECT: + case AS_EXP_CODE_CALL_APPLY: + if (get_int64_t(err, "return_type", temp_expr->pydict, &lval1) != + AEROSPIKE_OK) { + return err->code; + } + if (get_int64_t(err, "flags", temp_expr->pydict, &lval2) != + AEROSPIKE_OK) { + return err->code; + } + if (get_bin(err, temp_expr->pydict, unicodeStrVector, &bin_name) != + AEROSPIKE_OK) { + return err->code; + } + PyObject *py_mod_exp = + PyDict_GetItemString(temp_expr->pydict, "mod_exp"); + if (!py_mod_exp) { + return as_error_update( + err, AEROSPIKE_ERR_PARAM, + "mod_exp is required for cdt_apply() expression."); + } + + if (temp_expr->op == AS_EXP_CODE_CALL_APPLY) { + as_exp *mod_exp = NULL; + if (as_exp_new_from_pyobject(self, py_mod_exp, &mod_exp, err, + false) != AEROSPIKE_OK) { + return err->code; + } + APPEND_ARRAY(0, as_exp_cdt_apply(temp_expr->ctx, lval1, mod_exp, + lval2, bin_name)); + } + else { + APPEND_ARRAY(0, as_exp_cdt_select(temp_expr->ctx, lval1, lval2, + bin_name)); + } break; default: return as_error_update(err, AEROSPIKE_ERR_PARAM, From 18f8f73249196a78ec9591e5392228be4469f2d6 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 20 Oct 2025 17:30:51 -0700 Subject: [PATCH 052/131] Bin name must be an expr in c --- src/main/convert_expressions.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/convert_expressions.c b/src/main/convert_expressions.c index 9f4e168d1e..be8aaea1e7 100644 --- a/src/main/convert_expressions.c +++ b/src/main/convert_expressions.c @@ -1673,11 +1673,11 @@ add_expr_macros(AerospikeClient *self, as_static_pool *static_pool, return err->code; } APPEND_ARRAY(0, as_exp_cdt_apply(temp_expr->ctx, lval1, mod_exp, - lval2, bin_name)); + lval2, BIN_EXPR())); } else { APPEND_ARRAY(0, as_exp_cdt_select(temp_expr->ctx, lval1, lval2, - bin_name)); + BIN_EXPR())); } break; default: From 2a830c7d9770fe65bdf1bb0e2d9020d5dc72e7c3 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 20 Oct 2025 17:53:50 -0700 Subject: [PATCH 053/131] fix test --- test/new_tests/test_cdt_select.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 73b63dc4e3..96c681725a 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -15,12 +15,12 @@ class TestCDTSelectOperations: @pytest.fixture(scope="class", autouse=True) - def setup_class(cls, as_connection): + def setup_class(as_connection, request): if (TestBaseClass.major_ver, TestBaseClass.minor_ver, TestBaseClass.patch_ver) >= (8, 1, 1): - cls.expected_context_for_pos_tests = nullcontext() + request.cls.expected_context_for_pos_tests = nullcontext() else: # InvalidRequest, BinIncompatibleTypes are exceptions that have been raised - cls.expected_context_for_pos_tests = pytest.raises(e.ServerError) + request.cls.expected_context_for_pos_tests = pytest.raises(e.ServerError) MAP_BIN_NAME = "map_bin" From 2bb0625a74cb470b024f1fce2e2b046fae6248a2 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 20 Oct 2025 18:29:39 -0700 Subject: [PATCH 054/131] omit for now so test can run --- test/new_tests/test_cdt_select.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 96c681725a..dbcfd27071 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -78,7 +78,7 @@ def insert_record(self): @pytest.mark.parametrize( # TODO: ids - "op, expected_bins", + "op,expected_bins", [ pytest.param( operations.cdt_select( @@ -211,7 +211,7 @@ def test_cdt_modify(self): aerospike.CDT_SELECT_NO_FAIL ] ) - def test_cdt_select_flags(self, flags, expected_bins): + def test_cdt_select_flags(self, flags): ops = [ operations.cdt_select( # TODO: not done @@ -220,7 +220,7 @@ def test_cdt_select_flags(self, flags, expected_bins): ] with self.expected_context_for_pos_tests: _, _, bins = self.as_connection.operate(self.key, ops) - assert bins == expected_bins + # assert bins == expected_bins # TODO: set default for BUILTIN From 26b91f51af5fe6c2ba5443b2eb3cb5714fab0646 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 20 Oct 2025 18:53:32 -0700 Subject: [PATCH 055/131] wip on flag test. suspect that default CDT_SELECT_TREE will yield wrong results --- test/new_tests/test_cdt_select.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index dbcfd27071..b072225a75 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -201,26 +201,30 @@ def test_cdt_modify(self): @pytest.mark.parametrize( - "flags", [ - aerospike.CDT_SELECT_TREE, + "flags, expected_bins", [ + (aerospike.CDT_SELECT_TREE, {}) # TODO: combine? - aerospike.CDT_SELECT_LEAF_LIST_VALUE, + # (aerospike.CDT_SELECT_LEAF_LIST_VALUE,) # TODO: bad naming? - aerospike.CDT_SELECT_LEAF_MAP_VALUE, - aerospike.CDT_SELECT_LEAF_MAP_KEY, - aerospike.CDT_SELECT_NO_FAIL + # (aerospike.CDT_SELECT_LEAF_MAP_VALUE,) + # (aerospike.CDT_SELECT_LEAF_MAP_KEY,) ] ) - def test_cdt_select_flags(self, flags): + def test_cdt_select_flags(self, flags, expected_bins): ops = [ operations.cdt_select( + name=self.LIST_BIN_NAME, + ctx=[ + cdt_ctx.cdt_ctx_all(), + cdt_ctx.cdt_ctx_all() + ], # TODO: not done flags=flags ) ] with self.expected_context_for_pos_tests: _, _, bins = self.as_connection.operate(self.key, ops) - # assert bins == expected_bins + assert bins == expected_bins # TODO: set default for BUILTIN From 78ba26dc4422b0d468a88309512cbac8550e7ed6 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 21 Oct 2025 09:20:31 -0700 Subject: [PATCH 056/131] Change default cdt select flag for cdt_select and apply operations to match first code examples in PRD --- aerospike_helpers/operations/operations.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aerospike_helpers/operations/operations.py b/aerospike_helpers/operations/operations.py index a2c55ddc95..ae5413eb6b 100644 --- a/aerospike_helpers/operations/operations.py +++ b/aerospike_helpers/operations/operations.py @@ -135,7 +135,7 @@ def touch(ttl: Optional[int] = None): return op_dict -def cdt_select(name: str, ctx: list, flags: int = aerospike.CDT_SELECT_TREE): +def cdt_select(name: str, ctx: list, flags: int = aerospike.CDT_SELECT_LEAF_MAP_VALUE): """ Create CDT select operation. @@ -146,7 +146,7 @@ def cdt_select(name: str, ctx: list, flags: int = aerospike.CDT_SELECT_TREE): return op_dict -def cdt_apply(name: str, ctx: list, expr: TypeExpression, flags: int = aerospike.CDT_SELECT_TREE): +def cdt_apply(name: str, ctx: list, expr: TypeExpression, flags: int = aerospike.CDT_SELECT_LEAF_MAP_VALUE): """ Create CDT apply operation. From 175284f75d5d9dd1e1d40705a63e27b5931ff1db Mon Sep 17 00:00:00 2001 From: juliannguyen4 <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 21 Oct 2025 16:42:27 +0000 Subject: [PATCH 057/131] Fix test --- test/new_tests/test_cdt_select.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index b072225a75..90fb4d21f4 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -11,18 +11,16 @@ from .test_base_class import TestBaseClass -@pytest.mark.usefixtures("as_connection") -class TestCDTSelectOperations: - - @pytest.fixture(scope="class", autouse=True) - def setup_class(as_connection, request): - if (TestBaseClass.major_ver, TestBaseClass.minor_ver, TestBaseClass.patch_ver) >= (8, 1, 1): - request.cls.expected_context_for_pos_tests = nullcontext() - else: - # InvalidRequest, BinIncompatibleTypes are exceptions that have been raised - request.cls.expected_context_for_pos_tests = pytest.raises(e.ServerError) +@pytest.fixture(scope="class", autouse=True) +def setup_class(as_connection, request): + if (TestBaseClass.major_ver, TestBaseClass.minor_ver, TestBaseClass.patch_ver) >= (8, 1, 1): + request.cls.expected_context_for_pos_tests = nullcontext() + else: + # InvalidRequest, BinIncompatibleTypes are exceptions that have been raised + request.cls.expected_context_for_pos_tests = pytest.raises(e.ServerError) +class TestCDTSelectOperations: MAP_BIN_NAME = "map_bin" LIST_BIN_NAME = "list_bin" MAP_OF_NESTED_MAPS_BIN_NAME = "map_of_maps_bin" From a23fb011f618e85fe058817b6bf1e84a937ec5af Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 21 Oct 2025 11:59:28 -0700 Subject: [PATCH 058/131] fix test --- test/new_tests/test_cdt_select.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 90fb4d21f4..3a809e519e 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -98,7 +98,7 @@ def insert_record(self): ] ), { - MAP_BIN_NAME: BINS_FOR_CDT_SELECT_TEST[MAP_BIN_NAME] + MAP_BIN_NAME: list(BINS_FOR_CDT_SELECT_TEST[MAP_BIN_NAME].values()) }, id="select_all_children_once_in_map" ), From 6ff5e3df1f06355ef30e225a116111a6ea44f843 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 21 Oct 2025 12:16:12 -0700 Subject: [PATCH 059/131] fix bug --- src/main/client/operate.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/client/operate.c b/src/main/client/operate.c index 2dd0376ca5..9fbdfce7a6 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -429,7 +429,7 @@ as_status add_op(AerospikeClient *self, as_error *err, py_persist_index = value; } // Use set instead of this? - else if (strcmp(name, "expr") == 0) { + else if (strcmp(name, "mod_exp") == 0) { continue; } else if (strcmp(name, "flags") == 0) { @@ -592,7 +592,7 @@ as_status add_op(AerospikeClient *self, as_error *err, else if (operation == AS_OPERATOR_CDT_MODIFY) { PyObject *py_expr = NULL; int retval = - PyDict_GetItemStringRef(py_operation_dict, "expr", &py_expr); + PyDict_GetItemStringRef(py_operation_dict, "mod_exp", &py_expr); if (retval == 0) { as_error_update(err, AEROSPIKE_ERR_PARAM, "CDT operation is missing a flags argument"); From 453eae8a8725c80f00dca4fa9b6534d05dd569ec Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 21 Oct 2025 12:18:34 -0700 Subject: [PATCH 060/131] real fix --- aerospike_helpers/operations/operations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aerospike_helpers/operations/operations.py b/aerospike_helpers/operations/operations.py index ae5413eb6b..648957ac15 100644 --- a/aerospike_helpers/operations/operations.py +++ b/aerospike_helpers/operations/operations.py @@ -153,5 +153,5 @@ def cdt_apply(name: str, ctx: list, expr: TypeExpression, flags: int = aerospike Returns: A dictionary to be passed to operate or operate_ordered. """ - op_dict = {"op": aerospike._AS_OPERATOR_CDT_MODIFY, "bin": name, "ctx": ctx, "expr": expr, "flags": flags} + op_dict = {"op": aerospike._AS_OPERATOR_CDT_MODIFY, "bin": name, "ctx": ctx, "mod_exp": expr, "flags": flags} return op_dict From 026bb813c403d80665fd99dbdef62856411b7db6 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 21 Oct 2025 12:23:35 -0700 Subject: [PATCH 061/131] APPEND_ARRAY for builtin expression needs to include the 2 arguments from macro expansion since these 2 arguments are not represented as separate children expressions under VarBuiltIn --- src/main/convert_expressions.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/convert_expressions.c b/src/main/convert_expressions.c index be8aaea1e7..0ee1479da6 100644 --- a/src/main/convert_expressions.c +++ b/src/main/convert_expressions.c @@ -655,19 +655,19 @@ add_expr_macros(AerospikeClient *self, as_static_pool *static_pool, switch (lval1) { case AS_EXP_TYPE_MAP: - APPEND_ARRAY(2, as_exp_var_builtin_map(0)); + APPEND_ARRAY(0, as_exp_var_builtin_map(lval1)); break; case AS_EXP_TYPE_LIST: - APPEND_ARRAY(2, as_exp_var_builtin_list(0)); + APPEND_ARRAY(0, as_exp_var_builtin_list(lval1)); break; case AS_EXP_TYPE_STR: - APPEND_ARRAY(2, as_exp_var_builtin_str(0)); + APPEND_ARRAY(0, as_exp_var_builtin_str(lval1)); break; case AS_EXP_TYPE_INT: - APPEND_ARRAY(2, as_exp_var_builtin_int(0)); + APPEND_ARRAY(0, as_exp_var_builtin_int(lval1)); break; case AS_EXP_TYPE_FLOAT: - APPEND_ARRAY(2, as_exp_var_builtin_float(0)); + APPEND_ARRAY(0, as_exp_var_builtin_float(lval1)); break; } From ba1d5a20cb530a7e273f6d8e758d683763658834 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 21 Oct 2025 12:36:20 -0700 Subject: [PATCH 062/131] fix --- src/main/convert_expressions.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/convert_expressions.c b/src/main/convert_expressions.c index 0ee1479da6..e4308fa037 100644 --- a/src/main/convert_expressions.c +++ b/src/main/convert_expressions.c @@ -648,7 +648,7 @@ add_expr_macros(AerospikeClient *self, as_static_pool *static_pool, break; case _AS_EXP_CODE_VAR_BUILTIN: // TODO: replace with new expr from Sam - if (get_int64_t(err, AS_PY_VAL_KEY, temp_expr->pydict, &lval1) != + if (get_int64_t(err, "value_type", temp_expr->pydict, &lval1) != AEROSPIKE_OK) { return err->code; } From bedb5940a5fdc10040fa5360d09adb5a08b18817 Mon Sep 17 00:00:00 2001 From: juliannguyen4 <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 21 Oct 2025 19:44:15 +0000 Subject: [PATCH 063/131] Revert "fix" This reverts commit ba1d5a20cb530a7e273f6d8e758d683763658834. --- src/main/convert_expressions.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/convert_expressions.c b/src/main/convert_expressions.c index e4308fa037..0ee1479da6 100644 --- a/src/main/convert_expressions.c +++ b/src/main/convert_expressions.c @@ -648,7 +648,7 @@ add_expr_macros(AerospikeClient *self, as_static_pool *static_pool, break; case _AS_EXP_CODE_VAR_BUILTIN: // TODO: replace with new expr from Sam - if (get_int64_t(err, "value_type", temp_expr->pydict, &lval1) != + if (get_int64_t(err, AS_PY_VAL_KEY, temp_expr->pydict, &lval1) != AEROSPIKE_OK) { return err->code; } From 50b5907863f9ed43cd30419c0bf370f2993b39df Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 21 Oct 2025 12:48:23 -0700 Subject: [PATCH 064/131] proper fix --- src/main/convert_expressions.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/convert_expressions.c b/src/main/convert_expressions.c index 0ee1479da6..2d5f17d6c9 100644 --- a/src/main/convert_expressions.c +++ b/src/main/convert_expressions.c @@ -652,8 +652,12 @@ add_expr_macros(AerospikeClient *self, as_static_pool *static_pool, AEROSPIKE_OK) { return err->code; } + if (get_int64_t(err, "value_type", temp_expr->pydict, &lval2) != + AEROSPIKE_OK) { + return err->code; + } - switch (lval1) { + switch (lval2) { case AS_EXP_TYPE_MAP: APPEND_ARRAY(0, as_exp_var_builtin_map(lval1)); break; From c4995baeb092a2ce5d0f1aabc1ee8a1a0e2cb16e Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 21 Oct 2025 13:19:31 -0700 Subject: [PATCH 065/131] in test, expression subtracts an int from iterated value and expects latter to be an int. but it is actually a float --- test/new_tests/test_cdt_select.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 3a809e519e..2e6a6d9290 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -174,7 +174,7 @@ def test_cdt_select_with_filter(self): ] def test_cdt_modify(self): - mod_expr = Sub(VarBuiltIn(aerospike.EXP_BUILTIN_VALUE, ResultType.INTEGER), 5).compile() + mod_expr = Sub(VarBuiltIn(aerospike.EXP_BUILTIN_VALUE, ResultType.FLOAT), 5.0).compile() ops = [ operations.cdt_apply( name=self.MAP_OF_NESTED_MAPS_BIN_NAME, From a38035fc398afcbb3024a0c93e864ab1882fe92f Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 10:17:48 -0700 Subject: [PATCH 066/131] Cleanup WIP. will break --- aerospike-client-c | 2 +- aerospike_helpers/expressions/base.py | 34 +++++++----- aerospike_helpers/expressions/resources.py | 2 - src/include/policy.h | 8 +++ src/main/aerospike.c | 62 +++++++++++++++------- src/main/convert_expressions.c | 47 ++++++++-------- 6 files changed, 100 insertions(+), 55 deletions(-) diff --git a/aerospike-client-c b/aerospike-client-c index b558b27c13..547c2f3c16 160000 --- a/aerospike-client-c +++ b/aerospike-client-c @@ -1 +1 @@ -Subproject commit b558b27c139db5ddb3a8d8746e0292f6d3dff4cd +Subproject commit 547c2f3c164d73aa51fdf304193ffbaeb6ef055a diff --git a/aerospike_helpers/expressions/base.py b/aerospike_helpers/expressions/base.py index dd2e8f2478..6e1ece566e 100644 --- a/aerospike_helpers/expressions/base.py +++ b/aerospike_helpers/expressions/base.py @@ -33,6 +33,7 @@ from aerospike_helpers.expressions.resources import ResultType from aerospike_helpers.expressions.resources import _Keys from aerospike_helpers.cdt_ctx import _cdt_ctx +from abc import ABC TypeComparisonArg = Union[_BaseExpr, Any] TypeGeo = Union[_BaseExpr, aerospike.GeoJSON] @@ -1072,21 +1073,28 @@ def __init__(self, var_name: str): self._fixed = {_Keys.VALUE_KEY: var_name} -# TODO: forbid unsupported types -class VarBuiltIn(_BaseExpr): - """ - Retrieve expression value from a built-in variable. - """ - _op = _ExprOp._AS_EXP_CODE_VAR_BUILTIN +class _LoopVar(_BaseExpr, ABC): + def __init__(self, var_id: int): + self._fixed = {_Keys.VALUE_KEY: var_id} - # TODO: document to be certain constants? - def __init__(self, var_id: int, var_type: ResultType): - """Args: - `var_id` (int): Variable id. +class LoopVarMap(_LoopVar): + _op = aerospike._AS_EXP_LOOPVAR_MAP - :return: (value stored in variable) - """ - self._fixed = {_Keys.VALUE_KEY: var_id, _Keys.VALUE_TYPE_KEY: var_type} + +class LoopVarList(_LoopVar): + _op = aerospike._AS_EXP_LOOPVAR_LIST + + +class LoopVarStr(_LoopVar): + _op = aerospike._AS_EXP_LOOPVAR_STR + + +class LoopVarFloat(_LoopVar): + _op = aerospike._AS_EXP_LOOPVAR_FLOAT + + +class LoopVarInt(_LoopVar): + _op = aerospike._AS_EXP_LOOPVAR_INT class CDTSelect(_BaseExpr): diff --git a/aerospike_helpers/expressions/resources.py b/aerospike_helpers/expressions/resources.py index 71236a5b37..7206f27e02 100644 --- a/aerospike_helpers/expressions/resources.py +++ b/aerospike_helpers/expressions/resources.py @@ -83,8 +83,6 @@ class _ExprOp: # TODO replace this with an enum BIN_TYPE = 82 BIN_EXISTS = 83 - _AS_EXP_CODE_VAR_BUILTIN = 122 - COND = 123 VAR = 124 LET = 125 diff --git a/src/include/policy.h b/src/include/policy.h index 9a6ffa125a..b0598bb51f 100644 --- a/src/include/policy.h +++ b/src/include/policy.h @@ -172,6 +172,14 @@ enum aerospike_hll_operations { enum aerospike_expression_operations { OP_EXPR_READ = 2200, OP_EXPR_WRITE }; +enum { + _AS_EXP_LOOPVAR_FLOAT = 3000, + _AS_EXP_LOOPVAR_INT, + _AS_EXP_LOOPVAR_LIST, + _AS_EXP_LOOPVAR_MAP, + _AS_EXP_LOOPVAR_STR +}; + enum aerospike_regex_constants { REGEX_NONE = 0, REGEX_EXTENDED, diff --git a/src/main/aerospike.c b/src/main/aerospike.c index 90caea2d3c..6f898c1218 100644 --- a/src/main/aerospike.c +++ b/src/main/aerospike.c @@ -34,6 +34,7 @@ #include "cdt_types.h" #include "transaction.h" #include "config_provider.h" +#include "convert_expressions.h" #include #include @@ -102,6 +103,26 @@ struct module_constant_name_to_value { } value; }; +#define EXPOSE_AS_MACRO_WITHOUT_AS_PREFIX_AS_PUBLIC_FIELD( \ + macro_name_without_prefix) \ + { \ + #macro_name_without_prefix, \ + .value.integer = AS_##macro_name_without_prefix \ + } + +#define STRINGIFY(X) #X + +#define EXPOSE_AS_MACRO_AS_PRIVATE_FIELD(macro_name_without_prefix) \ + { \ + STRINGIFY(_##macro_name_without_prefix), \ + .value.integer = AS_##macro_name_without_prefix \ + } + +#define EXPOSE_MACRO(macro_name) \ + { \ + #macro_name, .value.integer = macro_name \ + } + // TODO: many of these names are the same as the enum name // Is there a way to generate this code? // TODO: regression tests for all these constants @@ -113,8 +134,8 @@ static struct module_constant_name_to_value module_constants[] = { {"OPERATOR_PREPEND", .value.integer = AS_OPERATOR_PREPEND}, {"OPERATOR_TOUCH", .value.integer = AS_OPERATOR_TOUCH}, {"OPERATOR_DELETE", .value.integer = AS_OPERATOR_DELETE}, - {"_AS_OPERATOR_CDT_READ", .value.integer = AS_OPERATOR_CDT_READ}, - {"_AS_OPERATOR_CDT_MODIFY", .value.integer = AS_OPERATOR_CDT_MODIFY}, + EXPOSE_AS_MACRO_AS_PRIVATE_FIELD(OPERATOR_CDT_READ), + EXPOSE_AS_MACRO_AS_PRIVATE_FIELD(OPERATOR_CDT_MODIFY), {"AUTH_INTERNAL", .value.integer = AS_AUTH_INTERNAL}, {"AUTH_EXTERNAL", .value.integer = AS_AUTH_EXTERNAL}, @@ -435,15 +456,6 @@ static struct module_constant_name_to_value module_constants[] = { {"CDT_CTX_MAP_KEY_CREATE", .value.integer = CDT_CTX_MAP_KEY_CREATE}, {"_CDT_CTX_EXP", .value.integer = AS_CDT_CTX_EXP}, - /* - When doing a cdt select/apply operation, and applying an expression on each - iterated object, this lets us choose a specific value over each iterated - object. - */ - {"EXP_BUILTIN_KEY", .value.integer = AS_EXP_BUILTIN_KEY}, - {"EXP_BUILTIN_VALUE", .value.integer = AS_EXP_BUILTIN_VALUE}, - {"EXP_BUILTIN_INDEX", .value.integer = AS_EXP_BUILTIN_INDEX}, - /* HLL constants 3.11.0 */ {"OP_HLL_ADD", .value.integer = OP_HLL_ADD}, {"OP_HLL_DESCRIBE", .value.integer = OP_HLL_DESCRIBE}, @@ -536,13 +548,27 @@ static struct module_constant_name_to_value module_constants[] = { {"JOB_SCAN", .is_str_value = true, .value.string = "scan"}, {"JOB_QUERY", .is_str_value = true, .value.string = "query"}, - {"CDT_SELECT_TREE", .value.integer = AS_CDT_SELECT_TREE}, - {"CDT_SELECT_LEAF_LIST_VALUE", - .value.integer = AS_CDT_SELECT_LEAF_LIST_VALUE}, - {"CDT_SELECT_LEAF_MAP_VALUE", - .value.integer = AS_CDT_SELECT_LEAF_MAP_VALUE}, - {"CDT_SELECT_LEAF_MAP_KEY", .value.integer = AS_CDT_SELECT_LEAF_MAP_KEY}, - {"CDT_SELECT_NO_FAIL", .value.integer = AS_CDT_SELECT_NO_FAIL}, + /* + When doing a cdt select/apply operation, and applying an expression on each + iterated object, this lets us choose a specific value over each iterated + object. + */ + EXPOSE_AS_MACRO_WITHOUT_AS_PREFIX_AS_PUBLIC_FIELD(EXP_LOOPVAR_KEY), + EXPOSE_AS_MACRO_WITHOUT_AS_PREFIX_AS_PUBLIC_FIELD(EXP_LOOPVAR_VALUE), + EXPOSE_AS_MACRO_WITHOUT_AS_PREFIX_AS_PUBLIC_FIELD(EXP_LOOPVAR_INDEX), + + EXPOSE_AS_MACRO_WITHOUT_AS_PREFIX_AS_PUBLIC_FIELD(CDT_SELECT_MATCHING_TREE), + EXPOSE_AS_MACRO_WITHOUT_AS_PREFIX_AS_PUBLIC_FIELD(CDT_SELECT_VALUES), + EXPOSE_AS_MACRO_WITHOUT_AS_PREFIX_AS_PUBLIC_FIELD( + CDT_SELECT_MAP_KEY_VALUES), + EXPOSE_AS_MACRO_WITHOUT_AS_PREFIX_AS_PUBLIC_FIELD(CDT_SELECT_MAP_KEYS), + EXPOSE_AS_MACRO_WITHOUT_AS_PREFIX_AS_PUBLIC_FIELD(CDT_SELECT_NO_FAIL), + + EXPOSE_MACRO(_AS_EXP_LOOPVAR_FLOAT), + EXPOSE_MACRO(_AS_EXP_LOOPVAR_INT), + EXPOSE_MACRO(_AS_EXP_LOOPVAR_LIST), + EXPOSE_MACRO(_AS_EXP_LOOPVAR_MAP), + EXPOSE_MACRO(_AS_EXP_LOOPVAR_STR), // TODO: move all internal constants used by aerospike_helpers to this loc {"_CDT_CTX_EXP_EXPR_KEY", .is_str_value = true, .value.string = "expr"}}; diff --git a/src/main/convert_expressions.c b/src/main/convert_expressions.c index 2d5f17d6c9..a4f4c75908 100644 --- a/src/main/convert_expressions.c +++ b/src/main/convert_expressions.c @@ -33,6 +33,7 @@ #include "geo.h" #include "cdt_types.h" #include "key_ordered_dict.h" +#include "convert_expressions.h" // EXPR OPS enum expr_ops { @@ -104,7 +105,7 @@ enum expr_ops { AS_EXP_CODE_CALL_APPLY = 129, LIST_MOD = 139, - VAL = 200 + VAL = 200, }; // VIRTUAL OPS @@ -209,13 +210,17 @@ static as_status get_expr_size(int *size_to_alloc, int *intermediate_exprs_size, static const int EXPR_SIZES[] = { // TODO: can also be cdt_apply() - [AS_EXP_CODE_CALL_SELECT] = EXP_SZ(as_exp_cdt_select(NULL, 0, 0, NIL)), + [AS_EXP_CODE_CALL_SELECT] = + EXP_SZ(as_exp_select_by_path(NULL, 0, 0, NIL)), [AS_EXP_CODE_CALL_APPLY] = - EXP_SZ(as_exp_cdt_apply(NULL, 0, NULL, 0, NIL)), + EXP_SZ(as_exp_modify_by_path(NULL, 0, NULL, 0, NIL)), [BIN] = EXP_SZ(as_exp_bin_int(0)), [_AS_EXP_CODE_AS_VAL] = EXP_SZ(as_exp_val(NULL)), - // TODO: have generic var_builtin? - [_AS_EXP_CODE_VAR_BUILTIN] = EXP_SZ(as_exp_var_builtin_str(0)), + [_AS_EXP_LOOPVAR_FLOAT] = EXP_SZ(as_exp_loopvar_float(0)), + [_AS_EXP_LOOPVAR_INT] = EXP_SZ(as_exp_loopvar_int(0)), + [_AS_EXP_LOOPVAR_LIST] = EXP_SZ(as_exp_loopvar_list(0)), + [_AS_EXP_LOOPVAR_MAP] = EXP_SZ(as_exp_loopvar_map(0)), + [_AS_EXP_LOOPVAR_STR] = EXP_SZ(as_exp_loopvar_str(0)), [VAL] = EXP_SZ(as_exp_val( NULL)), // NOTE if I don't count vals I don't need to subtract from other ops // MUST count these for expressions with var args. [EQ] = EXP_SZ( @@ -646,31 +651,30 @@ add_expr_macros(AerospikeClient *self, as_static_pool *static_pool, APPEND_ARRAY(0, BIN_EXPR()); break; - case _AS_EXP_CODE_VAR_BUILTIN: - // TODO: replace with new expr from Sam + case _AS_EXP_LOOPVAR_FLOAT: + case _AS_EXP_LOOPVAR_INT: + case _AS_EXP_LOOPVAR_LIST: + case _AS_EXP_LOOPVAR_MAP: + case _AS_EXP_LOOPVAR_STR: if (get_int64_t(err, AS_PY_VAL_KEY, temp_expr->pydict, &lval1) != AEROSPIKE_OK) { return err->code; } - if (get_int64_t(err, "value_type", temp_expr->pydict, &lval2) != - AEROSPIKE_OK) { - return err->code; - } - switch (lval2) { - case AS_EXP_TYPE_MAP: + switch (temp_expr->op) { + case _AS_EXP_LOOPVAR_MAP: APPEND_ARRAY(0, as_exp_var_builtin_map(lval1)); break; - case AS_EXP_TYPE_LIST: + case _AS_EXP_LOOPVAR_LIST: APPEND_ARRAY(0, as_exp_var_builtin_list(lval1)); break; - case AS_EXP_TYPE_STR: + case _AS_EXP_LOOPVAR_STR: APPEND_ARRAY(0, as_exp_var_builtin_str(lval1)); break; - case AS_EXP_TYPE_INT: + case _AS_EXP_LOOPVAR_INT: APPEND_ARRAY(0, as_exp_var_builtin_int(lval1)); break; - case AS_EXP_TYPE_FLOAT: + case _AS_EXP_LOOPVAR_FLOAT: APPEND_ARRAY(0, as_exp_var_builtin_float(lval1)); break; } @@ -1676,12 +1680,13 @@ add_expr_macros(AerospikeClient *self, as_static_pool *static_pool, false) != AEROSPIKE_OK) { return err->code; } - APPEND_ARRAY(0, as_exp_cdt_apply(temp_expr->ctx, lval1, mod_exp, - lval2, BIN_EXPR())); + APPEND_ARRAY(0, + as_exp_modify_by_path(temp_expr->ctx, lval1, + mod_exp, lval2, BIN_EXPR())); } else { - APPEND_ARRAY(0, as_exp_cdt_select(temp_expr->ctx, lval1, lval2, - BIN_EXPR())); + APPEND_ARRAY(0, as_exp_select_by_path(temp_expr->ctx, lval1, + lval2, BIN_EXPR())); } break; default: From fa93da6b676b30fe171957f01a4ee8d2822848b6 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 10:39:27 -0700 Subject: [PATCH 067/131] continue to cleanup. rm default flags for cdt select expr and ops, since java client doesn't have defaults --- aerospike_helpers/expressions/base.py | 11 ++++++----- aerospike_helpers/expressions/resources.py | 6 +----- aerospike_helpers/operations/operations.py | 4 ++-- src/include/policy.h | 11 +++++++++++ src/main/aerospike.c | 19 +++++++++++++++++-- src/main/convert_expressions.c | 12 +++++------- 6 files changed, 42 insertions(+), 21 deletions(-) diff --git a/aerospike_helpers/expressions/base.py b/aerospike_helpers/expressions/base.py index 6e1ece566e..1656af7544 100644 --- a/aerospike_helpers/expressions/base.py +++ b/aerospike_helpers/expressions/base.py @@ -1077,6 +1077,7 @@ class _LoopVar(_BaseExpr, ABC): def __init__(self, var_id: int): self._fixed = {_Keys.VALUE_KEY: var_id} + class LoopVarMap(_LoopVar): _op = aerospike._AS_EXP_LOOPVAR_MAP @@ -1100,7 +1101,7 @@ class LoopVarInt(_LoopVar): class CDTSelect(_BaseExpr): """ """ - _op = _ExprOp._AS_EXP_CODE_CALL_SELECT + _op = aerospike._AS_EXP_CODE_CALL_SELECT # TODO: document to be certain constants? # TODO: result_type not needed? @@ -1116,7 +1117,7 @@ def __init__(self, ctx: _cdt_ctx, return_type: ResultType, flags: int, bin: str) self._fixed = { _Keys.RETURN_TYPE_KEY: return_type, _Keys.CTX_KEY: ctx, - _Keys.CDT_SELECT_FLAGS_KEY: flags, + aerospike._CDT_SELECT_FLAGS_KEY: flags, _Keys.BIN_KEY: bin } @@ -1129,7 +1130,7 @@ class CDTApply(_BaseExpr): """ asdf """ - _op = _ExprOp._AS_EXP_CODE_CALL_APPLY + _op = aerospike._AS_EXP_CODE_CALL_APPLY # TODO: document to be certain constants? # TODO: why return type needed? this returns the whole bin after being modified? @@ -1142,9 +1143,9 @@ def __init__(self, ctx: _cdt_ctx, return_type: ResultType, mod_exp, flags: int, self._fixed = { _Keys.RETURN_TYPE_KEY: return_type, _Keys.CTX_KEY: ctx, - _Keys.CDT_SELECT_FLAGS_KEY: flags, + aerospike._CDT_APPLY_FLAGS_KEY: flags, _Keys.BIN_KEY: bin, - _Keys.CDT_SELECT_MOD_EXP_KEY: mod_exp + aerospike.CDT_SELECT_MOD_EXP_KEY: mod_exp } # TODO diff --git a/aerospike_helpers/expressions/resources.py b/aerospike_helpers/expressions/resources.py index 7206f27e02..ffd018103a 100644 --- a/aerospike_helpers/expressions/resources.py +++ b/aerospike_helpers/expressions/resources.py @@ -17,8 +17,6 @@ class _Keys: MAP_POLICY_KEY = "map_policy" LIST_ORDER_KEY = "list_order" REGEX_OPTIONS_KEY = "regex_options" - CDT_SELECT_FLAGS_KEY = "flags" - CDT_SELECT_MOD_EXP_KEY = "mod_exp" class _ExprOp: # TODO replace this with an enum @@ -88,9 +86,7 @@ class _ExprOp: # TODO replace this with an enum LET = 125 DEF = 126 - _AS_EXP_CODE_CALL_SELECT = 127 _AS_EXP_CODE_AS_VAL = 128 - _AS_EXP_CODE_CALL_APPLY = 129 # virtual ops _AS_EXP_CODE_CALL_VOP_START = 139 @@ -117,7 +113,7 @@ class ReturnType: MAP_RETURN_INVERTED = 0x10000 -# These enum constants must match the values for C client's as_exp_type +# TODO: These enum constants must match the values for C client's as_exp_type class ResultType: """ Flags used to indicate expression value_type. diff --git a/aerospike_helpers/operations/operations.py b/aerospike_helpers/operations/operations.py index 648957ac15..7f829fba8f 100644 --- a/aerospike_helpers/operations/operations.py +++ b/aerospike_helpers/operations/operations.py @@ -135,7 +135,7 @@ def touch(ttl: Optional[int] = None): return op_dict -def cdt_select(name: str, ctx: list, flags: int = aerospike.CDT_SELECT_LEAF_MAP_VALUE): +def cdt_select(name: str, ctx: list, flags: int): """ Create CDT select operation. @@ -146,7 +146,7 @@ def cdt_select(name: str, ctx: list, flags: int = aerospike.CDT_SELECT_LEAF_MAP_ return op_dict -def cdt_apply(name: str, ctx: list, expr: TypeExpression, flags: int = aerospike.CDT_SELECT_LEAF_MAP_VALUE): +def cdt_apply(name: str, ctx: list, expr: TypeExpression, flags: int): """ Create CDT apply operation. diff --git a/src/include/policy.h b/src/include/policy.h index b0598bb51f..538f5c56bb 100644 --- a/src/include/policy.h +++ b/src/include/policy.h @@ -170,8 +170,15 @@ enum aerospike_hll_operations { OP_HLL_MAY_CONTAIN }; +enum { + _AS_EXP_CODE_CALL_SELECT = 127, + _AS_EXP_CODE_CALL_APPLY = 129, +}; + enum aerospike_expression_operations { OP_EXPR_READ = 2200, OP_EXPR_WRITE }; +// Module constants to be used by aerospike_helpers + enum { _AS_EXP_LOOPVAR_FLOAT = 3000, _AS_EXP_LOOPVAR_INT, @@ -180,6 +187,10 @@ enum { _AS_EXP_LOOPVAR_STR }; +#define _CDT_SELECT_FLAGS_KEY "cdt_select_flags" +#define _CDT_APPLY_FLAGS_KEY "cdt_apply_flags" +#define _CDT_APPLY_MOD_EXP_KEY "mod_exp" + enum aerospike_regex_constants { REGEX_NONE = 0, REGEX_EXTENDED, diff --git a/src/main/aerospike.c b/src/main/aerospike.c index 6f898c1218..74e8ca3ffd 100644 --- a/src/main/aerospike.c +++ b/src/main/aerospike.c @@ -123,6 +123,11 @@ struct module_constant_name_to_value { #macro_name, .value.integer = macro_name \ } +#define EXPOSE_STRING_MACRO_FOR_AEROSPIKE_HELPERS(macro_name) \ + { \ + #macro_name, .is_str_value = true, .value.string = macro_name \ + } + // TODO: many of these names are the same as the enum name // Is there a way to generate this code? // TODO: regression tests for all these constants @@ -564,14 +569,24 @@ static struct module_constant_name_to_value module_constants[] = { EXPOSE_AS_MACRO_WITHOUT_AS_PREFIX_AS_PUBLIC_FIELD(CDT_SELECT_MAP_KEYS), EXPOSE_AS_MACRO_WITHOUT_AS_PREFIX_AS_PUBLIC_FIELD(CDT_SELECT_NO_FAIL), + // For aerospike_helpers to use. Not to be exposed in public API + // TODO: move all internal constants used by aerospike_helpers to this loc + EXPOSE_MACRO(_AS_EXP_LOOPVAR_FLOAT), EXPOSE_MACRO(_AS_EXP_LOOPVAR_INT), EXPOSE_MACRO(_AS_EXP_LOOPVAR_LIST), EXPOSE_MACRO(_AS_EXP_LOOPVAR_MAP), EXPOSE_MACRO(_AS_EXP_LOOPVAR_STR), - // TODO: move all internal constants used by aerospike_helpers to this loc - {"_CDT_CTX_EXP_EXPR_KEY", .is_str_value = true, .value.string = "expr"}}; + // C client uses the same expression code for these two expressions + // so we define unique ones in the Python client code + EXPOSE_MACRO(_AS_EXP_CODE_CALL_SELECT), + EXPOSE_MACRO(_AS_EXP_CODE_CALL_APPLY), + + EXPOSE_STRING_MACRO_FOR_AEROSPIKE_HELPERS(_CDT_SELECT_FLAGS_KEY), + EXPOSE_STRING_MACRO_FOR_AEROSPIKE_HELPERS(_CDT_APPLY_FLAGS_KEY), + EXPOSE_STRING_MACRO_FOR_AEROSPIKE_HELPERS(_CDT_APPLY_MOD_EXP_KEY), +}; struct submodule_name_to_creation_method { const char *name; diff --git a/src/main/convert_expressions.c b/src/main/convert_expressions.c index a4f4c75908..b3a33f7125 100644 --- a/src/main/convert_expressions.c +++ b/src/main/convert_expressions.c @@ -101,8 +101,6 @@ enum expr_ops { VAR = 124, LET = 125, DEF = 126, - AS_EXP_CODE_CALL_SELECT = 127, - AS_EXP_CODE_CALL_APPLY = 129, LIST_MOD = 139, VAL = 200, @@ -210,9 +208,9 @@ static as_status get_expr_size(int *size_to_alloc, int *intermediate_exprs_size, static const int EXPR_SIZES[] = { // TODO: can also be cdt_apply() - [AS_EXP_CODE_CALL_SELECT] = + [_AS_EXP_CODE_CALL_SELECT] = EXP_SZ(as_exp_select_by_path(NULL, 0, 0, NIL)), - [AS_EXP_CODE_CALL_APPLY] = + [_AS_EXP_CODE_CALL_APPLY] = EXP_SZ(as_exp_modify_by_path(NULL, 0, NULL, 0, NIL)), [BIN] = EXP_SZ(as_exp_bin_int(0)), [_AS_EXP_CODE_AS_VAL] = EXP_SZ(as_exp_val(NULL)), @@ -1652,8 +1650,8 @@ add_expr_macros(AerospikeClient *self, as_static_pool *static_pool, case UNKNOWN: APPEND_ARRAY(0, as_exp_unknown()); break; - case AS_EXP_CODE_CALL_SELECT: - case AS_EXP_CODE_CALL_APPLY: + case _AS_EXP_CODE_CALL_SELECT: + case _AS_EXP_CODE_CALL_APPLY: if (get_int64_t(err, "return_type", temp_expr->pydict, &lval1) != AEROSPIKE_OK) { return err->code; @@ -1674,7 +1672,7 @@ add_expr_macros(AerospikeClient *self, as_static_pool *static_pool, "mod_exp is required for cdt_apply() expression."); } - if (temp_expr->op == AS_EXP_CODE_CALL_APPLY) { + if (temp_expr->op == _AS_EXP_CODE_CALL_APPLY) { as_exp *mod_exp = NULL; if (as_exp_new_from_pyobject(self, py_mod_exp, &mod_exp, err, false) != AEROSPIKE_OK) { From 0fce163f49fe754625c9e0e18883375294f874e3 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 10:49:23 -0700 Subject: [PATCH 068/131] Clean up ops --- aerospike_helpers/expressions/base.py | 2 +- aerospike_helpers/operations/operations.py | 9 ++++++-- src/main/client/operate.c | 8 +++---- src/main/convert_expressions.c | 25 +++++++++++++--------- 4 files changed, 27 insertions(+), 17 deletions(-) diff --git a/aerospike_helpers/expressions/base.py b/aerospike_helpers/expressions/base.py index 1656af7544..814aae9444 100644 --- a/aerospike_helpers/expressions/base.py +++ b/aerospike_helpers/expressions/base.py @@ -1145,7 +1145,7 @@ def __init__(self, ctx: _cdt_ctx, return_type: ResultType, mod_exp, flags: int, _Keys.CTX_KEY: ctx, aerospike._CDT_APPLY_FLAGS_KEY: flags, _Keys.BIN_KEY: bin, - aerospike.CDT_SELECT_MOD_EXP_KEY: mod_exp + aerospike._CDT_APPLY_MOD_EXP_KEY: mod_exp } # TODO diff --git a/aerospike_helpers/operations/operations.py b/aerospike_helpers/operations/operations.py index 7f829fba8f..ad47c1ab42 100644 --- a/aerospike_helpers/operations/operations.py +++ b/aerospike_helpers/operations/operations.py @@ -142,7 +142,7 @@ def cdt_select(name: str, ctx: list, flags: int): Returns: A dictionary to be passed to operate or operate_ordered. """ - op_dict = {"op": aerospike._AS_OPERATOR_CDT_READ, "bin": name, "ctx": ctx, "flags": flags} + op_dict = {"op": aerospike._AS_OPERATOR_CDT_READ, "bin": name, "ctx": ctx, aerospike._CDT_SELECT_FLAGS_KEY: flags} return op_dict @@ -153,5 +153,10 @@ def cdt_apply(name: str, ctx: list, expr: TypeExpression, flags: int): Returns: A dictionary to be passed to operate or operate_ordered. """ - op_dict = {"op": aerospike._AS_OPERATOR_CDT_MODIFY, "bin": name, "ctx": ctx, "mod_exp": expr, "flags": flags} + op_dict = { + "op": aerospike._AS_OPERATOR_CDT_MODIFY, + "bin": name, + "ctx": ctx, aerospike._CDT_APPLY_MOD_EXP_KEY: expr, + aerospike._CDT_SELECT_FLAGS_KEY: flags + } return op_dict diff --git a/src/main/client/operate.c b/src/main/client/operate.c index 9fbdfce7a6..0143fc1055 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -566,8 +566,8 @@ as_status add_op(AerospikeClient *self, as_error *err, // TODO: set module constant for flags str PyObject *py_flags = NULL; // TODO: already a retval var previously? - int retval = - PyDict_GetItemStringRef(py_operation_dict, "flags", &py_flags); + int retval = PyDict_GetItemStringRef(py_operation_dict, + _CDT_SELECT_FLAGS_KEY, &py_flags); if (retval == 0) { as_error_update(err, AEROSPIKE_ERR_PARAM, "CDT operation is missing a flags argument"); @@ -591,8 +591,8 @@ as_status add_op(AerospikeClient *self, as_error *err, } else if (operation == AS_OPERATOR_CDT_MODIFY) { PyObject *py_expr = NULL; - int retval = - PyDict_GetItemStringRef(py_operation_dict, "mod_exp", &py_expr); + int retval = PyDict_GetItemStringRef( + py_operation_dict, _CDT_APPLY_MOD_EXP_KEY, &py_expr); if (retval == 0) { as_error_update(err, AEROSPIKE_ERR_PARAM, "CDT operation is missing a flags argument"); diff --git a/src/main/convert_expressions.c b/src/main/convert_expressions.c index b3a33f7125..cc9d0978d6 100644 --- a/src/main/convert_expressions.c +++ b/src/main/convert_expressions.c @@ -1656,28 +1656,33 @@ add_expr_macros(AerospikeClient *self, as_static_pool *static_pool, AEROSPIKE_OK) { return err->code; } - if (get_int64_t(err, "flags", temp_expr->pydict, &lval2) != - AEROSPIKE_OK) { - return err->code; - } + if (get_bin(err, temp_expr->pydict, unicodeStrVector, &bin_name) != AEROSPIKE_OK) { return err->code; } - PyObject *py_mod_exp = - PyDict_GetItemString(temp_expr->pydict, "mod_exp"); - if (!py_mod_exp) { - return as_error_update( - err, AEROSPIKE_ERR_PARAM, - "mod_exp is required for cdt_apply() expression."); + + // TODO: rm apply key + if (get_int64_t(err, _CDT_SELECT_FLAGS_KEY, temp_expr->pydict, + &lval2) != AEROSPIKE_OK) { + return err->code; } if (temp_expr->op == _AS_EXP_CODE_CALL_APPLY) { + PyObject *py_mod_exp = PyDict_GetItemString( + temp_expr->pydict, _CDT_APPLY_MOD_EXP_KEY); + if (!py_mod_exp) { + return as_error_update( + err, AEROSPIKE_ERR_PARAM, + "mod_exp is required for cdt_apply() expression."); + } + as_exp *mod_exp = NULL; if (as_exp_new_from_pyobject(self, py_mod_exp, &mod_exp, err, false) != AEROSPIKE_OK) { return err->code; } + APPEND_ARRAY(0, as_exp_modify_by_path(temp_expr->ctx, lval1, mod_exp, lval2, BIN_EXPR())); From 6c77bbb75c28fb4403dc859aa28de5835d24d728 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 10:51:54 -0700 Subject: [PATCH 069/131] missing --- src/include/convert_expressions.h | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/include/convert_expressions.h diff --git a/src/include/convert_expressions.h b/src/include/convert_expressions.h new file mode 100644 index 0000000000..e69de29bb2 From e65aed0a6d354dc1f852bd830f6ce6f800ecf606 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 10:52:21 -0700 Subject: [PATCH 070/131] no longer used --- src/include/convert_expressions.h | 0 src/main/aerospike.c | 1 - src/main/convert_expressions.c | 1 - 3 files changed, 2 deletions(-) delete mode 100644 src/include/convert_expressions.h diff --git a/src/include/convert_expressions.h b/src/include/convert_expressions.h deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/main/aerospike.c b/src/main/aerospike.c index 74e8ca3ffd..7fbac8cee2 100644 --- a/src/main/aerospike.c +++ b/src/main/aerospike.c @@ -34,7 +34,6 @@ #include "cdt_types.h" #include "transaction.h" #include "config_provider.h" -#include "convert_expressions.h" #include #include diff --git a/src/main/convert_expressions.c b/src/main/convert_expressions.c index cc9d0978d6..b8ff5e896c 100644 --- a/src/main/convert_expressions.c +++ b/src/main/convert_expressions.c @@ -33,7 +33,6 @@ #include "geo.h" #include "cdt_types.h" #include "key_ordered_dict.h" -#include "convert_expressions.h" // EXPR OPS enum expr_ops { From df5f29911883889a99bff2a401fe4b3e2bdff0f1 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 10:54:28 -0700 Subject: [PATCH 071/131] fix --- src/main/client/operate.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/client/operate.c b/src/main/client/operate.c index 0143fc1055..282a21b27e 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -587,7 +587,7 @@ as_status add_op(AerospikeClient *self, as_error *err, if (operation == AS_OPERATOR_CDT_READ) { operation_succeeded = - as_operations_cdt_select(ops, bin, ctx_ref, flags); + as_operations_select_by_path(ops, bin, ctx_ref, flags); } else if (operation == AS_OPERATOR_CDT_MODIFY) { PyObject *py_expr = NULL; @@ -610,7 +610,7 @@ as_status add_op(AerospikeClient *self, as_error *err, } operation_succeeded = - as_operations_cdt_apply(ops, bin, ctx_ref, mod_exp, flags); + as_operations_modify_by_path(ops, bin, ctx_ref, mod_exp, flags); } break; From 93899f8ff5264faa1256ff02b4a6e4a179d8e330 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 10:56:43 -0700 Subject: [PATCH 072/131] fix naming --- src/main/conversions.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/conversions.c b/src/main/conversions.c index 0aa58b3d8e..688a501b02 100644 --- a/src/main/conversions.c +++ b/src/main/conversions.c @@ -2849,7 +2849,7 @@ as_status get_cdt_ctx(AerospikeClient *self, as_error *err, as_cdt_ctx *cdt_ctx, } else if (item_type == AS_CDT_CTX_EXP) { if (Py_IsNone(py_extra_args)) { - as_cdt_ctx_add_all(cdt_ctx); + as_cdt_ctx_add_all_children(cdt_ctx); } else { // TODO: replace with aerospike._CDT_CTX_EXP_EXPR_KEY @@ -2870,7 +2870,7 @@ as_status get_cdt_ctx(AerospikeClient *self, as_error *err, as_cdt_ctx *cdt_ctx, } // This C client call memcpy's the expr's contents - as_cdt_ctx_add_exp(cdt_ctx, expr); + as_cdt_ctx_add_all_children_with_filter(cdt_ctx, expr); as_exp_destroy(expr); } } From 8af347204fdbb9adf2b038243a36be8e1675b5e3 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 10:58:32 -0700 Subject: [PATCH 073/131] fix naming --- src/main/convert_expressions.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/convert_expressions.c b/src/main/convert_expressions.c index b8ff5e896c..e0849f07ca 100644 --- a/src/main/convert_expressions.c +++ b/src/main/convert_expressions.c @@ -660,19 +660,19 @@ add_expr_macros(AerospikeClient *self, as_static_pool *static_pool, switch (temp_expr->op) { case _AS_EXP_LOOPVAR_MAP: - APPEND_ARRAY(0, as_exp_var_builtin_map(lval1)); + APPEND_ARRAY(0, as_exp_loopvar_map(lval1)); break; case _AS_EXP_LOOPVAR_LIST: - APPEND_ARRAY(0, as_exp_var_builtin_list(lval1)); + APPEND_ARRAY(0, as_exp_loopvar_list(lval1)); break; case _AS_EXP_LOOPVAR_STR: - APPEND_ARRAY(0, as_exp_var_builtin_str(lval1)); + APPEND_ARRAY(0, as_exp_loopvar_str(lval1)); break; case _AS_EXP_LOOPVAR_INT: - APPEND_ARRAY(0, as_exp_var_builtin_int(lval1)); + APPEND_ARRAY(0, as_exp_loopvar_int(lval1)); break; case _AS_EXP_LOOPVAR_FLOAT: - APPEND_ARRAY(0, as_exp_var_builtin_float(lval1)); + APPEND_ARRAY(0, as_exp_loopvar_float(lval1)); break; } From ffff0a8b3ec9e2bde940bdc27e217ea0b2b91ee3 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:00:55 -0700 Subject: [PATCH 074/131] fix naming for cdt ctx --- aerospike_helpers/cdt_ctx.py | 4 ++-- test/new_tests/test_cdt_select.py | 32 +++++++++++++++---------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/aerospike_helpers/cdt_ctx.py b/aerospike_helpers/cdt_ctx.py index b5f4704428..967d17d386 100644 --- a/aerospike_helpers/cdt_ctx.py +++ b/aerospike_helpers/cdt_ctx.py @@ -301,13 +301,13 @@ def cdt_ctx_map_key_create(key: any, order: int = 0) -> _cdt_ctx: """ return _cdt_ctx(id=aerospike.CDT_CTX_MAP_KEY_CREATE, value=key, extra_args={CDT_CTX_ORDER_KEY: order}) -def cdt_ctx_all() -> _cdt_ctx: +def cdt_ctx_all_children() -> _cdt_ctx: """ The cdt_ctx object selects all. """ return _cdt_ctx(id=aerospike._CDT_CTX_EXP) -def cdt_ctx_exp(expression: "TypeExpression") -> _cdt_ctx: +def cdt_ctx_all_children_with_filter(expression: "TypeExpression") -> _cdt_ctx: """ Select and filter using an expression. diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 2e6a6d9290..4d53f188ce 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -82,7 +82,7 @@ def insert_record(self): operations.cdt_select( name=LIST_BIN_NAME, ctx=[ - cdt_ctx.cdt_ctx_all(), + cdt_ctx.cdt_ctx_all_children(), ] ), { @@ -94,7 +94,7 @@ def insert_record(self): operations.cdt_select( name=MAP_BIN_NAME, ctx=[ - cdt_ctx.cdt_ctx_all(), + cdt_ctx.cdt_ctx_all_children(), ] ), { @@ -106,8 +106,8 @@ def insert_record(self): operations.cdt_select( name=LIST_BIN_NAME, ctx=[ - cdt_ctx.cdt_ctx_all(), - cdt_ctx.cdt_ctx_all() + cdt_ctx.cdt_ctx_all_children(), + cdt_ctx.cdt_ctx_all_children() ] ), { @@ -132,8 +132,8 @@ def insert_record(self): operations.cdt_select( name=MAP_BIN_NAME, ctx=[ - cdt_ctx.cdt_ctx_all(), - cdt_ctx.cdt_ctx_exp(expression=EXPR_ON_DIFFERENT_ITERATED_TYPE) + cdt_ctx.cdt_ctx_all_children(), + cdt_ctx.cdt_ctx_all_children_with_filter(expression=EXPR_ON_DIFFERENT_ITERATED_TYPE) ], flags=aerospike.CDT_SELECT_NO_FAIL ), @@ -161,8 +161,8 @@ def test_cdt_select_with_filter(self): operations.cdt_select( name=self.MAP_OF_NESTED_MAPS_BIN_NAME, ctx=[ - cdt_ctx.cdt_ctx_all(), - cdt_ctx.cdt_ctx_exp(expression=expr) + cdt_ctx.cdt_ctx_all_children(), + cdt_ctx.cdt_ctx_all_children_with_filter(expression=expr) ] ) ] @@ -179,16 +179,16 @@ def test_cdt_modify(self): operations.cdt_apply( name=self.MAP_OF_NESTED_MAPS_BIN_NAME, ctx=[ - cdt_ctx.cdt_ctx_all(), - cdt_ctx.cdt_ctx_all() + cdt_ctx.cdt_ctx_all_children(), + cdt_ctx.cdt_ctx_all_children() ], expr=mod_expr ), operations.cdt_select( name=self.MAP_OF_NESTED_MAPS_BIN_NAME, ctx=[ - cdt_ctx.cdt_ctx_all(), - cdt_ctx.cdt_ctx_all() + cdt_ctx.cdt_ctx_all_children(), + cdt_ctx.cdt_ctx_all_children() ] ), ] @@ -213,8 +213,8 @@ def test_cdt_select_flags(self, flags, expected_bins): operations.cdt_select( name=self.LIST_BIN_NAME, ctx=[ - cdt_ctx.cdt_ctx_all(), - cdt_ctx.cdt_ctx_all() + cdt_ctx.cdt_ctx_all_children(), + cdt_ctx.cdt_ctx_all_children() ], # TODO: not done flags=flags @@ -243,8 +243,8 @@ def test_cdt_select_flags(self, flags, expected_bins): operations.cdt_select( name=MAP_BIN_NAME, ctx=[ - cdt_ctx.cdt_ctx_all(), - cdt_ctx.cdt_ctx_exp(expression=EXPR_ON_DIFFERENT_ITERATED_TYPE) + cdt_ctx.cdt_ctx_all_children(), + cdt_ctx.cdt_ctx_all_children_with_filter(expression=EXPR_ON_DIFFERENT_ITERATED_TYPE) ] ), pytest.raises(e.AerospikeError), From c6182a34bf215acb57890a9bd3716aba3ebac25a Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:04:12 -0700 Subject: [PATCH 075/131] Address TODO --- aerospike_helpers/cdt_ctx.py | 2 +- src/include/policy.h | 1 + src/main/aerospike.c | 3 +++ src/main/conversions.c | 1 - 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/aerospike_helpers/cdt_ctx.py b/aerospike_helpers/cdt_ctx.py index 967d17d386..81dfe6b657 100644 --- a/aerospike_helpers/cdt_ctx.py +++ b/aerospike_helpers/cdt_ctx.py @@ -315,4 +315,4 @@ def cdt_ctx_all_children_with_filter(expression: "TypeExpression") -> _cdt_ctx: expression: compiled aerospike expression """ # TODO: make the same as helper that takes in expr value from dict - return _cdt_ctx(id=aerospike._CDT_CTX_EXP, extra_args={aerospike._CDT_CTX_EXP_EXPR_KEY: expression}) + return _cdt_ctx(id=aerospike._CDT_CTX_EXP, extra_args={aerospike._CDT_CTX_ALL_CHILDREN_WITH_FILTER_EXPR_KEY: expression}) diff --git a/src/include/policy.h b/src/include/policy.h index 538f5c56bb..ebdcdaa179 100644 --- a/src/include/policy.h +++ b/src/include/policy.h @@ -190,6 +190,7 @@ enum { #define _CDT_SELECT_FLAGS_KEY "cdt_select_flags" #define _CDT_APPLY_FLAGS_KEY "cdt_apply_flags" #define _CDT_APPLY_MOD_EXP_KEY "mod_exp" +#define _CDT_CTX_ALL_CHILDREN_WITH_FILTER_EXPR_KEY "filter_expr" enum aerospike_regex_constants { REGEX_NONE = 0, diff --git a/src/main/aerospike.c b/src/main/aerospike.c index 7fbac8cee2..a7ccf0b573 100644 --- a/src/main/aerospike.c +++ b/src/main/aerospike.c @@ -585,6 +585,9 @@ static struct module_constant_name_to_value module_constants[] = { EXPOSE_STRING_MACRO_FOR_AEROSPIKE_HELPERS(_CDT_SELECT_FLAGS_KEY), EXPOSE_STRING_MACRO_FOR_AEROSPIKE_HELPERS(_CDT_APPLY_FLAGS_KEY), EXPOSE_STRING_MACRO_FOR_AEROSPIKE_HELPERS(_CDT_APPLY_MOD_EXP_KEY), + + EXPOSE_STRING_MACRO_FOR_AEROSPIKE_HELPERS( + _CDT_CTX_ALL_CHILDREN_WITH_FILTER_EXPR_KEY), }; struct submodule_name_to_creation_method { diff --git a/src/main/conversions.c b/src/main/conversions.c index 688a501b02..d91b287ae7 100644 --- a/src/main/conversions.c +++ b/src/main/conversions.c @@ -2852,7 +2852,6 @@ as_status get_cdt_ctx(AerospikeClient *self, as_error *err, as_cdt_ctx *cdt_ctx, as_cdt_ctx_add_all_children(cdt_ctx); } else { - // TODO: replace with aerospike._CDT_CTX_EXP_EXPR_KEY PyObject *py_expr = NULL; int retval = PyDict_GetItemStringRef(py_extra_args, AS_EXPR_KEY, &py_expr); From 18c82b6385704e2a3be9e98315f7a58474b6e7a4 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:11:14 -0700 Subject: [PATCH 076/131] fix test --- test/new_tests/test_cdt_select.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 4d53f188ce..3981eb57af 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -3,7 +3,7 @@ import aerospike from aerospike_helpers.operations import operations from aerospike_helpers.expressions.resources import ResultType -from aerospike_helpers.expressions.base import GE, VarBuiltIn, Eq +from aerospike_helpers.expressions.base import GE, VarBuiltIn, Eq, LoopVarStr, LoopVarFloat from aerospike_helpers.expressions.arithmetic import Sub from aerospike_helpers import cdt_ctx from aerospike import exception as e @@ -72,7 +72,7 @@ def insert_record(self): yield self.as_connection.remove(self.key) - EXPR_ON_DIFFERENT_ITERATED_TYPE = Eq(VarBuiltIn(aerospike.EXP_BUILTIN_VALUE, ResultType.STRING), "a").compile() + EXPR_ON_DIFFERENT_ITERATED_TYPE = Eq(LoopVarStr(aerospike.EXP_BUILTIN_VALUE), "a").compile() @pytest.mark.parametrize( # TODO: ids @@ -154,7 +154,7 @@ def test_cdt_select_basic_functionality(self, op, expected_bins): def test_cdt_select_with_filter(self): expr = GE( - VarBuiltIn(aerospike.EXP_BUILTIN_VALUE, ResultType.FLOAT), + LoopVarFloat(aerospike.EXP_BUILTIN_VALUE), 20 ).compile() ops = [ @@ -174,7 +174,7 @@ def test_cdt_select_with_filter(self): ] def test_cdt_modify(self): - mod_expr = Sub(VarBuiltIn(aerospike.EXP_BUILTIN_VALUE, ResultType.FLOAT), 5.0).compile() + mod_expr = Sub(LoopVarFloat(aerospike.EXP_BUILTIN_VALUE), 5.0).compile() ops = [ operations.cdt_apply( name=self.MAP_OF_NESTED_MAPS_BIN_NAME, From 6fd79ec1b05522627ec6439c21a9fd0eb23a2bff Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:13:57 -0700 Subject: [PATCH 077/131] bad import --- test/new_tests/test_cdt_select.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 3981eb57af..982a1d41f3 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -3,7 +3,7 @@ import aerospike from aerospike_helpers.operations import operations from aerospike_helpers.expressions.resources import ResultType -from aerospike_helpers.expressions.base import GE, VarBuiltIn, Eq, LoopVarStr, LoopVarFloat +from aerospike_helpers.expressions.base import GE, Eq, LoopVarStr, LoopVarFloat from aerospike_helpers.expressions.arithmetic import Sub from aerospike_helpers import cdt_ctx from aerospike import exception as e From 1777b1f0d3ee60604390f5c0b2f1f85ac7103f66 Mon Sep 17 00:00:00 2001 From: juliannguyen4 <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 18:20:56 +0000 Subject: [PATCH 078/131] fix test --- test/new_tests/test_cdt_select.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 982a1d41f3..db0d0bfb15 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -72,7 +72,7 @@ def insert_record(self): yield self.as_connection.remove(self.key) - EXPR_ON_DIFFERENT_ITERATED_TYPE = Eq(LoopVarStr(aerospike.EXP_BUILTIN_VALUE), "a").compile() + EXPR_ON_DIFFERENT_ITERATED_TYPE = Eq(LoopVarStr(aerospike.EXP_LOOPVAR_VALUE), "a").compile() @pytest.mark.parametrize( # TODO: ids @@ -154,7 +154,7 @@ def test_cdt_select_basic_functionality(self, op, expected_bins): def test_cdt_select_with_filter(self): expr = GE( - LoopVarFloat(aerospike.EXP_BUILTIN_VALUE), + LoopVarFloat(aerospike.EXP_LOOPVAR_VALUE), 20 ).compile() ops = [ @@ -174,7 +174,7 @@ def test_cdt_select_with_filter(self): ] def test_cdt_modify(self): - mod_expr = Sub(LoopVarFloat(aerospike.EXP_BUILTIN_VALUE), 5.0).compile() + mod_expr = Sub(LoopVarFloat(aerospike.EXP_LOOPVAR_VALUE), 5.0).compile() ops = [ operations.cdt_apply( name=self.MAP_OF_NESTED_MAPS_BIN_NAME, From 0d894dc2de11848bdac4233983f55c263f529142 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:22:26 -0700 Subject: [PATCH 079/131] Change op naming to be consistent with c client --- aerospike_helpers/operations/operations.py | 4 ++-- test/new_tests/test_cdt_select.py | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/aerospike_helpers/operations/operations.py b/aerospike_helpers/operations/operations.py index ad47c1ab42..636aef0050 100644 --- a/aerospike_helpers/operations/operations.py +++ b/aerospike_helpers/operations/operations.py @@ -135,7 +135,7 @@ def touch(ttl: Optional[int] = None): return op_dict -def cdt_select(name: str, ctx: list, flags: int): +def select_by_path(name: str, ctx: list, flags: int): """ Create CDT select operation. @@ -146,7 +146,7 @@ def cdt_select(name: str, ctx: list, flags: int): return op_dict -def cdt_apply(name: str, ctx: list, expr: TypeExpression, flags: int): +def modify_by_path(name: str, ctx: list, expr: TypeExpression, flags: int): """ Create CDT apply operation. diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index db0d0bfb15..b54c067104 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -79,7 +79,7 @@ def insert_record(self): "op,expected_bins", [ pytest.param( - operations.cdt_select( + operations.select_by_path( name=LIST_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all_children(), @@ -91,7 +91,7 @@ def insert_record(self): id="select_all_children_once_in_list" ), pytest.param( - operations.cdt_select( + operations.select_by_path( name=MAP_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all_children(), @@ -103,7 +103,7 @@ def insert_record(self): id="select_all_children_once_in_map" ), pytest.param( - operations.cdt_select( + operations.select_by_path( name=LIST_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all_children(), @@ -129,7 +129,7 @@ def insert_record(self): id="select_all_children_twice_in_list" ), pytest.param( - operations.cdt_select( + operations.select_by_path( name=MAP_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all_children(), @@ -158,7 +158,7 @@ def test_cdt_select_with_filter(self): 20 ).compile() ops = [ - operations.cdt_select( + operations.select_by_path( name=self.MAP_OF_NESTED_MAPS_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all_children(), @@ -176,7 +176,7 @@ def test_cdt_select_with_filter(self): def test_cdt_modify(self): mod_expr = Sub(LoopVarFloat(aerospike.EXP_LOOPVAR_VALUE), 5.0).compile() ops = [ - operations.cdt_apply( + operations.modify_by_path( name=self.MAP_OF_NESTED_MAPS_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all_children(), @@ -184,7 +184,7 @@ def test_cdt_modify(self): ], expr=mod_expr ), - operations.cdt_select( + operations.select_by_path( name=self.MAP_OF_NESTED_MAPS_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all_children(), @@ -210,7 +210,7 @@ def test_cdt_modify(self): ) def test_cdt_select_flags(self, flags, expected_bins): ops = [ - operations.cdt_select( + operations.select_by_path( name=self.LIST_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all_children(), @@ -231,7 +231,7 @@ def test_cdt_select_flags(self, flags, expected_bins): "op, context", [ pytest.param( - operations.cdt_select( + operations.select_by_path( name=MAP_BIN_NAME, ctx=[], ), @@ -240,7 +240,7 @@ def test_cdt_select_flags(self, flags, expected_bins): id="empty_ctx" ), pytest.param( - operations.cdt_select( + operations.select_by_path( name=MAP_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all_children(), From e2f7cd53de2c204fdf14f3632ea4fcf4c86d9759 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:34:59 -0700 Subject: [PATCH 080/131] Add flags argument to test since it's now required. --- aerospike-stubs/aerospike.pyi | 14 +++++++------- test/new_tests/test_cdt_select.py | 25 +++++++++++++++++-------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/aerospike-stubs/aerospike.pyi b/aerospike-stubs/aerospike.pyi index db28e2ff88..8ecc3ea0f6 100644 --- a/aerospike-stubs/aerospike.pyi +++ b/aerospike-stubs/aerospike.pyi @@ -309,15 +309,15 @@ TXN_STATE_VERIFIED: Literal[1] TXN_STATE_COMMITTED: Literal[2] TXN_STATE_ABORTED: Literal[3] -CDT_SELECT_TREE: Literal[0] -CDT_SELECT_LEAF_LIST_VALUE: Literal[1] -CDT_SELECT_LEAF_MAP_VALUE: Literal[1] -CDT_SELECT_LEAF_MAP_KEY: Literal[2] +CDT_SELECT_MATCHING_TREE: Literal[0] +CDT_SELECT_VALUES: Literal[1] +CDT_SELECT_MAP_KEY_VALUES: Literal[1] +CDT_SELECT_MAP_KEYS: Literal[2] CDT_SELECT_NO_FAIL: Literal[16] -EXP_BUILTIN_KEY: Literal[0] -EXP_BUILTIN_VALUE: Literal[1] -EXP_BUILTIN_INDEX: Literal[2] +EXP_LOOPVAR_KEY: Literal[0] +EXP_LOOPVAR_VALUE: Literal[1] +EXP_LOOPVAR_INDEX: Literal[2] @final class CDTInfinite: diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index b54c067104..0cd99bd65f 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -83,7 +83,8 @@ def insert_record(self): name=LIST_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all_children(), - ] + ], + flags=aerospike.CDT_SELECT_VALUES ), { LIST_BIN_NAME: BINS_FOR_CDT_SELECT_TEST[LIST_BIN_NAME] @@ -95,7 +96,8 @@ def insert_record(self): name=MAP_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all_children(), - ] + ], + flags=aerospike.CDT_SELECT_VALUES ), { MAP_BIN_NAME: list(BINS_FOR_CDT_SELECT_TEST[MAP_BIN_NAME].values()) @@ -108,7 +110,8 @@ def insert_record(self): ctx=[ cdt_ctx.cdt_ctx_all_children(), cdt_ctx.cdt_ctx_all_children() - ] + ], + flags=aerospike.CDT_SELECT_VALUES ), { LIST_BIN_NAME: [ @@ -135,7 +138,7 @@ def insert_record(self): cdt_ctx.cdt_ctx_all_children(), cdt_ctx.cdt_ctx_all_children_with_filter(expression=EXPR_ON_DIFFERENT_ITERATED_TYPE) ], - flags=aerospike.CDT_SELECT_NO_FAIL + flags=aerospike.CDT_SELECT_VALUES | aerospike.CDT_SELECT_NO_FAIL ), { MAP_BIN_NAME: [] @@ -163,7 +166,8 @@ def test_cdt_select_with_filter(self): ctx=[ cdt_ctx.cdt_ctx_all_children(), cdt_ctx.cdt_ctx_all_children_with_filter(expression=expr) - ] + ], + flags=aerospike.CDT_SELECT_VALUES ) ] with self.expected_context_for_pos_tests: @@ -182,14 +186,17 @@ def test_cdt_modify(self): cdt_ctx.cdt_ctx_all_children(), cdt_ctx.cdt_ctx_all_children() ], - expr=mod_expr + expr=mod_expr, + # TODO: should have flag for FAIL + flags=aerospike.CDT_SELECT_NO_FAIL ), operations.select_by_path( name=self.MAP_OF_NESTED_MAPS_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all_children(), cdt_ctx.cdt_ctx_all_children() - ] + ], + flags=aerospike.CDT_SELECT_VALUES ), ] with self.expected_context_for_pos_tests: @@ -234,6 +241,7 @@ def test_cdt_select_flags(self, flags, expected_bins): operations.select_by_path( name=MAP_BIN_NAME, ctx=[], + flags=aerospike.CDT_SELECT_VALUES ), # TODO: vague pytest.raises(e.AerospikeError), @@ -245,7 +253,8 @@ def test_cdt_select_flags(self, flags, expected_bins): ctx=[ cdt_ctx.cdt_ctx_all_children(), cdt_ctx.cdt_ctx_all_children_with_filter(expression=EXPR_ON_DIFFERENT_ITERATED_TYPE) - ] + ], + flags=aerospike.CDT_SELECT_VALUES ), pytest.raises(e.AerospikeError), id="iterate_on_unexpected_type" From da642f76d353375e421f30e9e93ef5f170954194 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:42:01 -0700 Subject: [PATCH 081/131] fix syntax --- src/main/client/operate.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/client/operate.c b/src/main/client/operate.c index 282a21b27e..a7231dc382 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -562,7 +562,7 @@ as_status add_op(AerospikeClient *self, as_error *err, bool operation_succeeded = true; switch (operation) { case AS_OPERATOR_CDT_READ: - case AS_OPERATOR_CDT_MODIFY: + case AS_OPERATOR_CDT_MODIFY: { // TODO: set module constant for flags str PyObject *py_flags = NULL; // TODO: already a retval var previously? @@ -614,6 +614,7 @@ as_status add_op(AerospikeClient *self, as_error *err, } break; + } case AS_OPERATOR_APPEND: if (PyUnicode_Check(py_value)) { py_ustr1 = PyUnicode_AsUTF8String(py_value); From 8ada0fb29a317d6a8ba13750271ac3917ca16563 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:43:14 -0700 Subject: [PATCH 082/131] fix --- src/main/aerospike.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/aerospike.c b/src/main/aerospike.c index a7ccf0b573..28a5ea2cf1 100644 --- a/src/main/aerospike.c +++ b/src/main/aerospike.c @@ -114,7 +114,7 @@ struct module_constant_name_to_value { #define EXPOSE_AS_MACRO_AS_PRIVATE_FIELD(macro_name_without_prefix) \ { \ STRINGIFY(_##macro_name_without_prefix), \ - .value.integer = AS_##macro_name_without_prefix \ + .value.integer = macro_name_without_prefix \ } #define EXPOSE_MACRO(macro_name) \ @@ -138,8 +138,8 @@ static struct module_constant_name_to_value module_constants[] = { {"OPERATOR_PREPEND", .value.integer = AS_OPERATOR_PREPEND}, {"OPERATOR_TOUCH", .value.integer = AS_OPERATOR_TOUCH}, {"OPERATOR_DELETE", .value.integer = AS_OPERATOR_DELETE}, - EXPOSE_AS_MACRO_AS_PRIVATE_FIELD(OPERATOR_CDT_READ), - EXPOSE_AS_MACRO_AS_PRIVATE_FIELD(OPERATOR_CDT_MODIFY), + EXPOSE_AS_MACRO_AS_PRIVATE_FIELD(AS_OPERATOR_CDT_READ), + EXPOSE_AS_MACRO_AS_PRIVATE_FIELD(AS_OPERATOR_CDT_MODIFY), {"AUTH_INTERNAL", .value.integer = AS_AUTH_INTERNAL}, {"AUTH_EXTERNAL", .value.integer = AS_AUTH_EXTERNAL}, From ee7738685aa8514cda81ef8651ad6f7fe7ca9db2 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:51:50 -0700 Subject: [PATCH 083/131] fix test. fix code --- src/main/client/operate.c | 6 +++--- test/new_tests/test_cdt_select.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/client/operate.c b/src/main/client/operate.c index a7231dc382..653f48dc2e 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -428,11 +428,11 @@ as_status add_op(AerospikeClient *self, as_error *err, else if (strcmp(name, "persist_index") == 0) { py_persist_index = value; } - // Use set instead of this? - else if (strcmp(name, "mod_exp") == 0) { + // TODO: Use set instead of this? + else if (strcmp(name, _CDT_APPLY_MOD_EXP_KEY) == 0) { continue; } - else if (strcmp(name, "flags") == 0) { + else if (strcmp(name, _CDT_SELECT_FLAGS_KEY) == 0) { continue; } else { diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 0cd99bd65f..d886305900 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -207,7 +207,7 @@ def test_cdt_modify(self): @pytest.mark.parametrize( "flags, expected_bins", [ - (aerospike.CDT_SELECT_TREE, {}) + (aerospike.CDT_SELECT_MATCHING_TREE, {}) # TODO: combine? # (aerospike.CDT_SELECT_LEAF_LIST_VALUE,) # TODO: bad naming? From b378c1c089fdc16adba4d4076bd6dbd99c4c60dd Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:54:33 -0700 Subject: [PATCH 084/131] fix --- src/main/conversions.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/conversions.c b/src/main/conversions.c index d91b287ae7..3c49faa79b 100644 --- a/src/main/conversions.c +++ b/src/main/conversions.c @@ -2853,8 +2853,9 @@ as_status get_cdt_ctx(AerospikeClient *self, as_error *err, as_cdt_ctx *cdt_ctx, } else { PyObject *py_expr = NULL; - int retval = PyDict_GetItemStringRef(py_extra_args, AS_EXPR_KEY, - &py_expr); + int retval = PyDict_GetItemStringRef( + py_extra_args, _CDT_CTX_ALL_CHILDREN_WITH_FILTER_EXPR_KEY, + &py_expr); if (retval != 1) { status = as_error_update(err, AEROSPIKE_ERR_PARAM, "Invalid cdt_ctx_exp"); From b823b3d6d6dc53bc81657826a8a7aa383e66fc5b Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 13:29:53 -0700 Subject: [PATCH 085/131] fix test comparing float with int --- test/new_tests/test_cdt_select.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index d886305900..3952a61559 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -158,7 +158,7 @@ def test_cdt_select_basic_functionality(self, op, expected_bins): def test_cdt_select_with_filter(self): expr = GE( LoopVarFloat(aerospike.EXP_LOOPVAR_VALUE), - 20 + 20.0 ).compile() ops = [ operations.select_by_path( From e46b9bc45307c39ab248e531ffca826412eb4220 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 13:31:41 -0700 Subject: [PATCH 086/131] fix test --- test/new_tests/test_cdt_select.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 3952a61559..1e3ba3ab9a 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -202,7 +202,8 @@ def test_cdt_modify(self): with self.expected_context_for_pos_tests: _, _, bins = self.as_connection.operate(self.key, ops) - assert bins[self.MAP_OF_NESTED_MAPS_BIN_NAME] == [14.990000, 5.0000, 34.000000, 12.990000, 19.990000, 2.000000] + expected_results = [x - 5.0 for x in [14.990000, 5.0000, 34.000000, 12.990000, 19.990000, 2.000000]] + assert bins[self.MAP_OF_NESTED_MAPS_BIN_NAME] == expected_results @pytest.mark.parametrize( From 5c6eca0ef89067867349b7b432330277e9c27a40 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 13:41:06 -0700 Subject: [PATCH 087/131] Finish SELECT_TREE flag test --- test/new_tests/test_cdt_select.py | 38 ++++++++++++++++++------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 1e3ba3ab9a..300d1bb8ea 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -9,6 +9,7 @@ from aerospike import exception as e from contextlib import nullcontext from .test_base_class import TestBaseClass +import copy @pytest.fixture(scope="class", autouse=True) @@ -155,17 +156,18 @@ def test_cdt_select_basic_functionality(self, op, expected_bins): _, _, bins = self.as_connection.operate(self.key, ops) assert bins == expected_bins + FILTER_EXPR = GE( + LoopVarFloat(aerospike.EXP_LOOPVAR_VALUE), + 20.0 + ).compile() + def test_cdt_select_with_filter(self): - expr = GE( - LoopVarFloat(aerospike.EXP_LOOPVAR_VALUE), - 20.0 - ).compile() ops = [ operations.select_by_path( name=self.MAP_OF_NESTED_MAPS_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all_children(), - cdt_ctx.cdt_ctx_all_children_with_filter(expression=expr) + cdt_ctx.cdt_ctx_all_children_with_filter(expression=self.FILTER_EXPR) ], flags=aerospike.CDT_SELECT_VALUES ) @@ -207,30 +209,34 @@ def test_cdt_modify(self): @pytest.mark.parametrize( - "flags, expected_bins", [ - (aerospike.CDT_SELECT_MATCHING_TREE, {}) - # TODO: combine? - # (aerospike.CDT_SELECT_LEAF_LIST_VALUE,) - # TODO: bad naming? - # (aerospike.CDT_SELECT_LEAF_MAP_VALUE,) - # (aerospike.CDT_SELECT_LEAF_MAP_KEY,) + "flags", [ + (aerospike.CDT_SELECT_MATCHING_TREE) ] ) - def test_cdt_select_flags(self, flags, expected_bins): + def test_cdt_select_flag_matching_tree(self, flags): ops = [ operations.select_by_path( - name=self.LIST_BIN_NAME, + name=self.MAP_OF_NESTED_MAPS_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all_children(), - cdt_ctx.cdt_ctx_all_children() + cdt_ctx.cdt_ctx_all_children_with_filter(expression=self.FILTER_EXPR) ], # TODO: not done flags=flags ) ] + with self.expected_context_for_pos_tests: _, _, bins = self.as_connection.operate(self.key, ops) - assert bins == expected_bins + + expected_bin_value = copy.deepcopy(self.BINS_FOR_CDT_SELECT_TEST[self.MAP_OF_NESTED_MAPS_BIN_NAME]) + + # Remove all nodes that are filtered out (dict key-value pairs) + expected_bin_value["Day1"].clear() + del expected_bin_value["Day2"]["game"] + expected_bin_value["Day3"].clear() + + assert bins == {self.MAP_OF_NESTED_MAPS_BIN_NAME: expected_bin_value} # TODO: set default for BUILTIN From d41843caafb322b86a9a52b8edcd24d045e32de6 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 13:45:45 -0700 Subject: [PATCH 088/131] Add flag map key test --- aerospike-stubs/aerospike.pyi | 1 + test/new_tests/test_cdt_select.py | 28 ++++++++++++++++++++-------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/aerospike-stubs/aerospike.pyi b/aerospike-stubs/aerospike.pyi index 8ecc3ea0f6..d41784bb88 100644 --- a/aerospike-stubs/aerospike.pyi +++ b/aerospike-stubs/aerospike.pyi @@ -311,6 +311,7 @@ TXN_STATE_ABORTED: Literal[3] CDT_SELECT_MATCHING_TREE: Literal[0] CDT_SELECT_VALUES: Literal[1] +# TODO: not implemented correctly? CDT_SELECT_MAP_KEY_VALUES: Literal[1] CDT_SELECT_MAP_KEYS: Literal[2] CDT_SELECT_NO_FAIL: Literal[16] diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 300d1bb8ea..7c9207177c 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -208,12 +208,9 @@ def test_cdt_modify(self): assert bins[self.MAP_OF_NESTED_MAPS_BIN_NAME] == expected_results - @pytest.mark.parametrize( - "flags", [ - (aerospike.CDT_SELECT_MATCHING_TREE) - ] - ) - def test_cdt_select_flag_matching_tree(self, flags): + # Test cdt select flags + + def test_cdt_select_flag_matching_tree(self): ops = [ operations.select_by_path( name=self.MAP_OF_NESTED_MAPS_BIN_NAME, @@ -221,8 +218,7 @@ def test_cdt_select_flag_matching_tree(self, flags): cdt_ctx.cdt_ctx_all_children(), cdt_ctx.cdt_ctx_all_children_with_filter(expression=self.FILTER_EXPR) ], - # TODO: not done - flags=flags + flags=aerospike.CDT_SELECT_MATCHING_TREE ) ] @@ -238,6 +234,22 @@ def test_cdt_select_flag_matching_tree(self, flags): assert bins == {self.MAP_OF_NESTED_MAPS_BIN_NAME: expected_bin_value} + def test_cdt_select_flag_map_keys(self): + ops = [ + operations.select_by_path( + name=self.MAP_OF_NESTED_MAPS_BIN_NAME, + ctx=[ + cdt_ctx.cdt_ctx_all_children(), + cdt_ctx.cdt_ctx_all_children() + ], + flags=aerospike.CDT_SELECT_MAP_KEYS + ) + ] + + with self.expected_context_for_pos_tests: + _, _, bins = self.as_connection.operate(self.key, ops) + assert bins == {self.MAP_OF_NESTED_MAPS_BIN_NAME: ["book", "ferry", "food", "game", "plants", "stickers"]} + # TODO: set default for BUILTIN # TODO: negative case where cdt_select gets a var type not expected From a15517ea8ecc5f8c7326f7e1cb6384e11aeb6868 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 13:50:33 -0700 Subject: [PATCH 089/131] Add cdt_modify flags. use those in test instead of cdt_select flags --- aerospike-client-c | 2 +- src/main/aerospike.c | 3 +++ test/new_tests/test_cdt_select.py | 10 ++++++---- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/aerospike-client-c b/aerospike-client-c index 547c2f3c16..c087c1dd81 160000 --- a/aerospike-client-c +++ b/aerospike-client-c @@ -1 +1 @@ -Subproject commit 547c2f3c164d73aa51fdf304193ffbaeb6ef055a +Subproject commit c087c1dd81fc21b75d51d263dfdac55096696ad1 diff --git a/src/main/aerospike.c b/src/main/aerospike.c index 28a5ea2cf1..935250e1e8 100644 --- a/src/main/aerospike.c +++ b/src/main/aerospike.c @@ -568,6 +568,9 @@ static struct module_constant_name_to_value module_constants[] = { EXPOSE_AS_MACRO_WITHOUT_AS_PREFIX_AS_PUBLIC_FIELD(CDT_SELECT_MAP_KEYS), EXPOSE_AS_MACRO_WITHOUT_AS_PREFIX_AS_PUBLIC_FIELD(CDT_SELECT_NO_FAIL), + EXPOSE_AS_MACRO_WITHOUT_AS_PREFIX_AS_PUBLIC_FIELD(CDT_MODIFY_NO_FAIL), + EXPOSE_AS_MACRO_WITHOUT_AS_PREFIX_AS_PUBLIC_FIELD(CDT_MODIFY_DEFAULT), + // For aerospike_helpers to use. Not to be exposed in public API // TODO: move all internal constants used by aerospike_helpers to this loc diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 7c9207177c..45027d5aba 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -179,7 +179,11 @@ def test_cdt_select_with_filter(self): self.BINS_FOR_CDT_SELECT_TEST[self.MAP_OF_NESTED_MAPS_BIN_NAME]["Day2"]["food"] ] - def test_cdt_modify(self): + @pytest.mark.parametrize("flags", [ + aerospike.CDT_MODIFY_NO_FAIL, + aerospike.CDT_MODIFY_DEFAULT, + ]) + def test_cdt_modify(self, flags): mod_expr = Sub(LoopVarFloat(aerospike.EXP_LOOPVAR_VALUE), 5.0).compile() ops = [ operations.modify_by_path( @@ -189,8 +193,7 @@ def test_cdt_modify(self): cdt_ctx.cdt_ctx_all_children() ], expr=mod_expr, - # TODO: should have flag for FAIL - flags=aerospike.CDT_SELECT_NO_FAIL + flags=flags ), operations.select_by_path( name=self.MAP_OF_NESTED_MAPS_BIN_NAME, @@ -252,7 +255,6 @@ def test_cdt_select_flag_map_keys(self): # TODO: set default for BUILTIN - # TODO: negative case where cdt_select gets a var type not expected @pytest.mark.parametrize( "op, context", [ From 301dcb2cbe64cf1cc21a882028c185bd4d280795 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 14:03:56 -0700 Subject: [PATCH 090/131] fix stubs --- aerospike-stubs/aerospike.pyi | 3 +++ 1 file changed, 3 insertions(+) diff --git a/aerospike-stubs/aerospike.pyi b/aerospike-stubs/aerospike.pyi index d41784bb88..4d360c651c 100644 --- a/aerospike-stubs/aerospike.pyi +++ b/aerospike-stubs/aerospike.pyi @@ -316,6 +316,9 @@ CDT_SELECT_MAP_KEY_VALUES: Literal[1] CDT_SELECT_MAP_KEYS: Literal[2] CDT_SELECT_NO_FAIL: Literal[16] +CDT_MODIFY_DEFAULT: Literal[0] +CDT_MODIFY_NO_FAIL: Literal[0x10] + EXP_LOOPVAR_KEY: Literal[0] EXP_LOOPVAR_VALUE: Literal[1] EXP_LOOPVAR_INDEX: Literal[2] From dd54248024fb265b4727fa1fc1750dcaeac26e96 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 14:32:23 -0700 Subject: [PATCH 091/131] Add tests for all LoopVar types --- aerospike_helpers/cdt_ctx.py | 1 - test/new_tests/test_cdt_select.py | 69 ++++++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/aerospike_helpers/cdt_ctx.py b/aerospike_helpers/cdt_ctx.py index 81dfe6b657..d02f752a34 100644 --- a/aerospike_helpers/cdt_ctx.py +++ b/aerospike_helpers/cdt_ctx.py @@ -314,5 +314,4 @@ def cdt_ctx_all_children_with_filter(expression: "TypeExpression") -> _cdt_ctx: Args: expression: compiled aerospike expression """ - # TODO: make the same as helper that takes in expr value from dict return _cdt_ctx(id=aerospike._CDT_CTX_EXP, extra_args={aerospike._CDT_CTX_ALL_CHILDREN_WITH_FILTER_EXPR_KEY: expression}) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 45027d5aba..62a18c02b8 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -3,7 +3,9 @@ import aerospike from aerospike_helpers.operations import operations from aerospike_helpers.expressions.resources import ResultType -from aerospike_helpers.expressions.base import GE, Eq, LoopVarStr, LoopVarFloat +from aerospike_helpers.expressions.base import GE, Eq, LoopVarStr, LoopVarFloat, LoopVarInt, LoopVarMap, LoopVarList +from aerospike_helpers.expressions.map import MapGetByKey +from aerospike_helpers.expressions.list import ListSize from aerospike_helpers.expressions.arithmetic import Sub from aerospike_helpers import cdt_ctx from aerospike import exception as e @@ -25,6 +27,8 @@ class TestCDTSelectOperations: MAP_BIN_NAME = "map_bin" LIST_BIN_NAME = "list_bin" MAP_OF_NESTED_MAPS_BIN_NAME = "map_of_maps_bin" + NESTED_LIST_BIN_NAME = "list_of_lists" + BINS_FOR_CDT_SELECT_TEST = { MAP_BIN_NAME: { "a": 1, @@ -51,6 +55,11 @@ class TestCDTSelectOperations: "d": 4 } ], + NESTED_LIST_BIN_NAME: [ + [1, 2, 3], + [4, 5], + [6] + ], MAP_OF_NESTED_MAPS_BIN_NAME: { "Day1": { "book": 14.990000, @@ -179,6 +188,64 @@ def test_cdt_select_with_filter(self): self.BINS_FOR_CDT_SELECT_TEST[self.MAP_OF_NESTED_MAPS_BIN_NAME]["Day2"]["food"] ] + @pytest.mark.parametrize( + "filter_expr, expected_bin_value", + [ + pytest.param( + GE(LoopVarInt(aerospike.EXP_LOOPVAR_VALUE), 2), + # Should filter out 1 + [2] + ), + # At the first level below root, filter out all maps where "bb" does not have + # an int value greater than 10 + pytest.param( + GE( + expr0=MapGetByKey( + ctx=None, + return_type=aerospike.MAP_RETURN_VALUE, + value_type=ResultType.INTEGER, + key="bb", + bin=LoopVarMap(aerospike.EXP_LOOPVAR_VALUE) + ), + expr1=10 + ), + [BINS_FOR_CDT_SELECT_TEST[MAP_BIN_NAME]["ab"]] + ) + ] + ) + def test_exp_loopvar_int_and_map(self, filter_expr, expected_bin_value): + ops = [ + operations.select_by_path( + name=self.MAP_BIN_NAME, + ctx=[ + cdt_ctx.cdt_ctx_all_children_with_filter(expression=filter_expr.compile()) + ], + flags=aerospike.CDT_SELECT_VALUES | aerospike.CDT_SELECT_NO_FAIL + ) + ] + with self.expected_context_for_pos_tests: + _, _, bins = self.as_connection.operate(self.key, ops) + assert bins[self.MAP_BIN_NAME] == expected_bin_value + + LIST_SIZE_GE_TWO_EXPR = GE(ListSize(ctx=None, bin=LoopVarList(aerospike.CDT_SELECT_VALUES)), 2) + + def test_exp_loopvar_list(self): + ops = [ + operations.select_by_path( + name=self.NESTED_LIST_BIN_NAME, + ctx=[ + cdt_ctx.cdt_ctx_all_children_with_filter(expression=self.LIST_SIZE_GE_TWO_EXPR.compile()) + ], + flags=aerospike.CDT_SELECT_VALUES + ) + ] + with self.expected_context_for_pos_tests: + _, _, bins = self.as_connection.operate(self.key, ops) + assert bins[self.NESTED_LIST_BIN_NAME] == [ + [1, 2, 3], + [4, 5] + ] + @pytest.mark.parametrize("flags", [ aerospike.CDT_MODIFY_NO_FAIL, aerospike.CDT_MODIFY_DEFAULT, From a8d925525b54c3e4410f70361afe88d11ddaad37 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 15:04:07 -0700 Subject: [PATCH 092/131] Attempting to test exprs... --- aerospike-client-c | 2 +- aerospike_helpers/expressions/base.py | 6 ++--- test/new_tests/test_cdt_select.py | 33 +++++++++++++++++++++++---- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/aerospike-client-c b/aerospike-client-c index c087c1dd81..3f15b94be5 160000 --- a/aerospike-client-c +++ b/aerospike-client-c @@ -1 +1 @@ -Subproject commit c087c1dd81fc21b75d51d263dfdac55096696ad1 +Subproject commit 3f15b94be5aef435e5ae6681953638b680b91a6e diff --git a/aerospike_helpers/expressions/base.py b/aerospike_helpers/expressions/base.py index 814aae9444..a0e128822a 100644 --- a/aerospike_helpers/expressions/base.py +++ b/aerospike_helpers/expressions/base.py @@ -1098,7 +1098,7 @@ class LoopVarInt(_LoopVar): _op = aerospike._AS_EXP_LOOPVAR_INT -class CDTSelect(_BaseExpr): +class SelectByPath(_BaseExpr): """ """ _op = aerospike._AS_EXP_CODE_CALL_SELECT @@ -1106,7 +1106,7 @@ class CDTSelect(_BaseExpr): # TODO: document to be certain constants? # TODO: result_type not needed? # TODO: why return type needed? - def __init__(self, ctx: _cdt_ctx, return_type: ResultType, flags: int, bin: str): + def __init__(self, ctx: list[_cdt_ctx], return_type: ResultType, flags: int, bin: str): """Args: `ctx`: TODO @@ -1126,7 +1126,7 @@ def __init__(self, ctx: _cdt_ctx, return_type: ResultType, flags: int, bin: str) # self._fixed[_Keys.CTX_KEY] = ctx -class CDTApply(_BaseExpr): +class ModifyByPath(_BaseExpr): """ asdf """ diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 62a18c02b8..4191e31271 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -3,10 +3,11 @@ import aerospike from aerospike_helpers.operations import operations from aerospike_helpers.expressions.resources import ResultType -from aerospike_helpers.expressions.base import GE, Eq, LoopVarStr, LoopVarFloat, LoopVarInt, LoopVarMap, LoopVarList +from aerospike_helpers.expressions.base import GE, Eq, LoopVarStr, LoopVarFloat, LoopVarInt, LoopVarMap, LoopVarList, ModifyByPath, SelectByPath from aerospike_helpers.expressions.map import MapGetByKey from aerospike_helpers.expressions.list import ListSize from aerospike_helpers.expressions.arithmetic import Sub +from aerospike_helpers.operations import expression_operations as expr_ops from aerospike_helpers import cdt_ctx from aerospike import exception as e from contextlib import nullcontext @@ -246,12 +247,16 @@ def test_exp_loopvar_list(self): [4, 5] ] + + MOD_EXPR = Sub(LoopVarFloat(aerospike.EXP_LOOPVAR_VALUE), 5.0).compile() + # Expected results + SECOND_LEVEL_INTEGERS_MINUS_FIVE = [x - 5.0 for x in [14.990000, 5.0000, 34.000000, 12.990000, 19.990000, 2.000000]] + @pytest.mark.parametrize("flags", [ aerospike.CDT_MODIFY_NO_FAIL, aerospike.CDT_MODIFY_DEFAULT, ]) def test_cdt_modify(self, flags): - mod_expr = Sub(LoopVarFloat(aerospike.EXP_LOOPVAR_VALUE), 5.0).compile() ops = [ operations.modify_by_path( name=self.MAP_OF_NESTED_MAPS_BIN_NAME, @@ -259,7 +264,7 @@ def test_cdt_modify(self, flags): cdt_ctx.cdt_ctx_all_children(), cdt_ctx.cdt_ctx_all_children() ], - expr=mod_expr, + expr=self.MOD_EXPR, flags=flags ), operations.select_by_path( @@ -274,8 +279,7 @@ def test_cdt_modify(self, flags): with self.expected_context_for_pos_tests: _, _, bins = self.as_connection.operate(self.key, ops) - expected_results = [x - 5.0 for x in [14.990000, 5.0000, 34.000000, 12.990000, 19.990000, 2.000000]] - assert bins[self.MAP_OF_NESTED_MAPS_BIN_NAME] == expected_results + assert bins[self.MAP_OF_NESTED_MAPS_BIN_NAME] == self.SECOND_LEVEL_INTEGERS_MINUS_FIVE # Test cdt select flags @@ -355,3 +359,22 @@ def test_cdt_select_negative_cases(self, op, context): ] with context: self.as_connection.operate(self.key, ops) + + def test_exp_select_by_path(self): + ctx=[ + cdt_ctx.cdt_ctx_all_children(), + cdt_ctx.cdt_ctx_all_children() + ], + + modify_expr = ModifyByPath(ctx=ctx, return_type=ResultType.LIST, mod_exp=self.MOD_EXPR, flags=aerospike.CDT_MODIFY_DEFAULT, bin=self.MAP_OF_NESTED_MAPS_BIN_NAME).compile() + select_expr = SelectByPath(ctx=ctx, return_type=ResultType.LIST, flags=aerospike.EXP_LOOPVAR_VALUE, bin=self.MAP_OF_NESTED_MAPS_BIN_NAME).compile() + ops = [ + expr_ops.expression_write(bin_name=self.MAP_OF_NESTED_MAPS_BIN_NAME, expression=modify_expr), + expr_ops.expression_read(bin_name=self.MAP_OF_NESTED_MAPS_BIN_NAME, expression=select_expr) + + ] + with self.expected_context_for_pos_tests: + _, _, bins = self.as_connection.operate(self.key, ops) + assert bins[self.MAP_OF_NESTED_MAPS_BIN_NAME] == self.SECOND_LEVEL_INTEGERS_MINUS_FIVE + + # TODO: aerospike.EXP_LOOPVAR_* tests From 68190633f5f20f284872d58211ae3e4ad6371540 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 15:07:03 -0700 Subject: [PATCH 093/131] fix syntax --- test/new_tests/test_cdt_select.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 4191e31271..ef8fa8a00d 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -364,7 +364,7 @@ def test_exp_select_by_path(self): ctx=[ cdt_ctx.cdt_ctx_all_children(), cdt_ctx.cdt_ctx_all_children() - ], + ] modify_expr = ModifyByPath(ctx=ctx, return_type=ResultType.LIST, mod_exp=self.MOD_EXPR, flags=aerospike.CDT_MODIFY_DEFAULT, bin=self.MAP_OF_NESTED_MAPS_BIN_NAME).compile() select_expr = SelectByPath(ctx=ctx, return_type=ResultType.LIST, flags=aerospike.EXP_LOOPVAR_VALUE, bin=self.MAP_OF_NESTED_MAPS_BIN_NAME).compile() From 8c7b45622c68ca6b3eec3ad4b26d64e4c77db254 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 15:10:32 -0700 Subject: [PATCH 094/131] Have both cdt select and apply use the same map key for flags --- aerospike_helpers/expressions/base.py | 4 ++-- aerospike_helpers/operations/operations.py | 4 ++-- src/include/policy.h | 4 ++-- src/main/aerospike.c | 3 +-- src/main/client/operate.c | 4 ++-- src/main/convert_expressions.c | 5 ++--- 6 files changed, 11 insertions(+), 13 deletions(-) diff --git a/aerospike_helpers/expressions/base.py b/aerospike_helpers/expressions/base.py index a0e128822a..50f0f1c599 100644 --- a/aerospike_helpers/expressions/base.py +++ b/aerospike_helpers/expressions/base.py @@ -1117,7 +1117,7 @@ def __init__(self, ctx: list[_cdt_ctx], return_type: ResultType, flags: int, bin self._fixed = { _Keys.RETURN_TYPE_KEY: return_type, _Keys.CTX_KEY: ctx, - aerospike._CDT_SELECT_FLAGS_KEY: flags, + aerospike._CDT_FLAGS_KEY: flags, _Keys.BIN_KEY: bin } @@ -1143,7 +1143,7 @@ def __init__(self, ctx: _cdt_ctx, return_type: ResultType, mod_exp, flags: int, self._fixed = { _Keys.RETURN_TYPE_KEY: return_type, _Keys.CTX_KEY: ctx, - aerospike._CDT_APPLY_FLAGS_KEY: flags, + aerospike._CDT_FLAGS_KEY: flags, _Keys.BIN_KEY: bin, aerospike._CDT_APPLY_MOD_EXP_KEY: mod_exp } diff --git a/aerospike_helpers/operations/operations.py b/aerospike_helpers/operations/operations.py index 636aef0050..c9aebdb3f7 100644 --- a/aerospike_helpers/operations/operations.py +++ b/aerospike_helpers/operations/operations.py @@ -142,7 +142,7 @@ def select_by_path(name: str, ctx: list, flags: int): Returns: A dictionary to be passed to operate or operate_ordered. """ - op_dict = {"op": aerospike._AS_OPERATOR_CDT_READ, "bin": name, "ctx": ctx, aerospike._CDT_SELECT_FLAGS_KEY: flags} + op_dict = {"op": aerospike._AS_OPERATOR_CDT_READ, "bin": name, "ctx": ctx, aerospike._CDT_FLAGS_KEY: flags} return op_dict @@ -157,6 +157,6 @@ def modify_by_path(name: str, ctx: list, expr: TypeExpression, flags: int): "op": aerospike._AS_OPERATOR_CDT_MODIFY, "bin": name, "ctx": ctx, aerospike._CDT_APPLY_MOD_EXP_KEY: expr, - aerospike._CDT_SELECT_FLAGS_KEY: flags + aerospike._CDT_FLAGS_KEY: flags } return op_dict diff --git a/src/include/policy.h b/src/include/policy.h index ebdcdaa179..0b1d44f864 100644 --- a/src/include/policy.h +++ b/src/include/policy.h @@ -187,8 +187,8 @@ enum { _AS_EXP_LOOPVAR_STR }; -#define _CDT_SELECT_FLAGS_KEY "cdt_select_flags" -#define _CDT_APPLY_FLAGS_KEY "cdt_apply_flags" +// Can be either for select or apply +#define _CDT_FLAGS_KEY "cdt_flags" #define _CDT_APPLY_MOD_EXP_KEY "mod_exp" #define _CDT_CTX_ALL_CHILDREN_WITH_FILTER_EXPR_KEY "filter_expr" diff --git a/src/main/aerospike.c b/src/main/aerospike.c index 935250e1e8..932d0b5328 100644 --- a/src/main/aerospike.c +++ b/src/main/aerospike.c @@ -585,8 +585,7 @@ static struct module_constant_name_to_value module_constants[] = { EXPOSE_MACRO(_AS_EXP_CODE_CALL_SELECT), EXPOSE_MACRO(_AS_EXP_CODE_CALL_APPLY), - EXPOSE_STRING_MACRO_FOR_AEROSPIKE_HELPERS(_CDT_SELECT_FLAGS_KEY), - EXPOSE_STRING_MACRO_FOR_AEROSPIKE_HELPERS(_CDT_APPLY_FLAGS_KEY), + EXPOSE_STRING_MACRO_FOR_AEROSPIKE_HELPERS(_CDT_FLAGS_KEY), EXPOSE_STRING_MACRO_FOR_AEROSPIKE_HELPERS(_CDT_APPLY_MOD_EXP_KEY), EXPOSE_STRING_MACRO_FOR_AEROSPIKE_HELPERS( diff --git a/src/main/client/operate.c b/src/main/client/operate.c index 653f48dc2e..b7af1e0cbf 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -384,6 +384,7 @@ as_status add_op(AerospikeClient *self, as_error *err, ops, operation, SERIALIZER_PYTHON); } + // TODO: Use set instead of this? while (PyDict_Next(py_operation_dict, &pos, &key_op, &value)) { if (!PyUnicode_Check(key_op)) { return as_error_update(err, AEROSPIKE_ERR_CLIENT, @@ -428,11 +429,10 @@ as_status add_op(AerospikeClient *self, as_error *err, else if (strcmp(name, "persist_index") == 0) { py_persist_index = value; } - // TODO: Use set instead of this? else if (strcmp(name, _CDT_APPLY_MOD_EXP_KEY) == 0) { continue; } - else if (strcmp(name, _CDT_SELECT_FLAGS_KEY) == 0) { + else if (strcmp(name, _CDT_FLAGS_KEY) == 0) { continue; } else { diff --git a/src/main/convert_expressions.c b/src/main/convert_expressions.c index e0849f07ca..e7f4797a8e 100644 --- a/src/main/convert_expressions.c +++ b/src/main/convert_expressions.c @@ -1661,9 +1661,8 @@ add_expr_macros(AerospikeClient *self, as_static_pool *static_pool, return err->code; } - // TODO: rm apply key - if (get_int64_t(err, _CDT_SELECT_FLAGS_KEY, temp_expr->pydict, - &lval2) != AEROSPIKE_OK) { + if (get_int64_t(err, _CDT_FLAGS_KEY, temp_expr->pydict, &lval2) != + AEROSPIKE_OK) { return err->code; } From 2cfa250ea0359fd3a06e38f86ea60ba15cbad7aa Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 15:12:36 -0700 Subject: [PATCH 095/131] fix --- src/main/client/operate.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/client/operate.c b/src/main/client/operate.c index b7af1e0cbf..6a8254808a 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -566,8 +566,8 @@ as_status add_op(AerospikeClient *self, as_error *err, // TODO: set module constant for flags str PyObject *py_flags = NULL; // TODO: already a retval var previously? - int retval = PyDict_GetItemStringRef(py_operation_dict, - _CDT_SELECT_FLAGS_KEY, &py_flags); + int retval = PyDict_GetItemStringRef(py_operation_dict, _CDT_FLAGS_KEY, + &py_flags); if (retval == 0) { as_error_update(err, AEROSPIKE_ERR_PARAM, "CDT operation is missing a flags argument"); From dcda5e8fbb86ef4b195798203a0db62637c9b0af Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 19:24:14 -0700 Subject: [PATCH 096/131] For cdt select/apply exprs, pass in bin expr instead of bin name --- aerospike_helpers/expressions/base.py | 8 ++++---- src/main/convert_expressions.c | 14 ++++---------- test/new_tests/test_cdt_select.py | 7 ++++--- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/aerospike_helpers/expressions/base.py b/aerospike_helpers/expressions/base.py index 50f0f1c599..42affbd390 100644 --- a/aerospike_helpers/expressions/base.py +++ b/aerospike_helpers/expressions/base.py @@ -1106,7 +1106,7 @@ class SelectByPath(_BaseExpr): # TODO: document to be certain constants? # TODO: result_type not needed? # TODO: why return type needed? - def __init__(self, ctx: list[_cdt_ctx], return_type: ResultType, flags: int, bin: str): + def __init__(self, ctx: list[_cdt_ctx], return_type: ResultType, flags: int, bin: _BaseExpr): """Args: `ctx`: TODO @@ -1118,8 +1118,8 @@ def __init__(self, ctx: list[_cdt_ctx], return_type: ResultType, flags: int, bin _Keys.RETURN_TYPE_KEY: return_type, _Keys.CTX_KEY: ctx, aerospike._CDT_FLAGS_KEY: flags, - _Keys.BIN_KEY: bin } + self._children = (bin,) # TODO # if ctx is not None: @@ -1134,7 +1134,7 @@ class ModifyByPath(_BaseExpr): # TODO: document to be certain constants? # TODO: why return type needed? this returns the whole bin after being modified? - def __init__(self, ctx: _cdt_ctx, return_type: ResultType, mod_exp, flags: int, bin: str): + def __init__(self, ctx: _cdt_ctx, return_type: ResultType, mod_exp, flags: int, bin: _BaseExpr): """Args: `ctx`: TODO @@ -1144,9 +1144,9 @@ def __init__(self, ctx: _cdt_ctx, return_type: ResultType, mod_exp, flags: int, _Keys.RETURN_TYPE_KEY: return_type, _Keys.CTX_KEY: ctx, aerospike._CDT_FLAGS_KEY: flags, - _Keys.BIN_KEY: bin, aerospike._CDT_APPLY_MOD_EXP_KEY: mod_exp } + self._children = (bin,) # TODO # if ctx is not None: diff --git a/src/main/convert_expressions.c b/src/main/convert_expressions.c index e7f4797a8e..a144d84ecf 100644 --- a/src/main/convert_expressions.c +++ b/src/main/convert_expressions.c @@ -1656,11 +1656,6 @@ add_expr_macros(AerospikeClient *self, as_static_pool *static_pool, return err->code; } - if (get_bin(err, temp_expr->pydict, unicodeStrVector, &bin_name) != - AEROSPIKE_OK) { - return err->code; - } - if (get_int64_t(err, _CDT_FLAGS_KEY, temp_expr->pydict, &lval2) != AEROSPIKE_OK) { return err->code; @@ -1681,13 +1676,12 @@ add_expr_macros(AerospikeClient *self, as_static_pool *static_pool, return err->code; } - APPEND_ARRAY(0, - as_exp_modify_by_path(temp_expr->ctx, lval1, - mod_exp, lval2, BIN_EXPR())); + APPEND_ARRAY(1, as_exp_modify_by_path(temp_expr->ctx, lval1, + mod_exp, lval2, NIL)); } else { - APPEND_ARRAY(0, as_exp_select_by_path(temp_expr->ctx, lval1, - lval2, BIN_EXPR())); + APPEND_ARRAY(1, as_exp_select_by_path(temp_expr->ctx, lval1, + lval2, NIL)); } break; default: diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index ef8fa8a00d..d52b0a6fac 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -3,7 +3,7 @@ import aerospike from aerospike_helpers.operations import operations from aerospike_helpers.expressions.resources import ResultType -from aerospike_helpers.expressions.base import GE, Eq, LoopVarStr, LoopVarFloat, LoopVarInt, LoopVarMap, LoopVarList, ModifyByPath, SelectByPath +from aerospike_helpers.expressions.base import GE, Eq, LoopVarStr, LoopVarFloat, LoopVarInt, LoopVarMap, LoopVarList, ModifyByPath, SelectByPath, MapBin from aerospike_helpers.expressions.map import MapGetByKey from aerospike_helpers.expressions.list import ListSize from aerospike_helpers.expressions.arithmetic import Sub @@ -366,8 +366,9 @@ def test_exp_select_by_path(self): cdt_ctx.cdt_ctx_all_children() ] - modify_expr = ModifyByPath(ctx=ctx, return_type=ResultType.LIST, mod_exp=self.MOD_EXPR, flags=aerospike.CDT_MODIFY_DEFAULT, bin=self.MAP_OF_NESTED_MAPS_BIN_NAME).compile() - select_expr = SelectByPath(ctx=ctx, return_type=ResultType.LIST, flags=aerospike.EXP_LOOPVAR_VALUE, bin=self.MAP_OF_NESTED_MAPS_BIN_NAME).compile() + bin_expr=MapBin(bin=self.MAP_OF_NESTED_MAPS_BIN_NAME) + modify_expr = ModifyByPath(ctx=ctx, return_type=ResultType.LIST, mod_exp=self.MOD_EXPR, flags=aerospike.CDT_MODIFY_DEFAULT, bin=bin_expr).compile() + select_expr = SelectByPath(ctx=ctx, return_type=ResultType.LIST, flags=aerospike.EXP_LOOPVAR_VALUE, bin=bin_expr).compile() ops = [ expr_ops.expression_write(bin_name=self.MAP_OF_NESTED_MAPS_BIN_NAME, expression=modify_expr), expr_ops.expression_read(bin_name=self.MAP_OF_NESTED_MAPS_BIN_NAME, expression=select_expr) From f74d3c726f53e13c210e03f953cca4064c5113a1 Mon Sep 17 00:00:00 2001 From: juliannguyen4 <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 24 Oct 2025 02:32:09 +0000 Subject: [PATCH 097/131] Fix cdt modify expr test --- test/new_tests/test_cdt_select.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index d52b0a6fac..61abaf0f70 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -367,7 +367,7 @@ def test_exp_select_by_path(self): ] bin_expr=MapBin(bin=self.MAP_OF_NESTED_MAPS_BIN_NAME) - modify_expr = ModifyByPath(ctx=ctx, return_type=ResultType.LIST, mod_exp=self.MOD_EXPR, flags=aerospike.CDT_MODIFY_DEFAULT, bin=bin_expr).compile() + modify_expr = ModifyByPath(ctx=ctx, return_type=ResultType.MAP, mod_exp=self.MOD_EXPR, flags=aerospike.CDT_MODIFY_DEFAULT, bin=bin_expr).compile() select_expr = SelectByPath(ctx=ctx, return_type=ResultType.LIST, flags=aerospike.EXP_LOOPVAR_VALUE, bin=bin_expr).compile() ops = [ expr_ops.expression_write(bin_name=self.MAP_OF_NESTED_MAPS_BIN_NAME, expression=modify_expr), From 6f53e1b1d00717f7c3045045a2959a64fc48d911 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 19:50:17 -0700 Subject: [PATCH 098/131] Finish loopvar id enum tests --- test/new_tests/test_cdt_select.py | 44 ++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 61abaf0f70..a337908e18 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -378,4 +378,46 @@ def test_exp_select_by_path(self): _, _, bins = self.as_connection.operate(self.key, ops) assert bins[self.MAP_OF_NESTED_MAPS_BIN_NAME] == self.SECOND_LEVEL_INTEGERS_MINUS_FIVE - # TODO: aerospike.EXP_LOOPVAR_* tests + MAP_KEY_FILTER_EXPR = Eq(LoopVarStr(aerospike.EXP_LOOPVAR_KEY), "book").compile() + + def test_loopvar_id_map_key(self): + ops = [ + operations.select_by_path( + name=self.MAP_OF_NESTED_MAPS_BIN_NAME, + ctx=[ + cdt_ctx.cdt_ctx_all_children(), + cdt_ctx.cdt_ctx_all_children_with_filter(expression=self.MAP_KEY_FILTER_EXPR) + ], + flags=aerospike.CDT_SELECT_MATCHING_TREE + ) + ] + + with self.expected_context_for_pos_tests: + _, _, bins = self.as_connection.operate(self.key, ops) + + expected_bin_value = copy.deepcopy(self.BINS_FOR_CDT_SELECT_TEST[self.MAP_OF_NESTED_MAPS_BIN_NAME]) + + # Remove all nodes that are filtered out by dict key + del expected_bin_value["Day1"]["ferry"] + expected_bin_value["Day2"].clear() + expected_bin_value["Day3"].clear() + + assert bins == {self.MAP_OF_NESTED_MAPS_BIN_NAME: expected_bin_value} + + LIST_INDEX_FILTER_EXPR = Eq(LoopVarInt(aerospike.EXP_LOOPVAR_INDEX), 0).compile() + + def test_loopvar_id_list_index(self): + ops = [ + operations.select_by_path( + name=self.LIST_BIN_NAME, + ctx=[ + cdt_ctx.cdt_ctx_all_children_with_filter(expression=self.LIST_INDEX_FILTER_EXPR) + ], + flags=aerospike.CDT_SELECT_MATCHING_TREE + ) + ] + + with self.expected_context_for_pos_tests: + _, _, bins = self.as_connection.operate(self.key, ops) + # Return the same list, but with all list elements except at index 0 removed + assert bins == {self.LIST_BIN_NAME: [self.BINS_FOR_CDT_SELECT_TEST[self.LIST_BIN_NAME][0]]} From 6ce81daaea2e0d3697b2956b198ab29bbd6d8481 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 23 Oct 2025 19:58:30 -0700 Subject: [PATCH 099/131] Add neg test for empty ctx list passed to modify op --- test/new_tests/test_cdt_select.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index a337908e18..3886803934 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -337,7 +337,18 @@ def test_cdt_select_flag_map_keys(self): ), # TODO: vague pytest.raises(e.AerospikeError), - id="empty_ctx" + id="empty_ctx_with_select_op" + ), + pytest.param( + operations.modify_by_path( + name=MAP_BIN_NAME, + ctx=[], + expr=MOD_EXPR, + flags=aerospike.CDT_MODIFY_DEFAULT + ), + # TODO: vague + pytest.raises(e.AerospikeError), + id="empty_ctx_with_modify_op" ), pytest.param( operations.select_by_path( @@ -353,7 +364,7 @@ def test_cdt_select_flag_map_keys(self): ) ] ) - def test_cdt_select_negative_cases(self, op, context): + def test_negative_cases(self, op, context): ops = [ op ] From 59347971ca5a4867c127ddd8d88e84007573748a Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 24 Oct 2025 08:34:18 -0700 Subject: [PATCH 100/131] Add neg test for ctx=None. code should be able to handle invalid python types --- aerospike_helpers/expressions/base.py | 12 +---- test/new_tests/test_cdt_select.py | 68 +++++++++++++-------------- 2 files changed, 36 insertions(+), 44 deletions(-) diff --git a/aerospike_helpers/expressions/base.py b/aerospike_helpers/expressions/base.py index 42affbd390..bf30bd12d0 100644 --- a/aerospike_helpers/expressions/base.py +++ b/aerospike_helpers/expressions/base.py @@ -1108,7 +1108,7 @@ class SelectByPath(_BaseExpr): # TODO: why return type needed? def __init__(self, ctx: list[_cdt_ctx], return_type: ResultType, flags: int, bin: _BaseExpr): """Args: - `ctx`: TODO + `ctx`: :return: TODO """ @@ -1121,10 +1121,6 @@ def __init__(self, ctx: list[_cdt_ctx], return_type: ResultType, flags: int, bin } self._children = (bin,) - # TODO - # if ctx is not None: - # self._fixed[_Keys.CTX_KEY] = ctx - class ModifyByPath(_BaseExpr): """ @@ -1134,7 +1130,7 @@ class ModifyByPath(_BaseExpr): # TODO: document to be certain constants? # TODO: why return type needed? this returns the whole bin after being modified? - def __init__(self, ctx: _cdt_ctx, return_type: ResultType, mod_exp, flags: int, bin: _BaseExpr): + def __init__(self, ctx: list[_cdt_ctx], return_type: ResultType, mod_exp, flags: int, bin: _BaseExpr): """Args: `ctx`: TODO @@ -1147,7 +1143,3 @@ def __init__(self, ctx: _cdt_ctx, return_type: ResultType, mod_exp, flags: int, aerospike._CDT_APPLY_MOD_EXP_KEY: mod_exp } self._children = (bin,) - - # TODO - # if ctx is not None: - # self._fixed[_Keys.CTX_KEY] = ctx diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 3886803934..d2a343369a 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -326,49 +326,49 @@ def test_cdt_select_flag_map_keys(self): # TODO: set default for BUILTIN + def test_neg_iterate_on_unexpected_type(self): + op = operations.select_by_path( + name=self.MAP_BIN_NAME, + ctx=[ + cdt_ctx.cdt_ctx_all_children(), + cdt_ctx.cdt_ctx_all_children_with_filter(expression=self.EXPR_ON_DIFFERENT_ITERATED_TYPE) + ], + flags=aerospike.CDT_SELECT_VALUES + ) + ops = [ + op + ] + with pytest.raises(e.AerospikeError): + self.as_connection.operate(self.key, ops) + + @pytest.mark.parametrize("ctx_list, expected_context", [ + (None, pytest.raises(e.ParamError)), + ([], pytest.raises(e.InvalidRequest)) + ]) @pytest.mark.parametrize( - "op, context", - [ + "op_method, op_kwargs", [ pytest.param( - operations.select_by_path( - name=MAP_BIN_NAME, - ctx=[], - flags=aerospike.CDT_SELECT_VALUES - ), - # TODO: vague - pytest.raises(e.AerospikeError), - id="empty_ctx_with_select_op" + operations.select_by_path, + { + "name": MAP_BIN_NAME, + "flags": aerospike.CDT_SELECT_VALUES + } ), pytest.param( - operations.modify_by_path( - name=MAP_BIN_NAME, - ctx=[], - expr=MOD_EXPR, - flags=aerospike.CDT_MODIFY_DEFAULT - ), - # TODO: vague - pytest.raises(e.AerospikeError), - id="empty_ctx_with_modify_op" + operations.modify_by_path, + { + "name": MAP_BIN_NAME, + "expr": MOD_EXPR, + "flags": aerospike.CDT_MODIFY_DEFAULT + } ), - pytest.param( - operations.select_by_path( - name=MAP_BIN_NAME, - ctx=[ - cdt_ctx.cdt_ctx_all_children(), - cdt_ctx.cdt_ctx_all_children_with_filter(expression=EXPR_ON_DIFFERENT_ITERATED_TYPE) - ], - flags=aerospike.CDT_SELECT_VALUES - ), - pytest.raises(e.AerospikeError), - id="iterate_on_unexpected_type" - ) ] ) - def test_negative_cases(self, op, context): + def test_neg_invalid_ctx(self, ctx_list, expected_context, op_method, op_kwargs): ops = [ - op + op_method(ctx=ctx_list, **op_kwargs) ] - with context: + with expected_context: self.as_connection.operate(self.key, ops) def test_exp_select_by_path(self): From 963d196180d0412d1e82a91f7c86110079f8bbf2 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 24 Oct 2025 09:07:02 -0700 Subject: [PATCH 101/131] finish documentation. --- aerospike_helpers/cdt_ctx.py | 17 +++++-- aerospike_helpers/expressions/base.py | 46 ++++++++++++------ aerospike_helpers/expressions/resources.py | 3 +- aerospike_helpers/operations/operations.py | 25 ++++++++-- doc/aerospike.rst | 54 +++++++++++++++------- 5 files changed, 105 insertions(+), 40 deletions(-) diff --git a/aerospike_helpers/cdt_ctx.py b/aerospike_helpers/cdt_ctx.py index d02f752a34..e4d4670056 100644 --- a/aerospike_helpers/cdt_ctx.py +++ b/aerospike_helpers/cdt_ctx.py @@ -303,15 +303,26 @@ def cdt_ctx_map_key_create(key: any, order: int = 0) -> _cdt_ctx: def cdt_ctx_all_children() -> _cdt_ctx: """ - The cdt_ctx object selects all. + At the current context, causes a query to return a list of all the children + of the current item. For a map, this will recurse into the map elements. + For a list, this will include all the children in the list. + + Returns: + :class:`~aerospike_helpers.cdt_ctx._cdt_ctx` """ return _cdt_ctx(id=aerospike._CDT_CTX_EXP) def cdt_ctx_all_children_with_filter(expression: "TypeExpression") -> _cdt_ctx: """ - Select and filter using an expression. + All children of the current level will be selected, and then the filter expression + is applied to each item in turn. Items that cause the expression to evaluate to true will be added to the + list of items returned in a query for this level. Items that cause the expression to evaluate to false + will be filtered out Args: - expression: compiled aerospike expression + expression: Compiled expression. This expression must return a boolean. + + Returns: + :class:`~aerospike_helpers.cdt_ctx._cdt_ctx` """ return _cdt_ctx(id=aerospike._CDT_CTX_EXP, extra_args={aerospike._CDT_CTX_ALL_CHILDREN_WITH_FILTER_EXPR_KEY: expression}) diff --git a/aerospike_helpers/expressions/base.py b/aerospike_helpers/expressions/base.py index bf30bd12d0..1ae21c81a9 100644 --- a/aerospike_helpers/expressions/base.py +++ b/aerospike_helpers/expressions/base.py @@ -1074,7 +1074,16 @@ def __init__(self, var_name: str): class _LoopVar(_BaseExpr, ABC): + """ + Retrieve expression value from a path expression loop variable. + """ def __init__(self, var_id: int): + """ + Args: + var_id: See :ref:`exp_loopvar_metadata` for possible values. + + :return: (value stored in variable) + """ self._fixed = {_Keys.VALUE_KEY: var_id} @@ -1100,20 +1109,21 @@ class LoopVarInt(_LoopVar): class SelectByPath(_BaseExpr): """ + Constructs a select by path operation. This is used to retrieve a number of + records or fields of records, including those of structured types. """ _op = aerospike._AS_EXP_CODE_CALL_SELECT - # TODO: document to be certain constants? - # TODO: result_type not needed? - # TODO: why return type needed? def __init__(self, ctx: list[_cdt_ctx], return_type: ResultType, flags: int, bin: _BaseExpr): - """Args: - `ctx`: + """ + Args: + ctx: list of CDT contexts. This cannot be None or an empty list. + return_type: Return type specifier. + flags: See :ref:`cdt_select_flags` for possible values. + bin: Bin expression to which this expression applies. - :return: TODO + :return: (expression) """ - ''' - ''' self._fixed = { _Keys.RETURN_TYPE_KEY: return_type, _Keys.CTX_KEY: ctx, @@ -1124,17 +1134,23 @@ def __init__(self, ctx: list[_cdt_ctx], return_type: ResultType, flags: int, bin class ModifyByPath(_BaseExpr): """ - asdf + Constructs a CDT apply operation. + + The results of the evaluation of the modifying expression will replace the + selected map, and the changes are written back to storage. """ _op = aerospike._AS_EXP_CODE_CALL_APPLY - # TODO: document to be certain constants? - # TODO: why return type needed? this returns the whole bin after being modified? def __init__(self, ctx: list[_cdt_ctx], return_type: ResultType, mod_exp, flags: int, bin: _BaseExpr): - """Args: - `ctx`: TODO - - :return: TODO + """ + Args: + ctx: list of CDT contexts. This cannot be None or an empty list. + return_type: Return type specifier. + mod_exp: Compiled expression to apply. + flags: See :ref:`cdt_modify_flags` for possible values. + bin: Bin expression to which this expression applies. + + :return: (expression) """ self._fixed = { _Keys.RETURN_TYPE_KEY: return_type, diff --git a/aerospike_helpers/expressions/resources.py b/aerospike_helpers/expressions/resources.py index ffd018103a..048ae4aca0 100644 --- a/aerospike_helpers/expressions/resources.py +++ b/aerospike_helpers/expressions/resources.py @@ -113,7 +113,8 @@ class ReturnType: MAP_RETURN_INVERTED = 0x10000 -# TODO: These enum constants must match the values for C client's as_exp_type +# These enum constants must match the values for C client's as_exp_type. +# These are passed as arguments to ModifyByPath and SelectByPath expressions class ResultType: """ Flags used to indicate expression value_type. diff --git a/aerospike_helpers/operations/operations.py b/aerospike_helpers/operations/operations.py index c9aebdb3f7..599e30c5f5 100644 --- a/aerospike_helpers/operations/operations.py +++ b/aerospike_helpers/operations/operations.py @@ -25,6 +25,7 @@ from typing import Optional from aerospike_helpers.expressions.resources import TypeExpression +from aerospike_helpers.cdt_ctx import _cdt_ctx def read(bin_name): @@ -135,27 +136,41 @@ def touch(ttl: Optional[int] = None): return op_dict -def select_by_path(name: str, ctx: list, flags: int): +def select_by_path(bin_name: str, ctx: list[_cdt_ctx], flags: int): """ Create CDT select operation. + Args: + bin_name: Bin name + ctx: List of contexts to select nodes. It is an error for ctx to be :py:obj:`None` or an empty list. + flags: See :ref:`cdt_select_flags` for the set of valid flags for this function. + Returns: A dictionary to be passed to operate or operate_ordered. """ - op_dict = {"op": aerospike._AS_OPERATOR_CDT_READ, "bin": name, "ctx": ctx, aerospike._CDT_FLAGS_KEY: flags} + op_dict = {"op": aerospike._AS_OPERATOR_CDT_READ, "bin": bin_name, "ctx": ctx, aerospike._CDT_FLAGS_KEY: flags} return op_dict -def modify_by_path(name: str, ctx: list, expr: TypeExpression, flags: int): +def modify_by_path(bin_name: str, ctx: list[_cdt_ctx], expr: TypeExpression, flags: int): """ - Create CDT apply operation. + Create CDT modification operation. + + The results of the evaluation of the modifying expression will replace the + selected map, and the changes are written back to storage. + + Args: + bin_name: Bin name + ctx: List of contexts to select nodes. It is an error for ctx to be :py:obj:`None` or an empty list. + expr: compiled modifying expression. + flags: See :ref:`cdt_modify_flags` for the set of valid flags for this function. Returns: A dictionary to be passed to operate or operate_ordered. """ op_dict = { "op": aerospike._AS_OPERATOR_CDT_MODIFY, - "bin": name, + "bin": bin_name, "ctx": ctx, aerospike._CDT_APPLY_MOD_EXP_KEY: expr, aerospike._CDT_FLAGS_KEY: flags } diff --git a/doc/aerospike.rst b/doc/aerospike.rst index 6bf246c654..af439707d6 100644 --- a/doc/aerospike.rst +++ b/doc/aerospike.rst @@ -1798,33 +1798,55 @@ Transaction State CDT Select Flags ---------------- -.. data:: CDT_SELECT_TREE +.. data:: CDT_SELECT_MATCHING_TREE - Return a tree from the root (bin) level to the bottom of the tree, with only non-filtered out nodes + Return a tree from the root (bin) level to the bottom of the tree, with only non-filtered out nodes. -.. |CDT_SELECT_VALUE_DOCSTRING| replace:: Return the list of the values of the nodes finally selected by the context +.. data:: CDT_SELECT_VALUES -.. data:: CDT_SELECT_LEAF_LIST_VALUE + Return the list of the values of the nodes finally selected by the context. - |CDT_SELECT_VALUE_DOCSTRING| +.. data:: CDT_SELECT_MAP_KEY_VALUES -.. data:: CDT_SELECT_LEAF_MAP_VALUE + Return a list of key-value pairs. - |CDT_SELECT_VALUE_DOCSTRING| +.. data:: CDT_SELECT_MAP_KEYS -.. data:: CDT_SELECT_LEAF_MAP_KEY - - For final selected nodes which are elements of maps, return the appropiate map key + For final selected nodes which are elements of maps, return the appropiate map key. .. data:: CDT_SELECT_NO_FAIL - If the expression in the context hits an invalid type (eg selects as an integer when the value is a string), do not fail the operation, just ignore those elements. + If the expression in the context hits an invalid type (e.g selects as an integer when the value is a string), + do not fail the operation; just ignore those elements. + +.. _cdt_modify_flags: + +CDT Modify Flags +---------------- + +.. data:: CDT_MODIFY_DEFAULT + + If the expression in the context hits an invalid type, the operation + will fail. This is the default behavior. + +.. data:: CDT_MODIFY_NO_FAIL + + If the expression in the context hits an invalid type (e.g., selects as an integer when the value is a string), do + not fail the operation; just ignore those elements. + +.. _exp_loopvar_metadata: + +Expression Loop Variable Metadata +--------------------------------- + +.. data:: EXP_LOOPVAR_KEY + + The key associated with this value if part of a key-value pair of a map. -Expression Variable Built-in Types ----------------------------------- +.. data:: EXP_LOOPVAR_VALUE -.. data:: EXP_BUILTIN_KEY + List item, or value from a map key-value pair. -.. data:: EXP_BUILTIN_VALUE +.. data:: EXP_LOOPVAR_INDEX -.. data:: EXP_BUILTIN_INDEX + The index if this element was part of a list. From 2a6e37cb1bf4c256e3187552839c7e43b9ecfa4d Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 24 Oct 2025 09:10:32 -0700 Subject: [PATCH 102/131] try adding reference to type in param docstring. not letting me click cdt_ctx in func signature --- aerospike_helpers/operations/operations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aerospike_helpers/operations/operations.py b/aerospike_helpers/operations/operations.py index 599e30c5f5..7f3b660ea4 100644 --- a/aerospike_helpers/operations/operations.py +++ b/aerospike_helpers/operations/operations.py @@ -142,7 +142,7 @@ def select_by_path(bin_name: str, ctx: list[_cdt_ctx], flags: int): Args: bin_name: Bin name - ctx: List of contexts to select nodes. It is an error for ctx to be :py:obj:`None` or an empty list. + ctx (list[_cdt_ctx]): List of contexts to select nodes. It is an error for ctx to be :py:obj:`None` or an empty list. flags: See :ref:`cdt_select_flags` for the set of valid flags for this function. Returns: From b7868a842348c53d3594d6a971fd0118045732aa Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 24 Oct 2025 09:12:09 -0700 Subject: [PATCH 103/131] fully qualified name --- aerospike_helpers/operations/operations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aerospike_helpers/operations/operations.py b/aerospike_helpers/operations/operations.py index 7f3b660ea4..08bf0adca2 100644 --- a/aerospike_helpers/operations/operations.py +++ b/aerospike_helpers/operations/operations.py @@ -142,7 +142,7 @@ def select_by_path(bin_name: str, ctx: list[_cdt_ctx], flags: int): Args: bin_name: Bin name - ctx (list[_cdt_ctx]): List of contexts to select nodes. It is an error for ctx to be :py:obj:`None` or an empty list. + ctx (list[aerospike_helpers.cdt_ctx._cdt_ctx]): List of contexts to select nodes. It is an error for ctx to be :py:obj:`None` or an empty list. flags: See :ref:`cdt_select_flags` for the set of valid flags for this function. Returns: From 465bedf2c8663c9ea6ffad221cfd190e7cce266d Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 24 Oct 2025 09:15:59 -0700 Subject: [PATCH 104/131] Revert "fully qualified name" This reverts commit b7868a842348c53d3594d6a971fd0118045732aa. --- aerospike_helpers/operations/operations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aerospike_helpers/operations/operations.py b/aerospike_helpers/operations/operations.py index 08bf0adca2..7f3b660ea4 100644 --- a/aerospike_helpers/operations/operations.py +++ b/aerospike_helpers/operations/operations.py @@ -142,7 +142,7 @@ def select_by_path(bin_name: str, ctx: list[_cdt_ctx], flags: int): Args: bin_name: Bin name - ctx (list[aerospike_helpers.cdt_ctx._cdt_ctx]): List of contexts to select nodes. It is an error for ctx to be :py:obj:`None` or an empty list. + ctx (list[_cdt_ctx]): List of contexts to select nodes. It is an error for ctx to be :py:obj:`None` or an empty list. flags: See :ref:`cdt_select_flags` for the set of valid flags for this function. Returns: From 091147bf38d9c7ac63d6baa73a93fcf592eccf32 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 24 Oct 2025 09:18:43 -0700 Subject: [PATCH 105/131] dont need since func signature already has --- aerospike_helpers/operations/operations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aerospike_helpers/operations/operations.py b/aerospike_helpers/operations/operations.py index 7f3b660ea4..599e30c5f5 100644 --- a/aerospike_helpers/operations/operations.py +++ b/aerospike_helpers/operations/operations.py @@ -142,7 +142,7 @@ def select_by_path(bin_name: str, ctx: list[_cdt_ctx], flags: int): Args: bin_name: Bin name - ctx (list[_cdt_ctx]): List of contexts to select nodes. It is an error for ctx to be :py:obj:`None` or an empty list. + ctx: List of contexts to select nodes. It is an error for ctx to be :py:obj:`None` or an empty list. flags: See :ref:`cdt_select_flags` for the set of valid flags for this function. Returns: From ad93161f49aba15a2bd66bc97eaec0b47658b941 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 24 Oct 2025 09:20:26 -0700 Subject: [PATCH 106/131] Keyword was changed to make more readable --- test/new_tests/test_cdt_select.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index d2a343369a..b7f4d9bb0e 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -91,7 +91,7 @@ def insert_record(self): [ pytest.param( operations.select_by_path( - name=LIST_BIN_NAME, + bin_name=LIST_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all_children(), ], @@ -104,7 +104,7 @@ def insert_record(self): ), pytest.param( operations.select_by_path( - name=MAP_BIN_NAME, + bin_name=MAP_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all_children(), ], @@ -117,7 +117,7 @@ def insert_record(self): ), pytest.param( operations.select_by_path( - name=LIST_BIN_NAME, + bin_name=LIST_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all_children(), cdt_ctx.cdt_ctx_all_children() @@ -144,7 +144,7 @@ def insert_record(self): ), pytest.param( operations.select_by_path( - name=MAP_BIN_NAME, + bin_name=MAP_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all_children(), cdt_ctx.cdt_ctx_all_children_with_filter(expression=EXPR_ON_DIFFERENT_ITERATED_TYPE) @@ -174,7 +174,7 @@ def test_cdt_select_basic_functionality(self, op, expected_bins): def test_cdt_select_with_filter(self): ops = [ operations.select_by_path( - name=self.MAP_OF_NESTED_MAPS_BIN_NAME, + bin_name=self.MAP_OF_NESTED_MAPS_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all_children(), cdt_ctx.cdt_ctx_all_children_with_filter(expression=self.FILTER_EXPR) @@ -217,7 +217,7 @@ def test_cdt_select_with_filter(self): def test_exp_loopvar_int_and_map(self, filter_expr, expected_bin_value): ops = [ operations.select_by_path( - name=self.MAP_BIN_NAME, + bin_name=self.MAP_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all_children_with_filter(expression=filter_expr.compile()) ], @@ -233,7 +233,7 @@ def test_exp_loopvar_int_and_map(self, filter_expr, expected_bin_value): def test_exp_loopvar_list(self): ops = [ operations.select_by_path( - name=self.NESTED_LIST_BIN_NAME, + bin_name=self.NESTED_LIST_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all_children_with_filter(expression=self.LIST_SIZE_GE_TWO_EXPR.compile()) ], @@ -259,7 +259,7 @@ def test_exp_loopvar_list(self): def test_cdt_modify(self, flags): ops = [ operations.modify_by_path( - name=self.MAP_OF_NESTED_MAPS_BIN_NAME, + bin_name=self.MAP_OF_NESTED_MAPS_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all_children(), cdt_ctx.cdt_ctx_all_children() @@ -268,7 +268,7 @@ def test_cdt_modify(self, flags): flags=flags ), operations.select_by_path( - name=self.MAP_OF_NESTED_MAPS_BIN_NAME, + bin_name=self.MAP_OF_NESTED_MAPS_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all_children(), cdt_ctx.cdt_ctx_all_children() @@ -287,7 +287,7 @@ def test_cdt_modify(self, flags): def test_cdt_select_flag_matching_tree(self): ops = [ operations.select_by_path( - name=self.MAP_OF_NESTED_MAPS_BIN_NAME, + bin_name=self.MAP_OF_NESTED_MAPS_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all_children(), cdt_ctx.cdt_ctx_all_children_with_filter(expression=self.FILTER_EXPR) @@ -311,7 +311,7 @@ def test_cdt_select_flag_matching_tree(self): def test_cdt_select_flag_map_keys(self): ops = [ operations.select_by_path( - name=self.MAP_OF_NESTED_MAPS_BIN_NAME, + bin_name=self.MAP_OF_NESTED_MAPS_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all_children(), cdt_ctx.cdt_ctx_all_children() @@ -328,7 +328,7 @@ def test_cdt_select_flag_map_keys(self): def test_neg_iterate_on_unexpected_type(self): op = operations.select_by_path( - name=self.MAP_BIN_NAME, + bin_name=self.MAP_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all_children(), cdt_ctx.cdt_ctx_all_children_with_filter(expression=self.EXPR_ON_DIFFERENT_ITERATED_TYPE) @@ -394,7 +394,7 @@ def test_exp_select_by_path(self): def test_loopvar_id_map_key(self): ops = [ operations.select_by_path( - name=self.MAP_OF_NESTED_MAPS_BIN_NAME, + bin_name=self.MAP_OF_NESTED_MAPS_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all_children(), cdt_ctx.cdt_ctx_all_children_with_filter(expression=self.MAP_KEY_FILTER_EXPR) @@ -420,7 +420,7 @@ def test_loopvar_id_map_key(self): def test_loopvar_id_list_index(self): ops = [ operations.select_by_path( - name=self.LIST_BIN_NAME, + bin_name=self.LIST_BIN_NAME, ctx=[ cdt_ctx.cdt_ctx_all_children_with_filter(expression=self.LIST_INDEX_FILTER_EXPR) ], From a16dabf0f254d538262f1c1e8286ba199ff00f84 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 24 Oct 2025 09:23:41 -0700 Subject: [PATCH 107/131] Rm outdated TODOs --- src/main/client/operate.c | 2 -- src/main/convert_expressions.c | 1 - test/new_tests/test_cdt_select.py | 3 --- 3 files changed, 6 deletions(-) diff --git a/src/main/client/operate.c b/src/main/client/operate.c index 6a8254808a..48b42d3940 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -563,9 +563,7 @@ as_status add_op(AerospikeClient *self, as_error *err, switch (operation) { case AS_OPERATOR_CDT_READ: case AS_OPERATOR_CDT_MODIFY: { - // TODO: set module constant for flags str PyObject *py_flags = NULL; - // TODO: already a retval var previously? int retval = PyDict_GetItemStringRef(py_operation_dict, _CDT_FLAGS_KEY, &py_flags); if (retval == 0) { diff --git a/src/main/convert_expressions.c b/src/main/convert_expressions.c index a144d84ecf..83f7732919 100644 --- a/src/main/convert_expressions.c +++ b/src/main/convert_expressions.c @@ -206,7 +206,6 @@ static as_status get_expr_size(int *size_to_alloc, int *intermediate_exprs_size, { static const int EXPR_SIZES[] = { - // TODO: can also be cdt_apply() [_AS_EXP_CODE_CALL_SELECT] = EXP_SZ(as_exp_select_by_path(NULL, 0, 0, NIL)), [_AS_EXP_CODE_CALL_APPLY] = diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index b7f4d9bb0e..835258ff93 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -86,7 +86,6 @@ def insert_record(self): EXPR_ON_DIFFERENT_ITERATED_TYPE = Eq(LoopVarStr(aerospike.EXP_LOOPVAR_VALUE), "a").compile() @pytest.mark.parametrize( - # TODO: ids "op,expected_bins", [ pytest.param( @@ -324,8 +323,6 @@ def test_cdt_select_flag_map_keys(self): _, _, bins = self.as_connection.operate(self.key, ops) assert bins == {self.MAP_OF_NESTED_MAPS_BIN_NAME: ["book", "ferry", "food", "game", "plants", "stickers"]} - # TODO: set default for BUILTIN - def test_neg_iterate_on_unexpected_type(self): op = operations.select_by_path( bin_name=self.MAP_BIN_NAME, From ac603dd357693d8bc74cab8fa2811e190d276269 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 24 Oct 2025 09:55:37 -0700 Subject: [PATCH 108/131] Fix four failing tests... --- test/new_tests/test_cdt_select.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 835258ff93..f5f427cd8b 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -347,14 +347,14 @@ def test_neg_iterate_on_unexpected_type(self): pytest.param( operations.select_by_path, { - "name": MAP_BIN_NAME, + "bin_name": MAP_BIN_NAME, "flags": aerospike.CDT_SELECT_VALUES } ), pytest.param( operations.modify_by_path, { - "name": MAP_BIN_NAME, + "bin_name": MAP_BIN_NAME, "expr": MOD_EXPR, "flags": aerospike.CDT_MODIFY_DEFAULT } From cf7eaf98f6cb1fea79bb636442e6f4bbefc60bc5 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 24 Oct 2025 09:59:03 -0700 Subject: [PATCH 109/131] fix spelling --- doc/aerospike.rst | 2 +- doc/spelling_wordlist.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/aerospike.rst b/doc/aerospike.rst index af439707d6..a6a9c0f2e7 100644 --- a/doc/aerospike.rst +++ b/doc/aerospike.rst @@ -1812,7 +1812,7 @@ CDT Select Flags .. data:: CDT_SELECT_MAP_KEYS - For final selected nodes which are elements of maps, return the appropiate map key. + For final selected nodes which are elements of maps, return the appropriate map key. .. data:: CDT_SELECT_NO_FAIL diff --git a/doc/spelling_wordlist.txt b/doc/spelling_wordlist.txt index 5ea16aae29..fdae2f3d12 100644 --- a/doc/spelling_wordlist.txt +++ b/doc/spelling_wordlist.txt @@ -91,3 +91,4 @@ namespaces yaml suberror hardcoded +recurse From fb61fac0322f87d3321b830f79521c83d75a58e0 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 24 Oct 2025 10:21:19 -0700 Subject: [PATCH 110/131] fix mem leak --- src/main/convert_expressions.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/convert_expressions.c b/src/main/convert_expressions.c index 83f7732919..bcc0c93d33 100644 --- a/src/main/convert_expressions.c +++ b/src/main/convert_expressions.c @@ -1921,7 +1921,14 @@ as_status as_exp_new_from_pyobject(AerospikeClient *self, PyObject *py_expr, } as_vector_destroy(&intermediate_expr_queue); + if (c_expr_entries != NULL) { + for (int i = 0; i < size_to_alloc / sizeof(c_expr_entries); i++) { + if (c_expr_entries[i].op == _AS_EXP_CODE_MERGE) { + as_exp_destroy(c_expr_entries[i].v.expr); + } + } + free(c_expr_entries); } From 18665b2a4d688de6452fcc0f6529d78b00d745ad Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 24 Oct 2025 10:31:54 -0700 Subject: [PATCH 111/131] pointer to struct, not struct ref --- src/main/convert_expressions.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/convert_expressions.c b/src/main/convert_expressions.c index bcc0c93d33..255956bef8 100644 --- a/src/main/convert_expressions.c +++ b/src/main/convert_expressions.c @@ -1923,7 +1923,7 @@ as_status as_exp_new_from_pyobject(AerospikeClient *self, PyObject *py_expr, as_vector_destroy(&intermediate_expr_queue); if (c_expr_entries != NULL) { - for (int i = 0; i < size_to_alloc / sizeof(c_expr_entries); i++) { + for (int i = 0; i < size_to_alloc / sizeof(c_expr_entries[i]); i++) { if (c_expr_entries[i].op == _AS_EXP_CODE_MERGE) { as_exp_destroy(c_expr_entries[i].v.expr); } From 66de999ac03fdc2b14106e61b7cd0c6584c596c4 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 24 Oct 2025 10:32:54 -0700 Subject: [PATCH 112/131] most clear --- src/main/convert_expressions.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/convert_expressions.c b/src/main/convert_expressions.c index 255956bef8..b52f5ce65b 100644 --- a/src/main/convert_expressions.c +++ b/src/main/convert_expressions.c @@ -1923,7 +1923,7 @@ as_status as_exp_new_from_pyobject(AerospikeClient *self, PyObject *py_expr, as_vector_destroy(&intermediate_expr_queue); if (c_expr_entries != NULL) { - for (int i = 0; i < size_to_alloc / sizeof(c_expr_entries[i]); i++) { + for (int i = 0; i < size_to_alloc / sizeof(as_exp_entry); i++) { if (c_expr_entries[i].op == _AS_EXP_CODE_MERGE) { as_exp_destroy(c_expr_entries[i].v.expr); } From 5d34c872871a5ccc1ecf11b722c564a8ecbc2aa0 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 24 Oct 2025 10:54:44 -0700 Subject: [PATCH 113/131] Only 'bottom' number of as_exp_entry entries are actually initialized. more memory is being allocated than needed --- src/main/convert_expressions.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/convert_expressions.c b/src/main/convert_expressions.c index b52f5ce65b..c077443b53 100644 --- a/src/main/convert_expressions.c +++ b/src/main/convert_expressions.c @@ -1923,7 +1923,7 @@ as_status as_exp_new_from_pyobject(AerospikeClient *self, PyObject *py_expr, as_vector_destroy(&intermediate_expr_queue); if (c_expr_entries != NULL) { - for (int i = 0; i < size_to_alloc / sizeof(as_exp_entry); i++) { + for (int i = 0; i < bottom; i++) { if (c_expr_entries[i].op == _AS_EXP_CODE_MERGE) { as_exp_destroy(c_expr_entries[i].v.expr); } From a1d4d20919fa21a9b25f3d3c2c5f5cc632486878 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 24 Oct 2025 13:37:46 -0700 Subject: [PATCH 114/131] rm todo for now --- aerospike-stubs/aerospike.pyi | 1 - 1 file changed, 1 deletion(-) diff --git a/aerospike-stubs/aerospike.pyi b/aerospike-stubs/aerospike.pyi index 4d360c651c..44a66a625e 100644 --- a/aerospike-stubs/aerospike.pyi +++ b/aerospike-stubs/aerospike.pyi @@ -311,7 +311,6 @@ TXN_STATE_ABORTED: Literal[3] CDT_SELECT_MATCHING_TREE: Literal[0] CDT_SELECT_VALUES: Literal[1] -# TODO: not implemented correctly? CDT_SELECT_MAP_KEY_VALUES: Literal[1] CDT_SELECT_MAP_KEYS: Literal[2] CDT_SELECT_NO_FAIL: Literal[16] From 52b125bac30b3020c97afff821e38d30ce29da54 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 24 Oct 2025 13:41:16 -0700 Subject: [PATCH 115/131] Make LoopVar public so we can see the init documentation for inheriting classes --- aerospike_helpers/expressions/base.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/aerospike_helpers/expressions/base.py b/aerospike_helpers/expressions/base.py index 1ae21c81a9..a0c2be5ca4 100644 --- a/aerospike_helpers/expressions/base.py +++ b/aerospike_helpers/expressions/base.py @@ -1073,7 +1073,7 @@ def __init__(self, var_name: str): self._fixed = {_Keys.VALUE_KEY: var_name} -class _LoopVar(_BaseExpr, ABC): +class LoopVar(_BaseExpr, ABC): """ Retrieve expression value from a path expression loop variable. """ @@ -1087,23 +1087,23 @@ def __init__(self, var_id: int): self._fixed = {_Keys.VALUE_KEY: var_id} -class LoopVarMap(_LoopVar): +class LoopVarMap(LoopVar): _op = aerospike._AS_EXP_LOOPVAR_MAP -class LoopVarList(_LoopVar): +class LoopVarList(LoopVar): _op = aerospike._AS_EXP_LOOPVAR_LIST -class LoopVarStr(_LoopVar): +class LoopVarStr(LoopVar): _op = aerospike._AS_EXP_LOOPVAR_STR -class LoopVarFloat(_LoopVar): +class LoopVarFloat(LoopVar): _op = aerospike._AS_EXP_LOOPVAR_FLOAT -class LoopVarInt(_LoopVar): +class LoopVarInt(LoopVar): _op = aerospike._AS_EXP_LOOPVAR_INT From f6895f9f149ef0208c7b53a516bc2ec8e7d9c646 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 24 Oct 2025 13:43:51 -0700 Subject: [PATCH 116/131] Show that LoopVar* classes are subclasses of LoopVar class --- doc/aerospike_helpers.expressions.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/aerospike_helpers.expressions.rst b/doc/aerospike_helpers.expressions.rst index a4b024e69b..ac363d437e 100644 --- a/doc/aerospike_helpers.expressions.rst +++ b/doc/aerospike_helpers.expressions.rst @@ -167,6 +167,7 @@ aerospike\_helpers\.expressions\.base module .. automodule:: aerospike_helpers.expressions.base :members: :special-members: + :show-inheritance: aerospike\_helpers\.expressions\.list module -------------------------------------------- From 1e2d5c27563b6d38d34eeaff7d64b833c9b90ec5 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 24 Oct 2025 13:49:46 -0700 Subject: [PATCH 117/131] Remove type annotation for expr since it gets expanded in the docs --- aerospike_helpers/operations/operations.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/aerospike_helpers/operations/operations.py b/aerospike_helpers/operations/operations.py index 599e30c5f5..eda976d78c 100644 --- a/aerospike_helpers/operations/operations.py +++ b/aerospike_helpers/operations/operations.py @@ -24,7 +24,6 @@ import aerospike from typing import Optional -from aerospike_helpers.expressions.resources import TypeExpression from aerospike_helpers.cdt_ctx import _cdt_ctx @@ -152,7 +151,7 @@ def select_by_path(bin_name: str, ctx: list[_cdt_ctx], flags: int): return op_dict -def modify_by_path(bin_name: str, ctx: list[_cdt_ctx], expr: TypeExpression, flags: int): +def modify_by_path(bin_name: str, ctx: list[_cdt_ctx], expr, flags: int): """ Create CDT modification operation. From a157ae1d6c233915ce666d1959868151ce53e724 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 24 Oct 2025 14:10:20 -0700 Subject: [PATCH 118/131] Make name shorter since the file name is self-explanatory --- test/new_tests/test_cdt_select.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index f5f427cd8b..b7d2c13358 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -30,7 +30,7 @@ class TestCDTSelectOperations: MAP_OF_NESTED_MAPS_BIN_NAME = "map_of_maps_bin" NESTED_LIST_BIN_NAME = "list_of_lists" - BINS_FOR_CDT_SELECT_TEST = { + RECORD_BINS = { MAP_BIN_NAME: { "a": 1, "ab": { @@ -79,7 +79,7 @@ class TestCDTSelectOperations: @pytest.fixture(autouse=True) def insert_record(self): self.key = ("test", "demo", 1) - self.as_connection.put(self.key, bins=self.BINS_FOR_CDT_SELECT_TEST) + self.as_connection.put(self.key, bins=self.RECORD_BINS) yield self.as_connection.remove(self.key) @@ -97,7 +97,7 @@ def insert_record(self): flags=aerospike.CDT_SELECT_VALUES ), { - LIST_BIN_NAME: BINS_FOR_CDT_SELECT_TEST[LIST_BIN_NAME] + LIST_BIN_NAME: RECORD_BINS[LIST_BIN_NAME] }, id="select_all_children_once_in_list" ), @@ -110,7 +110,7 @@ def insert_record(self): flags=aerospike.CDT_SELECT_VALUES ), { - MAP_BIN_NAME: list(BINS_FOR_CDT_SELECT_TEST[MAP_BIN_NAME].values()) + MAP_BIN_NAME: list(RECORD_BINS[MAP_BIN_NAME].values()) }, id="select_all_children_once_in_map" ), @@ -185,7 +185,7 @@ def test_cdt_select_with_filter(self): _, _, bins = self.as_connection.operate(self.key, ops) assert bins[self.MAP_OF_NESTED_MAPS_BIN_NAME] == [ - self.BINS_FOR_CDT_SELECT_TEST[self.MAP_OF_NESTED_MAPS_BIN_NAME]["Day2"]["food"] + self.RECORD_BINS[self.MAP_OF_NESTED_MAPS_BIN_NAME]["Day2"]["food"] ] @pytest.mark.parametrize( @@ -209,7 +209,7 @@ def test_cdt_select_with_filter(self): ), expr1=10 ), - [BINS_FOR_CDT_SELECT_TEST[MAP_BIN_NAME]["ab"]] + [RECORD_BINS[MAP_BIN_NAME]["ab"]] ) ] ) @@ -298,7 +298,7 @@ def test_cdt_select_flag_matching_tree(self): with self.expected_context_for_pos_tests: _, _, bins = self.as_connection.operate(self.key, ops) - expected_bin_value = copy.deepcopy(self.BINS_FOR_CDT_SELECT_TEST[self.MAP_OF_NESTED_MAPS_BIN_NAME]) + expected_bin_value = copy.deepcopy(self.RECORD_BINS[self.MAP_OF_NESTED_MAPS_BIN_NAME]) # Remove all nodes that are filtered out (dict key-value pairs) expected_bin_value["Day1"].clear() @@ -403,7 +403,7 @@ def test_loopvar_id_map_key(self): with self.expected_context_for_pos_tests: _, _, bins = self.as_connection.operate(self.key, ops) - expected_bin_value = copy.deepcopy(self.BINS_FOR_CDT_SELECT_TEST[self.MAP_OF_NESTED_MAPS_BIN_NAME]) + expected_bin_value = copy.deepcopy(self.RECORD_BINS[self.MAP_OF_NESTED_MAPS_BIN_NAME]) # Remove all nodes that are filtered out by dict key del expected_bin_value["Day1"]["ferry"] @@ -428,4 +428,4 @@ def test_loopvar_id_list_index(self): with self.expected_context_for_pos_tests: _, _, bins = self.as_connection.operate(self.key, ops) # Return the same list, but with all list elements except at index 0 removed - assert bins == {self.LIST_BIN_NAME: [self.BINS_FOR_CDT_SELECT_TEST[self.LIST_BIN_NAME][0]]} + assert bins == {self.LIST_BIN_NAME: [self.RECORD_BINS[self.LIST_BIN_NAME][0]]} From 39d6632902510e842c2e418e3c5324e224e0fae8 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 24 Oct 2025 14:12:58 -0700 Subject: [PATCH 119/131] fix confusing comment --- test/new_tests/test_cdt_select.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index b7d2c13358..590c95ad5d 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -196,8 +196,7 @@ def test_cdt_select_with_filter(self): # Should filter out 1 [2] ), - # At the first level below root, filter out all maps where "bb" does not have - # an int value greater than 10 + # At the first level below root, only return maps that have a key "bb" with value >= 10 pytest.param( GE( expr0=MapGetByKey( From e166f24e120b9b666ea27d08c61143c126ece7e7 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 24 Oct 2025 14:15:27 -0700 Subject: [PATCH 120/131] Clear up why test set up this way --- test/new_tests/test_cdt_select.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 590c95ad5d..6e3a25a77a 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -250,6 +250,7 @@ def test_exp_loopvar_list(self): # Expected results SECOND_LEVEL_INTEGERS_MINUS_FIVE = [x - 5.0 for x in [14.990000, 5.0000, 34.000000, 12.990000, 19.990000, 2.000000]] + # This operate command will pass with either flag set, but we are just checking the API by using it @pytest.mark.parametrize("flags", [ aerospike.CDT_MODIFY_NO_FAIL, aerospike.CDT_MODIFY_DEFAULT, From a84e3073239ebf5416f9f403f9f43910a2ae6ac1 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 24 Oct 2025 14:17:11 -0700 Subject: [PATCH 121/131] confusing spacing --- test/new_tests/test_cdt_select.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 6e3a25a77a..281b057279 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -299,7 +299,6 @@ def test_cdt_select_flag_matching_tree(self): _, _, bins = self.as_connection.operate(self.key, ops) expected_bin_value = copy.deepcopy(self.RECORD_BINS[self.MAP_OF_NESTED_MAPS_BIN_NAME]) - # Remove all nodes that are filtered out (dict key-value pairs) expected_bin_value["Day1"].clear() del expected_bin_value["Day2"]["game"] From 91cbccc547b80af21a8f41d8bfb8cfcf8263fd63 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 24 Oct 2025 14:19:32 -0700 Subject: [PATCH 122/131] bad spacing --- test/new_tests/test_cdt_select.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 281b057279..1b6bc04db4 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -403,7 +403,6 @@ def test_loopvar_id_map_key(self): _, _, bins = self.as_connection.operate(self.key, ops) expected_bin_value = copy.deepcopy(self.RECORD_BINS[self.MAP_OF_NESTED_MAPS_BIN_NAME]) - # Remove all nodes that are filtered out by dict key del expected_bin_value["Day1"]["ferry"] expected_bin_value["Day2"].clear() From 9c15c37d177f5dbcf3921780a25f52f917417b66 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 24 Oct 2025 14:20:42 -0700 Subject: [PATCH 123/131] improve var name --- test/new_tests/test_cdt_select.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 1b6bc04db4..4d82326677 100644 --- a/test/new_tests/test_cdt_select.py +++ b/test/new_tests/test_cdt_select.py @@ -246,7 +246,7 @@ def test_exp_loopvar_list(self): ] - MOD_EXPR = Sub(LoopVarFloat(aerospike.EXP_LOOPVAR_VALUE), 5.0).compile() + SUBTRACT_FIVE_FROM_ITERATED_FLOAT_EXPR = Sub(LoopVarFloat(aerospike.EXP_LOOPVAR_VALUE), 5.0).compile() # Expected results SECOND_LEVEL_INTEGERS_MINUS_FIVE = [x - 5.0 for x in [14.990000, 5.0000, 34.000000, 12.990000, 19.990000, 2.000000]] @@ -263,7 +263,7 @@ def test_cdt_modify(self, flags): cdt_ctx.cdt_ctx_all_children(), cdt_ctx.cdt_ctx_all_children() ], - expr=self.MOD_EXPR, + expr=self.SUBTRACT_FIVE_FROM_ITERATED_FLOAT_EXPR, flags=flags ), operations.select_by_path( @@ -354,7 +354,7 @@ def test_neg_iterate_on_unexpected_type(self): operations.modify_by_path, { "bin_name": MAP_BIN_NAME, - "expr": MOD_EXPR, + "expr": SUBTRACT_FIVE_FROM_ITERATED_FLOAT_EXPR, "flags": aerospike.CDT_MODIFY_DEFAULT } ), @@ -374,7 +374,7 @@ def test_exp_select_by_path(self): ] bin_expr=MapBin(bin=self.MAP_OF_NESTED_MAPS_BIN_NAME) - modify_expr = ModifyByPath(ctx=ctx, return_type=ResultType.MAP, mod_exp=self.MOD_EXPR, flags=aerospike.CDT_MODIFY_DEFAULT, bin=bin_expr).compile() + modify_expr = ModifyByPath(ctx=ctx, return_type=ResultType.MAP, mod_exp=self.SUBTRACT_FIVE_FROM_ITERATED_FLOAT_EXPR, flags=aerospike.CDT_MODIFY_DEFAULT, bin=bin_expr).compile() select_expr = SelectByPath(ctx=ctx, return_type=ResultType.LIST, flags=aerospike.EXP_LOOPVAR_VALUE, bin=bin_expr).compile() ops = [ expr_ops.expression_write(bin_name=self.MAP_OF_NESTED_MAPS_BIN_NAME, expression=modify_expr), From 99b30cb4ab106fa1f0c3ca099d394e7c280ef09b Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 27 Oct 2025 09:52:36 -0700 Subject: [PATCH 124/131] Stubs: define constant as hexadecimal to be consistent with c client macro def --- aerospike-stubs/aerospike.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aerospike-stubs/aerospike.pyi b/aerospike-stubs/aerospike.pyi index 44a66a625e..9d84df70e5 100644 --- a/aerospike-stubs/aerospike.pyi +++ b/aerospike-stubs/aerospike.pyi @@ -313,7 +313,7 @@ CDT_SELECT_MATCHING_TREE: Literal[0] CDT_SELECT_VALUES: Literal[1] CDT_SELECT_MAP_KEY_VALUES: Literal[1] CDT_SELECT_MAP_KEYS: Literal[2] -CDT_SELECT_NO_FAIL: Literal[16] +CDT_SELECT_NO_FAIL: Literal[0x10] CDT_MODIFY_DEFAULT: Literal[0] CDT_MODIFY_NO_FAIL: Literal[0x10] From 3777ad9b6d9ff3d65966a47538a3c1e96113bc76 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 27 Oct 2025 10:01:48 -0700 Subject: [PATCH 125/131] Improve aerospike constant name per dominic's suggestion --- aerospike_helpers/cdt_ctx.py | 2 +- src/include/policy.h | 2 +- src/main/aerospike.c | 3 +-- src/main/conversions.c | 3 +-- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/aerospike_helpers/cdt_ctx.py b/aerospike_helpers/cdt_ctx.py index e4d4670056..e256546c46 100644 --- a/aerospike_helpers/cdt_ctx.py +++ b/aerospike_helpers/cdt_ctx.py @@ -325,4 +325,4 @@ def cdt_ctx_all_children_with_filter(expression: "TypeExpression") -> _cdt_ctx: Returns: :class:`~aerospike_helpers.cdt_ctx._cdt_ctx` """ - return _cdt_ctx(id=aerospike._CDT_CTX_EXP, extra_args={aerospike._CDT_CTX_ALL_CHILDREN_WITH_FILTER_EXPR_KEY: expression}) + return _cdt_ctx(id=aerospike._CDT_CTX_EXP, extra_args={aerospike._CDT_CTX_FILTER_EXPR_KEY: expression}) diff --git a/src/include/policy.h b/src/include/policy.h index 0b1d44f864..510ceb851a 100644 --- a/src/include/policy.h +++ b/src/include/policy.h @@ -190,7 +190,7 @@ enum { // Can be either for select or apply #define _CDT_FLAGS_KEY "cdt_flags" #define _CDT_APPLY_MOD_EXP_KEY "mod_exp" -#define _CDT_CTX_ALL_CHILDREN_WITH_FILTER_EXPR_KEY "filter_expr" +#define _CDT_CTX_FILTER_EXPR_KEY "filter_expr" enum aerospike_regex_constants { REGEX_NONE = 0, diff --git a/src/main/aerospike.c b/src/main/aerospike.c index 932d0b5328..194249bfab 100644 --- a/src/main/aerospike.c +++ b/src/main/aerospike.c @@ -588,8 +588,7 @@ static struct module_constant_name_to_value module_constants[] = { EXPOSE_STRING_MACRO_FOR_AEROSPIKE_HELPERS(_CDT_FLAGS_KEY), EXPOSE_STRING_MACRO_FOR_AEROSPIKE_HELPERS(_CDT_APPLY_MOD_EXP_KEY), - EXPOSE_STRING_MACRO_FOR_AEROSPIKE_HELPERS( - _CDT_CTX_ALL_CHILDREN_WITH_FILTER_EXPR_KEY), + EXPOSE_STRING_MACRO_FOR_AEROSPIKE_HELPERS(_CDT_CTX_FILTER_EXPR_KEY), }; struct submodule_name_to_creation_method { diff --git a/src/main/conversions.c b/src/main/conversions.c index 3c49faa79b..65f006a966 100644 --- a/src/main/conversions.c +++ b/src/main/conversions.c @@ -2854,8 +2854,7 @@ as_status get_cdt_ctx(AerospikeClient *self, as_error *err, as_cdt_ctx *cdt_ctx, else { PyObject *py_expr = NULL; int retval = PyDict_GetItemStringRef( - py_extra_args, _CDT_CTX_ALL_CHILDREN_WITH_FILTER_EXPR_KEY, - &py_expr); + py_extra_args, _CDT_CTX_FILTER_EXPR_KEY, &py_expr); if (retval != 1) { status = as_error_update(err, AEROSPIKE_ERR_PARAM, "Invalid cdt_ctx_exp"); From 90f022e6fbe0ebabe65139b13f742964e4274701 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 27 Oct 2025 11:12:19 -0700 Subject: [PATCH 126/131] Define in the same enum construct to make sure these enums have unique values --- src/include/policy.h | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/include/policy.h b/src/include/policy.h index 510ceb851a..3fa9d4dabc 100644 --- a/src/include/policy.h +++ b/src/include/policy.h @@ -170,11 +170,6 @@ enum aerospike_hll_operations { OP_HLL_MAY_CONTAIN }; -enum { - _AS_EXP_CODE_CALL_SELECT = 127, - _AS_EXP_CODE_CALL_APPLY = 129, -}; - enum aerospike_expression_operations { OP_EXPR_READ = 2200, OP_EXPR_WRITE }; // Module constants to be used by aerospike_helpers @@ -184,7 +179,9 @@ enum { _AS_EXP_LOOPVAR_INT, _AS_EXP_LOOPVAR_LIST, _AS_EXP_LOOPVAR_MAP, - _AS_EXP_LOOPVAR_STR + _AS_EXP_LOOPVAR_STR, + _AS_EXP_CODE_CALL_SELECT, + _AS_EXP_CODE_CALL_APPLY, }; // Can be either for select or apply From 816e1d6228620a4a8bfa878df3bffacec992d214 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 27 Oct 2025 11:17:10 -0700 Subject: [PATCH 127/131] use macro --- src/main/aerospike.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/aerospike.c b/src/main/aerospike.c index 194249bfab..10ba45234b 100644 --- a/src/main/aerospike.c +++ b/src/main/aerospike.c @@ -458,7 +458,7 @@ static struct module_constant_name_to_value module_constants[] = { {"CDT_CTX_MAP_KEY", .value.integer = AS_CDT_CTX_MAP_KEY}, {"CDT_CTX_MAP_VALUE", .value.integer = AS_CDT_CTX_MAP_VALUE}, {"CDT_CTX_MAP_KEY_CREATE", .value.integer = CDT_CTX_MAP_KEY_CREATE}, - {"_CDT_CTX_EXP", .value.integer = AS_CDT_CTX_EXP}, + EXPOSE_AS_MACRO_AS_PRIVATE_FIELD(AS_CDT_CTX_EXP), /* HLL constants 3.11.0 */ {"OP_HLL_ADD", .value.integer = OP_HLL_ADD}, From 11916346b1affa4346381d94023b92d2738a621d Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 27 Oct 2025 11:17:58 -0700 Subject: [PATCH 128/131] fix. --- aerospike_helpers/cdt_ctx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aerospike_helpers/cdt_ctx.py b/aerospike_helpers/cdt_ctx.py index e256546c46..5719066e8f 100644 --- a/aerospike_helpers/cdt_ctx.py +++ b/aerospike_helpers/cdt_ctx.py @@ -310,7 +310,7 @@ def cdt_ctx_all_children() -> _cdt_ctx: Returns: :class:`~aerospike_helpers.cdt_ctx._cdt_ctx` """ - return _cdt_ctx(id=aerospike._CDT_CTX_EXP) + return _cdt_ctx(id=aerospike._AS_CDT_CTX_EXP) def cdt_ctx_all_children_with_filter(expression: "TypeExpression") -> _cdt_ctx: """ @@ -325,4 +325,4 @@ def cdt_ctx_all_children_with_filter(expression: "TypeExpression") -> _cdt_ctx: Returns: :class:`~aerospike_helpers.cdt_ctx._cdt_ctx` """ - return _cdt_ctx(id=aerospike._CDT_CTX_EXP, extra_args={aerospike._CDT_CTX_FILTER_EXPR_KEY: expression}) + return _cdt_ctx(id=aerospike._AS_CDT_CTX_EXP, extra_args={aerospike._CDT_CTX_FILTER_EXPR_KEY: expression}) From 6263de317546ed5ae6a10cfbdc8d2df95add6db8 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 27 Oct 2025 11:20:02 -0700 Subject: [PATCH 129/131] fix mem leak --- src/main/client/operate.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/client/operate.c b/src/main/client/operate.c index 48b42d3940..81db578281 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -577,6 +577,7 @@ as_status add_op(AerospikeClient *self, as_error *err, } uint32_t flags = convert_pyobject_to_uint32_t(py_flags); + Py_DECREF(py_flags); if (PyErr_Occurred()) { as_error_update(err, AEROSPIKE_ERR_PARAM, "CDT operation's flags argument is invalid"); From 9d08dabc99f4562e280e698f02be3ba7db92d664 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 27 Oct 2025 20:15:26 -0700 Subject: [PATCH 130/131] Fix mem leak.. --- src/main/client/operate.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/client/operate.c b/src/main/client/operate.c index 81db578281..e9c8a6a11b 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -604,6 +604,7 @@ as_status add_op(AerospikeClient *self, as_error *err, as_status status = as_exp_new_from_pyobject(self, py_expr, &mod_exp, err, false); + Py_DECREF(py_expr); if (status != AEROSPIKE_OK) { goto CLEANUP; } From 6355ab3f53f1298c5d555a5447c79390e20e722f Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 27 Oct 2025 20:38:06 -0700 Subject: [PATCH 131/131] Dont inherit from ABC to prevent performance overhead --- aerospike_helpers/expressions/base.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/aerospike_helpers/expressions/base.py b/aerospike_helpers/expressions/base.py index a0c2be5ca4..2751477382 100644 --- a/aerospike_helpers/expressions/base.py +++ b/aerospike_helpers/expressions/base.py @@ -33,7 +33,6 @@ from aerospike_helpers.expressions.resources import ResultType from aerospike_helpers.expressions.resources import _Keys from aerospike_helpers.cdt_ctx import _cdt_ctx -from abc import ABC TypeComparisonArg = Union[_BaseExpr, Any] TypeGeo = Union[_BaseExpr, aerospike.GeoJSON] @@ -1073,7 +1072,13 @@ def __init__(self, var_name: str): self._fixed = {_Keys.VALUE_KEY: var_name} -class LoopVar(_BaseExpr, ABC): +# Although this class is meant to be abstract, we don't inherit from ABC because it might impact performance +# when other expressions pass in LoopVar* expressions as arguments. +# A lot of expressions take in an object of TypeBinName, and isinstance() is called on that object +# Calling isinstance() on an object whose class inherits from ABC may cause performance to slow down +# https://stackoverflow.com/questions/34846631/computational-cost-of-abc +# TODO: we should performance test this to double check +class LoopVar(_BaseExpr): """ Retrieve expression value from a path expression loop variable. """