diff --git a/aerospike-client-c b/aerospike-client-c index 3f15b94be5..3bf664e7cf 160000 --- a/aerospike-client-c +++ b/aerospike-client-c @@ -1 +1 @@ -Subproject commit 3f15b94be5aef435e5ae6681953638b680b91a6e +Subproject commit 3bf664e7cfc56908a2479b11cf2cf4f985c178e3 diff --git a/aerospike-stubs/aerospike.pyi b/aerospike-stubs/aerospike.pyi index 9d84df70e5..4d87bdee94 100644 --- a/aerospike-stubs/aerospike.pyi +++ b/aerospike-stubs/aerospike.pyi @@ -310,9 +310,10 @@ TXN_STATE_COMMITTED: Literal[2] TXN_STATE_ABORTED: Literal[3] 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_MAP_VALUE: Literal[1] +CDT_SELECT_LIST_VALUE: Literal[1] +CDT_SELECT_MAP_KEY: Literal[2] +CDT_SELECT_MAP_KEY_VALUES: Literal[3] CDT_SELECT_NO_FAIL: Literal[0x10] CDT_MODIFY_DEFAULT: Literal[0] diff --git a/aerospike_helpers/expressions/base.py b/aerospike_helpers/expressions/base.py index 2751477382..58527bf3de 100644 --- a/aerospike_helpers/expressions/base.py +++ b/aerospike_helpers/expressions/base.py @@ -1112,6 +1112,27 @@ class LoopVarInt(LoopVar): _op = aerospike._AS_EXP_LOOPVAR_INT +class LoopVarBlob(LoopVar): + _op = aerospike._AS_EXP_LOOPVAR_BLOB + + +class LoopVarBool(LoopVar): + _op = aerospike._AS_EXP_LOOPVAR_BOOL + + +class ResultRemove(_BaseExpr): + """ + Indicates entry deletion for modify_by_path. + """ + _op = aerospike._AS_EXP_CODE_RESULT_REMOVE + + def __init__(self): + """ + :return: (result_remove) + """ + pass + + class SelectByPath(_BaseExpr): """ Constructs a select by path operation. This is used to retrieve a number of diff --git a/doc/aerospike.rst b/doc/aerospike.rst index a8e1660572..9aabb1dced 100644 --- a/doc/aerospike.rst +++ b/doc/aerospike.rst @@ -1798,29 +1798,32 @@ Transaction State .. _cdt_select_flags: -CDT Select Flags ----------------- +Path Expression Flags +--------------------- .. 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. .. data:: CDT_SELECT_VALUES +.. data:: CDT_SELECT_LIST_VALUE +.. data:: CDT_SELECT_MAP_VALUE Return the list of the values of the nodes finally selected by the context. -.. data:: CDT_SELECT_MAP_KEY_VALUES +.. data:: CDT_SELECT_MAP_KEYS - Return a list of key-value pairs. + Return the list of map keys of the nodes finally selected by the context. -.. data:: CDT_SELECT_MAP_KEYS +.. data:: CDT_SELECT_MAP_KEY_VALUES - For final selected nodes which are elements of maps, return the appropriate map key. + Returns the list of map (key, value) pairs of the nodes finally selected + by the context. .. data:: CDT_SELECT_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. + do not fail the operation; just ignore those elements. Interpret UNKNOWN as false instead. .. _cdt_modify_flags: diff --git a/src/include/policy.h b/src/include/policy.h index cd97895534..5ce467efaf 100644 --- a/src/include/policy.h +++ b/src/include/policy.h @@ -180,6 +180,8 @@ enum { _AS_EXP_LOOPVAR_LIST, _AS_EXP_LOOPVAR_MAP, _AS_EXP_LOOPVAR_STR, + _AS_EXP_LOOPVAR_BLOB, + _AS_EXP_LOOPVAR_BOOL, _AS_EXP_CODE_CALL_SELECT, _AS_EXP_CODE_CALL_APPLY, }; diff --git a/src/main/aerospike.c b/src/main/aerospike.c index 2871839680..13d6036c92 100644 --- a/src/main/aerospike.c +++ b/src/main/aerospike.c @@ -562,10 +562,12 @@ static struct module_constant_name_to_value module_constants[] = { 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), + // TODO: remove since this isn't a C client enum value + {"CDT_SELECT_VALUES", .value.integer = AS_CDT_SELECT_MAP_VALUE}, + EXPOSE_AS_MACRO_WITHOUT_AS_PREFIX_AS_PUBLIC_FIELD(CDT_SELECT_MAP_VALUE), + EXPOSE_AS_MACRO_WITHOUT_AS_PREFIX_AS_PUBLIC_FIELD(CDT_SELECT_LIST_VALUE), + EXPOSE_AS_MACRO_WITHOUT_AS_PREFIX_AS_PUBLIC_FIELD(CDT_SELECT_MAP_KEY), + EXPOSE_AS_MACRO_WITHOUT_AS_PREFIX_AS_PUBLIC_FIELD(CDT_SELECT_MAP_KEY_VALUE), 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), @@ -579,11 +581,14 @@ static struct module_constant_name_to_value module_constants[] = { EXPOSE_MACRO(_AS_EXP_LOOPVAR_LIST), EXPOSE_MACRO(_AS_EXP_LOOPVAR_MAP), EXPOSE_MACRO(_AS_EXP_LOOPVAR_STR), + EXPOSE_MACRO(_AS_EXP_LOOPVAR_BLOB), + EXPOSE_MACRO(_AS_EXP_LOOPVAR_BOOL), // 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_MACRO(_AS_EXP_CODE_RESULT_REMOVE), EXPOSE_STRING_MACRO_FOR_AEROSPIKE_HELPERS(_CDT_FLAGS_KEY), EXPOSE_STRING_MACRO_FOR_AEROSPIKE_HELPERS(_CDT_APPLY_MOD_EXP_KEY), diff --git a/src/main/convert_expressions.c b/src/main/convert_expressions.c index c077443b53..3d81118317 100644 --- a/src/main/convert_expressions.c +++ b/src/main/convert_expressions.c @@ -210,6 +210,7 @@ static as_status get_expr_size(int *size_to_alloc, int *intermediate_exprs_size, EXP_SZ(as_exp_select_by_path(NULL, 0, 0, NIL)), [_AS_EXP_CODE_CALL_APPLY] = EXP_SZ(as_exp_modify_by_path(NULL, 0, NULL, 0, NIL)), + [_AS_EXP_CODE_RESULT_REMOVE] = EXP_SZ(as_exp_result_remove()), [BIN] = EXP_SZ(as_exp_bin_int(0)), [_AS_EXP_CODE_AS_VAL] = EXP_SZ(as_exp_val(NULL)), [_AS_EXP_LOOPVAR_FLOAT] = EXP_SZ(as_exp_loopvar_float(0)), @@ -217,6 +218,8 @@ static as_status get_expr_size(int *size_to_alloc, int *intermediate_exprs_size, [_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)), + [_AS_EXP_LOOPVAR_BOOL] = EXP_SZ(as_exp_loopvar_bool(0)), + [_AS_EXP_LOOPVAR_BLOB] = EXP_SZ(as_exp_loopvar_blob(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( @@ -652,6 +655,8 @@ add_expr_macros(AerospikeClient *self, as_static_pool *static_pool, case _AS_EXP_LOOPVAR_LIST: case _AS_EXP_LOOPVAR_MAP: case _AS_EXP_LOOPVAR_STR: + case _AS_EXP_LOOPVAR_BOOL: + case _AS_EXP_LOOPVAR_BLOB: if (get_int64_t(err, AS_PY_VAL_KEY, temp_expr->pydict, &lval1) != AEROSPIKE_OK) { return err->code; @@ -673,6 +678,12 @@ add_expr_macros(AerospikeClient *self, as_static_pool *static_pool, case _AS_EXP_LOOPVAR_FLOAT: APPEND_ARRAY(0, as_exp_loopvar_float(lval1)); break; + case _AS_EXP_LOOPVAR_BLOB: + APPEND_ARRAY(0, as_exp_loopvar_blob(lval1)); + break; + case _AS_EXP_LOOPVAR_BOOL: + APPEND_ARRAY(0, as_exp_loopvar_bool(lval1)); + break; } break; @@ -1683,6 +1694,9 @@ add_expr_macros(AerospikeClient *self, as_static_pool *static_pool, lval2, NIL)); } break; + case _AS_EXP_CODE_RESULT_REMOVE: + APPEND_ARRAY(0, as_exp_result_remove()); + break; default: return as_error_update(err, AEROSPIKE_ERR_PARAM, "Unrecognised expression op type."); diff --git a/test/new_tests/test_cdt_select.py b/test/new_tests/test_cdt_select.py index 4d82326677..7b4b1d3693 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, MapBin +from aerospike_helpers.expressions.base import GE, Eq, LoopVarStr, LoopVarFloat, LoopVarInt, LoopVarMap, LoopVarList, ModifyByPath, SelectByPath, MapBin, LoopVarBool, LoopVarBlob, ResultRemove from aerospike_helpers.expressions.map import MapGetByKey from aerospike_helpers.expressions.list import ListSize from aerospike_helpers.expressions.arithmetic import Sub @@ -36,7 +36,9 @@ class TestCDTSelectOperations: "ab": { "bb": 12 }, - "b": 2 + "b": 2, + "c": True, + "d": bytearray(b'123') }, LIST_BIN_NAME: [ { @@ -194,7 +196,8 @@ def test_cdt_select_with_filter(self): pytest.param( GE(LoopVarInt(aerospike.EXP_LOOPVAR_VALUE), 2), # Should filter out 1 - [2] + [2], + id="LoopVarInt" ), # At the first level below root, only return maps that have a key "bb" with value >= 10 pytest.param( @@ -208,11 +211,22 @@ def test_cdt_select_with_filter(self): ), expr1=10 ), - [RECORD_BINS[MAP_BIN_NAME]["ab"]] - ) + [RECORD_BINS[MAP_BIN_NAME]["ab"]], + id="LoopVarMap" + ), + pytest.param( + Eq(LoopVarBool(aerospike.EXP_LOOPVAR_VALUE), True), + [True], + id="LoopVarBool" + ), + pytest.param( + Eq(LoopVarBlob(aerospike.EXP_LOOPVAR_VALUE), b'123'), + [bytearray(b'123')], + id="LoopVarBlob" + ), ] ) - def test_exp_loopvar_int_and_map(self, filter_expr, expected_bin_value): + def test_exp_loopvar_types(self, filter_expr, expected_bin_value): ops = [ operations.select_by_path( bin_name=self.MAP_BIN_NAME, @@ -314,7 +328,7 @@ def test_cdt_select_flag_map_keys(self): cdt_ctx.cdt_ctx_all_children(), cdt_ctx.cdt_ctx_all_children() ], - flags=aerospike.CDT_SELECT_MAP_KEYS + flags=aerospike.CDT_SELECT_MAP_KEY ) ] @@ -427,3 +441,29 @@ def test_loopvar_id_list_index(self): _, _, 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.RECORD_BINS[self.LIST_BIN_NAME][0]]} + + def test_expr_result_remove(self): + ops = [ + operations.modify_by_path( + bin_name=self.MAP_OF_NESTED_MAPS_BIN_NAME, + ctx=[ + cdt_ctx.cdt_ctx_all_children(), + cdt_ctx.cdt_ctx_all_children() + ], + expr=ResultRemove().compile(), + flags=aerospike.CDT_MODIFY_DEFAULT + ) + ] + + with self.expected_context_for_pos_tests: + self.as_connection.operate(self.key, ops) + + _, _, bins = self.as_connection.get(self.key) + assert bins[self.MAP_OF_NESTED_MAPS_BIN_NAME] == { + "Day1": { + }, + "Day2": { + }, + "Day3": { + } + }