String operations
For the complete documentation index see: llms.txt
All documentation pages available in markdown.
String operations let the server search, transform, extract, and normalize text in a String bin. That avoids fetching a bin, editing it in your application, and writing it back. This reference covers the Aerospike Python client surface, for developers already using client.operate() and expressions. Use aerospike_helpers.operations.string_operations for operate() calls and aerospike_helpers.expressions.string for expressions.
It requires Aerospike Database 8.2.0 or later and Python client 19.3.0 or later. See Version requirements before upgrading a production cluster. For operation semantics, argument details, and the full 37-operation catalog, see the String operations reference and String expressions reference.
Setup
The examples on this page use the following connection and key:
import aerospike
# Define host configurationconfig = { 'hosts': [ ('127.0.0.1', 3000) ]}# Establishes a connection to the serverclient = aerospike.client(config)
# Creates a key with the namespace "sandbox", set "users", and user key "jdoe123"key = ('sandbox', 'users', 'jdoe123')Round-trip elimination
Without String operations, normalizing a bin takes a read, an application-side edit, and a write:
# Before: fetch, modify, write(key_, meta, bins) = client.get(key)email = bins['email'].strip().lower()client.put(key, {'email': email})string_operations runs the same edit inside a single operate() call, on the server:
from aerospike_helpers.operations import string_operations as so
# After: one round tripclient.operate(key, [ so.trim('email'), so.lower('email'),])
# Verify: " Jane.Doe@Example.com " becomes "jane.doe@example.com"(key_, meta, bins) = client.get(key)print(bins['email'])Two surfaces
Every String operation is available in two forms:
| Surface | Module (common alias) | Used with | Argument order |
|---|---|---|---|
| Operation | aerospike_helpers.operations.string_operations (so) | client.operate() | Bin name first: so.strlen(bin_name[, ctx]) |
| Expression | aerospike_helpers.expressions.string (str_expr) | expression_operations.expression_read/expression_write, filter policies ({'expressions': ...}) | Bin last: str_expr.StrLen(bin=src) |
string_operations functions read or modify a bin directly, returning a dictionary usable in operate(). expressions.string classes build an expression node that composes inside a larger expression, using .compile().
A modify-style expression class (Upper, Replace, Trim, and similar) returns the transformed string as a value. It does not write the result back to the bin on its own. To persist a modify expression’s result on a top-level String bin, write it back with expression_operations.expression_write (see Operation expressions), or use the string_operations equivalent instead. See Nested strings for a worked filter and projection example, and for how persisting a transform applied to a nested value differs.
Each expressions.string class name is the PascalCase form of its string_operations function name (strlen/StrLen, is_upper/IsUpper, regex_compare/RegexCompare), with two exceptions: b64_decode is Base64Decode, and normalize_nfc is NormalizeNFC. See Read operations and Modify operations for the full name mapping.
Modify-op expression classes also take a policy as their first argument, ahead of the bin:
from aerospike_helpers.string_helpers import StringPolicyfrom aerospike_helpers.operations import string_operations as sofrom aerospike_helpers.expressions import string as str_expr
# Operation: bin name first, policy defaults to Noneso.upper('text')
# Expression: policy first (required, no default), bin laststr_expr.Upper(policy=StringPolicy(), bin='text')Unlike string_operations, where policy defaults to None, every modify-op class in expressions.string requires an explicit StringPolicy argument. There is no default value. Pass StringPolicy() for default behavior.
RegexCompare is an exception to the “bin last” rule: its signature is RegexCompare(pattern, bin, regex_flags=RegexFlags.DEFAULT), with bin second rather than last. Calling it positionally with bin in the final position passes arguments in the wrong order.
String write policy
Modify operations take a StringPolicy, which wraps a bitmask of WriteFlags:
from aerospike_helpers.string_helpers import StringPolicy, WriteFlags
policy = StringPolicy() # DEFAULT (0)custom = StringPolicy(WriteFlags.CREATE_ONLY | WriteFlags.NO_FAIL) # combine with bitwise OR| Flag | Value | Effect |
|---|---|---|
DEFAULT | 0 | Allow create or update. |
CREATE_ONLY | 1 | Fail with BinExistsError if the bin already exists. Valid only on the eight operations that can create a missing bin: insert, overwrite, concat, append, prepend, pad_start, pad_end, repeat. Every other modify operation rejects it with InvalidRequest. |
UPDATE_ONLY | 2 | No-op (bin not created) if the bin is missing. Valid on all modify operations. Mutually exclusive with CREATE_ONLY. Combining the two raises InvalidRequest. |
NO_FAIL | 4 | Suppress runtime errors. The bin keeps its prior value. Does not suppress a wrong bin type, invalid UTF-8, or the argument-parsing rejections described for CREATE_ONLY. This includes an oversized result. See the following caution. |
StringPolicy is a per-operation argument, not client configuration. There is no string-policy entry on the client’s global policy dictionaries. Pass a StringPolicy to each call that needs non-default flags.
CREATE_ONLY, UPDATE_ONLY, and most argument errors raise an exception from aerospike.exception rather than failing silently. Catch it and inspect code to distinguish an expected condition from one you need to propagate:
from aerospike.exception import BinExistsErrorfrom aerospike_helpers.operations import string_operations as sofrom aerospike_helpers.string_helpers import StringPolicy, WriteFlags
try: client.operate(key, [ so.insert('email', 0, 'prefix-', policy=StringPolicy(WriteFlags.CREATE_ONLY)), ])except BinExistsError: # Expected: the bin already had a value, so CREATE_ONLY rejected the insert passoperate() runs its whole operation list all or nothing: a failure that NO_FAIL doesn’t cover discards the entire in-memory copy for that call, not just the operation that failed. NO_FAIL only keeps its own operation from triggering that discard. It does nothing for a sibling operation’s failure.
The following example combines an operation NO_FAIL covers (a CREATE_ONLY conflict on a bin that already exists) with one it doesn’t (a String operation on visits, an Integer bin):
from aerospike.exception import BinIncompatibleTypefrom aerospike_helpers.operations import string_operations as sofrom aerospike_helpers.string_helpers import StringPolicy, WriteFlags
# "username" is an existing String bin; "visits" is an Integer bin.try: client.operate(key, [ # 1. NO_FAIL-covered: CREATE_ONLY normally raises BinExistsError # because "username" already has a value. NO_FAIL silently # skips this operation instead, leaving "username" unchanged. so.overwrite('username', 0, 'nobody', policy=StringPolicy(WriteFlags.CREATE_ONLY | WriteFlags.NO_FAIL)), # 2. Not NO_FAIL-covered: a String operation on an Integer bin is a # wrong-bin-type error, which NO_FAIL never suppresses. so.upper('visits'), # 3. Otherwise a normal, unconditionally successful operation. so.upper('username'), ])except BinIncompatibleType: pass
# Confirm: nothing in the call applied, not even operation 1's silent# no-op or operation 3's otherwise-successful uppercase, because# operation 2's uncovered failure discarded the whole in-memory copy.(key_, meta, bins) = client.get(key)print(bins['username']) # unchanged from before the operate() callBecause operation 2 isn’t covered by NO_FAIL, its failure discards the entire call. Operation 1’s NO_FAIL-covered no-op and operation 3’s otherwise-successful upper() are both discarded along with it. NO_FAIL only changes the outcome when every failure in the call is one it covers. Read the bin back to confirm which operations applied rather than assuming success from the absence of an error.
On expressions.string, only NO_FAIL is helpful for most use cases. CREATE_ONLY and UPDATE_ONLY only apply when the target is a bin, so they don’t carry over cleanly to a source expression that may not be a bin at all. RegexReplace is the exception: it accepts the same policy flags as regex_replace().
Read operations
All read operations take the bin name (and an optional ctx path to a value nested in a List or Map, covered in Nested strings) as string_operations arguments, or the source as the bin keyword argument in expressions.string. Every operation requires the target to already be a String. Calling one against another bin type raises BinIncompatibleType.
In the following table, index and length values count Unicode codepoints, not bytes. Most characters are one codepoint, but some emoji and some non-Latin writing systems use multiple codepoints for one visible character. Several operations use Unicode canonical matching: equivalent character sequences (for example, composed and decomposed accents) match even when their byte sequences differ.
| Operation | Expression class | Returns | Description |
|---|---|---|---|
strlen | StrLen | integer | Codepoint count. |
byte_length | ByteLength | integer | UTF-8 byte count. Differs from strlen for non-ASCII text. |
substr / substr_range | SubStr / SubStrRange | string | Substring from start to end, or the range from start up to but not including end ([start, end)). Negative indexes count from the end. |
char_at | CharAt | string | The one-codepoint string at index. Negative indexes count from the end. |
find | Find | integer | Codepoint index of the occurrence-th match of needle, or -1 if not found. |
contains | Contains | boolean | Whether the bin contains needle. |
starts_with | StartsWith | boolean | Whether the bin begins with prefix. |
ends_with | EndsWith | boolean | Whether the bin ends with suffix. |
to_integer | ToInteger | integer | Parses the string as a 64-bit integer. Raises OpNotApplicable if it doesn’t parse. |
to_double | ToDouble | float | Parses the string as a 64-bit float. Raises OpNotApplicable if it doesn’t parse. |
is_numeric | IsNumeric | boolean | Whether the bin’s spelling matches numeric_type (NumericType.ANY, INT, or FLOAT). |
is_upper / is_lower | IsUpper / IsLower | boolean | Whether every codepoint is an uppercase / lowercase letter. Digits, spaces, and punctuation are not cased letters, so any of them yields false. Empty string returns true. |
to_blob | ToBlob | blob | The UTF-8 bytes of the string, as a Blob. |
split / split_separator | Split / SplitSeparator | list | Splits by Unicode codepoint, or by separator (a singleton list if separator isn’t found). |
b64_decode | Base64Decode | blob | Decodes the bin as base64 text into a Blob. Raises OpNotApplicable if it isn’t valid base64. |
regex_compare | RegexCompare | boolean | Matches an ICU regex pattern against the bin. |
Only the eight operations marked in the String write policy table accept WriteFlags.CREATE_ONLY. The server rejects it with InvalidRequest on every other modify operation, and on any operation that carries a ctx (nested) path. UPDATE_ONLY and NO_FAIL are valid on all of them.
Modify operations
Modify operations write a transformed value back to the bin and return None by default. See A bin with more than one operation for how results come back when a bin has more than one operation in the same call. Each operation takes a policy (StringPolicy) as an optional string_operations argument, or a required first argument in expressions.string. See String write policy for CREATE_ONLY/UPDATE_ONLY/NO_FAIL eligibility.
| Operation | Expression class | Description |
|---|---|---|
insert | Insert | Inserts value at codepoint offset. |
overwrite | Overwrite | Overwrites the bin starting at codepoint offset with value. |
concat | Concat | Concatenates additional string values onto the bin. |
append | Append | Appends value to the bin (Unicode-aware). |
prepend | Prepend | Prepends value to the bin (Unicode-aware). |
pad_start | PadStart | Pads the start of the bin to target_length using pad_string. |
pad_end | PadEnd | Pads the end of the bin to target_length using pad_string. |
repeat | Repeat | Repeats the bin count times. |
replace | Replace | Replaces the first occurrence of find with replace. |
replace_all | ReplaceAll | Replaces all occurrences of find with replace. |
snip | Snip | Removes the codepoint range from from (inclusive) to to (exclusive). |
trim | Trim | Removes leading and trailing Unicode whitespace. |
trim_start | TrimStart | Removes leading Unicode whitespace. |
trim_end | TrimEnd | Removes trailing Unicode whitespace. |
upper | Upper | Converts the bin to uppercase. |
lower | Lower | Converts the bin to lowercase. |
case_fold | CaseFold | Applies Unicode case folding for case-insensitive comparison. |
normalize_nfc | NormalizeNFC | Normalizes the bin to Unicode NFC form. |
regex_replace | RegexReplace | Replaces the first regex match, or every match when RegexFlags.GLOBAL is set. Shares StringPolicy flags with modify operations, unlike other expression classes. |
Type conversion
to_string converts an Integer, Float, Boolean, String, or Blob bin to its string representation. It raises BinIncompatibleType for any other bin type, and OpNotApplicable if a Blob bin’s bytes aren’t valid UTF-8.
_, _, bins = client.operate(key, [so.to_string('n')])print(bins['n'])to_string is the only operation that does not accept a ctx. It is a separate server operation that always reads the whole bin and cannot carry a context path in its payload.
To convert a value nested inside a List or Map, extract the nested string first with list_operations.list_get_by_index/map_operations.map_get_by_key (using the same ctx), then convert it client-side. Or compose str_expr.ToString with list.ListGetByIndex/map.MapGetByKey inside an expression.
Regex and numeric-type flags
RegexFlags (combine with bitwise OR) controls regex_compare and regex_replace:
| Flag | Applies to |
|---|---|
CASE_INSENSITIVE | Both |
MULTILINE | Both |
DOTALL | Both |
UNIX_LINES | Both |
GLOBAL | regex_replace only. Replaces every match instead of only the first. |
NumericType narrows is_numeric: ANY (default), INT, or FLOAT. FLOAT requires a literal . followed by a digit, so is_numeric('5', NumericType.FLOAT) is False even though '5' parses as a double.
Reading operate results
String operation results decode with Python-native types rather than raw server wire values.
Booleans decode as booleans
contains, starts_with, ends_with, is_numeric, is_upper, is_lower, and regex_compare decode as a native bool, not an integer 0/1:
_, _, bins = client.operate(key, [so.contains('email', '@')])has_at = bins['email'] # True or FalseA bin with more than one operation
operate()’s dict return does not group multiple results for the same bin into a list. Each operation’s result simply overwrites the previous one under that bin’s key, so bins[bin_name] reflects only the last operation targeting that bin, in submission order. Results from every earlier operation on the same bin are lost, not combined.
To read every operation’s result for a bin with more than one operation in the same call, use operate_ordered() instead of operate(). It returns bins as an ordered list of (bin_name, value) tuples, one tuple per operation, in submission order, instead of a dict:
_, _, bins = client.operate_ordered(key, [ so.trim('email'), # modify so.strlen('email'), # read so.substr_range('email', 0, 5), # read])
for bin_name, value in bins: print(bin_name, value)A single operation on a bin, with nothing else targeting that bin, returns its value directly from operate(), with no list or tuple wrapper:
_, _, bins = client.operate(key, [so.strlen('email')])length = bins['email'] # a plain integer, not a listNested strings
string_operations functions take an optional trailing ctx list to reach a string nested inside a List or Map. The path must already resolve to a string. A non-string nested value fails with BinIncompatibleType. An invalid path (an out-of-bounds index or a missing map key) also fails. See nested context for general ctx error behavior.
from aerospike_helpers import cdt_ctx
# Uppercase a string nested in a list bin "items" at index 0.client.operate(key, [so.upper('items', ctx=[cdt_ctx.cdt_ctx_list_index(0)])])
# Read strlen of a string nested under a map key._, _, bins = client.operate(key, [so.strlen('profile', ctx=[cdt_ctx.cdt_ctx_map_key('bio')])])expressions.string classes do not take a ctx at all. To apply a string expression to a nested value, project the value first with list.ListGetByIndex/map.MapGetByKey (which do take ctx), then pass the result as the bin argument. The following example builds a StrLen condition, then uses it two ways: as a read filter, and as a projected read value.
import aerospikefrom aerospike_helpers import expressions as expfrom aerospike_helpers.expressions import string as str_exprfrom aerospike_helpers.operations import expression_operationsfrom aerospike.exception import FilteredOut
bio = exp.MapGetByKey(None, aerospike.MAP_RETURN_VALUE, exp.ResultType.STRING, 'bio', exp.MapBin('profile'))is_long = exp.GT(str_expr.StrLen(bin=bio), 280).compile()
# As a filter: fetch the record only if its bio is over 280 codepointspolicy = {'expressions': is_long}try: (key_, meta, bins) = client.get(key, policy=policy)except FilteredOut: pass # the filter excluded the record
# As a projection: always fetch the record, with the condition's result in a computed binops = [expression_operations.expression_read('isLong', is_long, aerospike.EXP_READ_DEFAULT)](key_, meta, bins) = client.operate(key, ops)bio_is_long = bins['isLong']to_string/ToString never accepts ctx, on either surface. See Type conversion.
Version requirements
String operations require Aerospike Database 8.2.0 or later on every node, and Python client 19.3.0 or later. Aerospike Database 8.1.2 and earlier does not recognize the string opcodes and returns a generic “invalid request” error. Check the cluster version rather than branching on the error: run asinfo -v build against each node to confirm it reports 8.2.0 or later, and pip show aerospike to confirm the installed client version.
Next steps
- String operations reference: full semantics, index-bounds behavior, and error codes for all 37 operations
- String expressions reference
- String examples
- String operations and UTF-8 validation
- Bin operations
- Expressions - Python: building and using expressions, filter policies, and
expression_operations - Error handling - Python: general exception-handling pattern
- Error codes: full server status code list, including String-operation-specific entries
- API reference (Python)