Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
bca05b9
Compile with -Werror for linux and macos.
juliannguyen4 Jun 1, 2026
c80b412
Update documentation to reflect latest changes in PRD. TODO - need ad…
juliannguyen4 Jul 14, 2026
4b0e04a
Refactor string op tests by combining all test cases that operate on …
juliannguyen4 Jul 14, 2026
352cef8
Add test_string_ops_on_nonexistent_bin_creates_it for additive string…
juliannguyen4 Jul 14, 2026
1f91d0d
Refactor string op tests by combining all test cases that operate on …
juliannguyen4 Jul 14, 2026
221e0a4
Address incorrect tests for pad_start and pad_end on nonexistent bin.
juliannguyen4 Jul 14, 2026
061a174
Since all tests in this function expect NEEDLE as the output, don't c…
juliannguyen4 Jul 14, 2026
4416fb5
Add negative tests that check the other string write ops don't create…
juliannguyen4 Jul 15, 2026
96c0595
Address failing negative tests where operations.read() will return a …
juliannguyen4 Jul 15, 2026
d1a66a1
Merge branch 'CLIENT-5057-refactor-string-ops-tests' into CLIENT-5057…
juliannguyen4 Jul 15, 2026
0dfc023
Document how each string op behaves with regards to a nonexistent bin…
juliannguyen4 Jul 15, 2026
028688a
Address to_string expression and operation not being implemented... T…
juliannguyen4 Jul 15, 2026
b08d986
Fully implement to_string() expression and operation. TODO - to_strin…
juliannguyen4 Jul 15, 2026
69c45b1
Merge remote-tracking branch 'origin/dev' into CLIENT-4869-fix-macos-…
juliannguyen4 Jul 15, 2026
dac0e4f
Merge branch 'CLIENT-4869-fix-macos-failing-to-compile-with-Werror-fl…
juliannguyen4 Jul 15, 2026
1a14d52
To make simpler, just have ToString expression reuse aerospike._OP_ST…
juliannguyen4 Jul 15, 2026
3f97c8d
The problem was passing in the bin name as a string to string express…
juliannguyen4 Jul 15, 2026
e507e82
Merge remote-tracking branch 'origin/dev' into CLIENT-5057-document-s…
juliannguyen4 Jul 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 17 additions & 17 deletions aerospike_helpers/expressions/string.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,23 @@ 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.

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 = {
Expand Down Expand Up @@ -823,23 +840,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,
Expand Down
84 changes: 63 additions & 21 deletions aerospike_helpers/operations/string_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,10 +397,32 @@ 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
index. Negative indexes count from the end of the string.
index.

Negative indexes count from the end of the string.
If the bin doesn't exist, this operation will create a new bin.

Args:

Expand All @@ -423,8 +445,10 @@ def insert(bin_name: str, index: int, value: str, policy: StringPolicy | None =
def overwrite(bin_name: str, index: int, value: str, policy: StringPolicy | None = None, ctx: TypeCTX = None):
"""
Create string ``overwrite`` operation that overwrites codepoints starting at index
with value. The result may grow beyond the original length when value extends
past the end.
with value.

The result may grow beyond the original length when value extends past the end.
If the bin doesn't exist, this operation will create a new bin.

Args:

Expand All @@ -447,8 +471,10 @@ def overwrite(bin_name: str, index: int, value: str, policy: StringPolicy | None
def append(bin_name: str, value: str, policy: StringPolicy | None = None, ctx: TypeCTX = None):
"""
Create string ``append`` operation that appends value to the end of the bin.

Unlike :func:`~aerospike_helpers.operations.operations.append`, this string-package operation
uses Unicode codepoint semantics and supports string policy and ctx.
If the bin doesn't exist, this operation will create a new bin.

Args:

Expand All @@ -469,8 +495,10 @@ def append(bin_name: str, value: str, policy: StringPolicy | None = None, ctx: T
def prepend(bin_name: str, value: str, policy: StringPolicy | None = None, ctx: TypeCTX = None):
"""
Create string ``prepend`` operation that prepend value to the start of the bin.

Unlike :func:`~aerospike_helpers.operations.operations.prepend`, this string-package operation
uses Unicode codepoint semantics and supports string policy and ctx.
If the bin doesn't exist, this operation will create a new bin.

Args:

Expand All @@ -493,6 +521,8 @@ def concat(bin_name: str, value_list: list[str], policy: StringPolicy | None = N
Create string ``concat`` operation that appends each string element in values to
the bin in order.

If the bin doesn't exist, this operation will create a new bin.

Args:

bin_name: name of string bin.
Expand All @@ -513,6 +543,8 @@ def snip(bin_name: str, start: int, end: int, policy: StringPolicy | None = None
"""
Create string ``snip`` operation that removes codepoints from start to end.

If the bin doesn't exist, this operation will be a no-op.

Args:

bin_name: name of string bin.
Expand All @@ -536,6 +568,8 @@ def replace(bin_name: str, needle: str, replacement: str, policy: StringPolicy |
Create string ``replace`` operation that replaces the first occurrence of needle
with replacement.

If the bin doesn't exist, this operation will be a no-op.

Args:

bin_name: name of string bin.
Expand All @@ -559,6 +593,8 @@ def replace_all(bin_name: str, needle: str, replacement: str, policy: StringPoli
Create string ``replace_all`` operation that replaces every occurrence of needle
with replacement.

If the bin doesn't exist, this operation will be a no-op.

Args:

bin_name: name of string bin.
Expand All @@ -581,6 +617,8 @@ def upper(bin_name: str, policy: StringPolicy | None = None, ctx: TypeCTX = None
"""
Create string ``upper`` operation that uppercases the bin in place.

If the bin doesn't exist, this operation will be a no-op.

Args:

bin_name: name of string bin.
Expand All @@ -599,6 +637,8 @@ def lower(bin_name: str, policy: StringPolicy | None = None, ctx: TypeCTX = None
"""
Create string ``lower`` operation that lowercases the bin in place.

If the bin doesn't exist, this operation will be a no-op.

Args:

bin_name: name of string bin.
Expand All @@ -616,7 +656,10 @@ def lower(bin_name: str, policy: StringPolicy | None = None, ctx: TypeCTX = None
def casefold(bin_name: str, policy: StringPolicy | None = None, ctx: TypeCTX = None):
"""
Create string ``case_fold`` operation that applies locale-independent case folding
(lowercase) to the bin. This is useful for normalized comparison keys.
(lowercase) to the bin.

This is useful for normalized comparison keys.
If the bin doesn't exist, this operation will be a no-op.

Args:

Expand All @@ -635,7 +678,9 @@ def casefold(bin_name: str, policy: StringPolicy | None = None, ctx: TypeCTX = N
def normalize_nfc(bin_name: str, policy: StringPolicy | None = None, ctx: TypeCTX = None):
"""
Create string ``normalize_nfc`` operation that normalizes the bin to Unicode NFC.

Already-normalized strings are unchanged.
If the bin doesn't exist, this operation will be a no-op.

Args:

Expand All @@ -656,6 +701,8 @@ def trim_start(bin_name: str, policy: StringPolicy | None = None, ctx: TypeCTX =
Create string ``trim_start`` operation that removes whitespace from the start of
the bin.

If the bin doesn't exist, this operation will be a no-op.

Args:

bin_name: name of string bin.
Expand All @@ -675,6 +722,8 @@ def trim_end(bin_name: str, policy: StringPolicy | None = None, ctx: TypeCTX = N
Create string ``trim_end`` operation that removes whitespace from the end of the
bin.

If the bin doesn't exist, this operation will be a no-op.

Args:

bin_name: name of string bin.
Expand All @@ -693,6 +742,8 @@ def trim(bin_name: str, policy: StringPolicy | None = None, ctx: TypeCTX = None)
"""
Create string ``trim`` operation that removes whitespace from both ends of the bin.

If the bin doesn't exist, this operation will be a no-op.

Args:

bin_name: name of string bin.
Expand All @@ -719,6 +770,8 @@ def pad_start(
the bin reaches ``target_length`` codepoints. No-op when the bin is already at or
above the target length.

If the bin doesn't exist, this operation will create a new bin.

Args:

bin_name: name of string bin.
Expand Down Expand Up @@ -749,6 +802,8 @@ def pad_end(
bin reaches ``target_length`` codepoints. No-op when the bin is already at or
above the target length.

If the bin doesn't exist, this operation will create a new bin.

Args:

bin_name: name of string bin.
Expand All @@ -771,6 +826,8 @@ def repeat(bin_name: str, count: int, policy: StringPolicy | None = None, ctx: T
"""
Create string ``repeat`` operation that repeats the bin contents count times.

If the bin doesn't exist, this operation will create a new bin.

Args:

bin_name: name of string bin.
Expand Down Expand Up @@ -800,6 +857,8 @@ def regex_replace(
with replacement. Pass :py:attr:`~aerospike_helpers.string_helpers.RegexFlags.GLOBAL` to replace every match.
This server operation accepts regex flags but not string policy flags.

If the bin doesn't exist, this operation will be a no-op.

Args:

bin_name: name of string bin.
Expand All @@ -818,20 +877,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,
}
9 changes: 6 additions & 3 deletions aerospike_helpers/string_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,16 @@ class WriteFlags(IntEnum):
String operation policy write bit flags. Use bitwise OR to combine flags.
"""

#: Default. Allow create or update.
DEFAULT = 0
"""
Default. Create or replace.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

outdated

"""

NO_FAIL = 4
"""
Do not raise an error if a modify operation cannot be applied because
the target bin does not exist. The record is left unchanged.
Suppress an operation failure with the bin unchanged.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awaiting more info on how to test this

Does not suppress wrong-type errors.
"""


Expand Down
5 changes: 3 additions & 2 deletions src/include/policy.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -219,7 +220,7 @@ enum {
_AS_EXP_LOOPVAR_GEOJSON,
_AS_EXP_LOOPVAR_HLL,
_AS_EXP_CODE_CALL_SELECT,
_AS_EXP_CODE_CALL_APPLY,
_AS_EXP_CODE_CALL_APPLY
};

// Can be either for select or apply
Expand Down
2 changes: 1 addition & 1 deletion src/main/aerospike.c
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@ static struct module_constant_name_to_value module_constants[] = {
EXPOSE_MACRO(_AS_EXP_LOOPVAR_GEOJSON),
EXPOSE_MACRO(_AS_EXP_LOOPVAR_HLL),

// C client uses the same expression code for these two expressions
// C client uses the same expression code for some expressions
// so we define unique ones in the Python client code
EXPOSE_MACRO(_AS_EXP_CODE_CALL),
EXPOSE_MACRO(_AS_EXP_CODE_CALL_SELECT),
Expand Down
2 changes: 1 addition & 1 deletion src/main/client/operate.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 ||
Expand Down
4 changes: 3 additions & 1 deletion src/main/client/operate_helper.c
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
4 changes: 4 additions & 0 deletions src/main/convert_expressions.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down Expand Up @@ -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;
Expand Down
Loading