diff --git a/aerospike_helpers/expressions/string.py b/aerospike_helpers/expressions/string.py index 10cc005725..b778fcf64f 100644 --- a/aerospike_helpers/expressions/string.py +++ b/aerospike_helpers/expressions/string.py @@ -401,6 +401,24 @@ def __init__(self, pattern: str, bin: "TypeBinName", regex_flags: RegexFlags = R self._children = (_convert_bin_name_to_expr(bin),) +class ToString(_BaseExpr): + _op = aerospike._OP_STRING_TO_STRING + + def __init__(self, bin: "TypeBinName"): + """ + Args: + + bin: A bin expression to apply this function to. + If this argument is a string, the bin must contain a string. + + Returns: + + The string in the bin with the value converted to a string. + + """ + self._children = (_convert_bin_name_to_expr(bin),) + + class _WriteOp(_BaseExpr): def __init__(self, policy: StringPolicy): self._fixed = { @@ -823,23 +841,6 @@ def __init__( self._children = (_convert_bin_name_to_expr(bin),) -class ToString(_WriteOp): - _op = aerospike._AS_EXP_CODE_CALL - - def __init__(self, bin: "TypeBinName"): - """ - Args: - - bin: A bin expression to apply this function to. - - Returns: - - The string in the bin with the value converted to a string. - - """ - self._children = (_convert_bin_name_to_expr(bin),) - - __exp_class_to_op_func = { StrLen: str_ops.strlen, SubStr: str_ops.substr, diff --git a/aerospike_helpers/operations/string_operations.py b/aerospike_helpers/operations/string_operations.py index ce3fec80d2..62b7ed6cf6 100644 --- a/aerospike_helpers/operations/string_operations.py +++ b/aerospike_helpers/operations/string_operations.py @@ -397,6 +397,25 @@ def regex_compare(bin_name: str, pattern: str, regex_flags: RegexFlags = RegexFl } +def to_string(bin_name: str): + """ + Create ``to_string`` operation that converts an integer, double, string, or blob + bin to its string representation. + + Raises :exc:`~aerospike.exception.BinIncompatibleType` for + any other bin type. This top-level operation does not accept ctx and does not + send a msgpack payload. + + Args: + + bin_name: name of string bin. + """ + return { + "op": aerospike._OP_STRING_TO_STRING, + "bin": bin_name, + } + + def insert(bin_name: str, index: int, value: str, policy: StringPolicy | None = None, ctx: TypeCTX = None): """ Create string ``insert`` operation that splices value into the bin at codepoint @@ -818,20 +837,3 @@ def regex_replace( "policy": policy, "ctx": ctx } - - -def to_string(bin_name: str): - """ - Create ``to_string`` operation that converts an integer, double, string, or blob - bin to its string representation. Raises :exc:`~aerospike.exception.BinIncompatibleType` for - any other bin type. This top-level operation does not accept ctx and does not - send a msgpack payload. - - Args: - - bin_name: name of string bin. - """ - return { - "op": aerospike._OP_STRING_TO_STRING, - "bin": bin_name, - } diff --git a/src/include/policy.h b/src/include/policy.h index 1a82671647..620b9fdcdd 100644 --- a/src/include/policy.h +++ b/src/include/policy.h @@ -102,7 +102,8 @@ enum Aerospike_send_bool_as_values { X(STRING_REPEAT), \ X(STRING_REGEX_REPLACE), \ X(STRING_APPEND), \ - X(STRING_PREPEND), + X(STRING_PREPEND), \ + X(STRING_TO_STRING), // clang-format on enum { diff --git a/src/main/client/operate.c b/src/main/client/operate.c index b85bd6619e..7b96b916cb 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -218,7 +218,7 @@ static inline bool use_operate_conversion_helper(int op) op == OP_LIST_REMOVE_BY_VALUE_RANGE || op == OP_LIST_SET_ORDER || op == OP_LIST_SORT || op == OP_LIST_REMOVE_BY_VALUE_RANK_RANGE_REL || op == OP_LIST_GET_BY_VALUE_RANK_RANGE_REL || op == OP_LIST_CREATE || - (op >= OP_STRING_STRLEN && op <= OP_STRING_PREPEND) || + (op >= OP_STRING_STRLEN && op <= OP_STRING_TO_STRING) || (op == OP_MAP_REMOVE_BY_KEY_INDEX_RANGE_REL || op == OP_MAP_REMOVE_BY_VALUE_RANK_RANGE_REL || op == OP_MAP_GET_BY_VALUE_RANK_RANGE_REL || diff --git a/src/main/client/operate_helper.c b/src/main/client/operate_helper.c index f641d933e5..623f759fd3 100644 --- a/src/main/client/operate_helper.c +++ b/src/main/client/operate_helper.c @@ -766,7 +766,9 @@ as_status as_operations_add_from_pyobject(AerospikeClient *self, as_error *err, success = as_operations_string_prepend(ops, bin, ctx_ref, &str_policy, str_attr_value1); break; - + case OP_STRING_TO_STRING: + success = as_operations_to_string(ops, bin); + break; case OP_MAP_REMOVE_BY_VALUE_RANK_RANGE_REL: { if (range_specified) { success = as_operations_map_remove_by_value_rel_rank_range( diff --git a/src/main/convert_expressions.c b/src/main/convert_expressions.c index 133ef9e84a..6f55943c14 100644 --- a/src/main/convert_expressions.c +++ b/src/main/convert_expressions.c @@ -473,6 +473,7 @@ static as_status get_expr_size(int *size_to_alloc, int *intermediate_exprs_size, [OP_STRING_B64_DECODE] = EXP_SZ(as_exp_string_b64_decode(NIL)), [OP_STRING_REGEX_COMPARE] = EXP_SZ(as_exp_string_regex_compare_flags("", 0, NIL)), + [OP_STRING_TO_STRING] = EXP_SZ(as_exp_to_string(NIL)), [OP_STRING_INSERT] = EXP_SZ(as_exp_string_insert(NULL, 0, "", NIL)), [OP_STRING_OVERWRITE] = EXP_SZ(as_exp_string_overwrite(NULL, 0, "", NIL)), @@ -1908,6 +1909,9 @@ add_expr_macros(AerospikeClient *self, as_static_pool *static_pool, case OP_STRING_B64_DECODE: APPEND_ARRAY(1, as_exp_string_b64_decode(NIL)); break; + case OP_STRING_TO_STRING: + APPEND_ARRAY(1, as_exp_to_string(NIL)); + break; case OP_STRING_REGEX_REPLACE: case OP_STRING_REGEX_COMPARE: { char *pattern = NULL; diff --git a/test/new_tests/string_helpers.py b/test/new_tests/string_helpers.py index 3ef5147370..1cef3d127c 100644 --- a/test/new_tests/string_helpers.py +++ b/test/new_tests/string_helpers.py @@ -6,8 +6,10 @@ KEY = KEYS[0] -NON_STR_BIN_NAME = "nonstr" +NON_STR_BIN_NAME = INT_BIN_NAME = "nonstr" STR_BIN_NAME = "str" +DOUBLE_BIN_NAME = "double" +BLOB_BIN_NAME = "blob" UPPERCASE_STR_BIN_NAME = "uppercase_str" NESTED_STR_BIN_NAME = "nested_str" STR_WITH_INT_BIN_NAME = "str_with_int" @@ -37,7 +39,7 @@ BINS = { STR_BIN_NAME: EXAMPLE_STR, - NON_STR_BIN_NAME: 1, + INT_BIN_NAME: 1, UPPERCASE_STR_BIN_NAME: UPPERCASE_STR, NESTED_STR_BIN_NAME: [EXAMPLE_STR], STR_WITH_INT_BIN_NAME: STRING_WITH_INT, @@ -46,7 +48,9 @@ BASE64_ENCODED_BIN_NAME: BASE64_ENCODED_STR, SURROUNDING_WHITESPACE_BIN_NAME: EXAMPLE_STR_WITH_SURROUNDING_WHITESPACE, MULTILINE_STR_BIN_NAME: MULTILINE_STR, - MULTILINE_STR_WITH_CR_BIN_NAME: MULTILINE_STR_WITH_CR + MULTILINE_STR_WITH_CR_BIN_NAME: MULTILINE_STR_WITH_CR, + DOUBLE_BIN_NAME: 1.0, + BLOB_BIN_NAME: b'123' } START_IDX = 1 diff --git a/test/new_tests/test_expressions_string.py b/test/new_tests/test_expressions_string.py index 157fe2638c..f2c7d8d7c8 100644 --- a/test/new_tests/test_expressions_string.py +++ b/test/new_tests/test_expressions_string.py @@ -1,7 +1,7 @@ import pytest import base64 -from aerospike_helpers.expressions import string as str_expr +from aerospike_helpers.expressions import string as str_expr, IntBin, FloatBin, BlobBin from aerospike_helpers.operations import expression_operations as expr_ops from aerospike_helpers.operations import operations from aerospike_helpers.string_helpers import NumericType, RegexFlags @@ -67,7 +67,23 @@ def setup(self, request, as_connection, expect_earlier_than_server_version_to_fa ( str_expr.RegexCompare(pattern="π", regex_flags=RegexFlags.DEFAULT, bin=MULTIBYTE_CODEPOINT_BIN_NAME), False - ) + ), + ( + str_expr.ToString(bin=IntBin(INT_BIN_NAME)), + str(BINS[INT_BIN_NAME]) + ), + ( + str_expr.ToString(bin=FloatBin(DOUBLE_BIN_NAME)), + str(BINS[INT_BIN_NAME]) + ), + ( + str_expr.ToString(bin=STR_BIN_NAME), + str(BINS[STR_BIN_NAME]) + ), + ( + str_expr.ToString(bin=BlobBin(BLOB_BIN_NAME)), + bytes.decode(BINS[BLOB_BIN_NAME]) + ), ] ) @expect_server_version_earlier_than_8_1_3_to_fail diff --git a/test/new_tests/test_string_operations.py b/test/new_tests/test_string_operations.py index 116fb98303..3ac4042e9f 100644 --- a/test/new_tests/test_string_operations.py +++ b/test/new_tests/test_string_operations.py @@ -271,6 +271,26 @@ def test_regex_compare(self, pattern: str, expected_result: bool): assert bins[MULTIBYTE_CODEPOINT_BIN_NAME] is expected_result + @expect_server_version_earlier_than_8_1_3_to_fail + @pytest.mark.parametrize( + "bin_name, expected_result", + [ + (INT_BIN_NAME, str(BINS[INT_BIN_NAME])), + (DOUBLE_BIN_NAME, str(BINS[INT_BIN_NAME])), + (STR_BIN_NAME, BINS[STR_BIN_NAME]), + (BLOB_BIN_NAME, bytes.decode(BINS[BLOB_BIN_NAME])) + ] + ) + def test_to_string(self, bin_name: str, expected_result: str): + ops = [ + str_ops.to_string(bin_name=bin_name) + ] + + with self.expected_context_for_pos_tests: + _, _, bins = self.as_connection.operate(KEY, ops) + + assert bins[bin_name] == expected_result + # Write operations def add_read_op(self, ops, bin_name):