Skip to content

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 configuration
config = {
'hosts': [ ('127.0.0.1', 3000) ]
}
# Establishes a connection to the server
client = 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 trip
client.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:

SurfaceModule (common alias)Used withArgument order
Operationaerospike_helpers.operations.string_operations (so)client.operate()Bin name first: so.strlen(bin_name[, ctx])
Expressionaerospike_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 StringPolicy
from aerospike_helpers.operations import string_operations as so
from aerospike_helpers.expressions import string as str_expr
# Operation: bin name first, policy defaults to None
so.upper('text')
# Expression: policy first (required, no default), bin last
str_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
FlagValueEffect
DEFAULT0Allow create or update.
CREATE_ONLY1Fail 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_ONLY2No-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_FAIL4Suppress 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 BinExistsError
from aerospike_helpers.operations import string_operations as so
from 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
pass

operate() 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 BinIncompatibleType
from aerospike_helpers.operations import string_operations as so
from 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() call

Because 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.

OperationExpression classReturnsDescription
strlenStrLenintegerCodepoint count.
byte_lengthByteLengthintegerUTF-8 byte count. Differs from strlen for non-ASCII text.
substr / substr_rangeSubStr / SubStrRangestringSubstring from start to end, or the range from start up to but not including end ([start, end)). Negative indexes count from the end.
char_atCharAtstringThe one-codepoint string at index. Negative indexes count from the end.
findFindintegerCodepoint index of the occurrence-th match of needle, or -1 if not found.
containsContainsbooleanWhether the bin contains needle.
starts_withStartsWithbooleanWhether the bin begins with prefix.
ends_withEndsWithbooleanWhether the bin ends with suffix.
to_integerToIntegerintegerParses the string as a 64-bit integer. Raises OpNotApplicable if it doesn’t parse.
to_doubleToDoublefloatParses the string as a 64-bit float. Raises OpNotApplicable if it doesn’t parse.
is_numericIsNumericbooleanWhether the bin’s spelling matches numeric_type (NumericType.ANY, INT, or FLOAT).
is_upper / is_lowerIsUpper / IsLowerbooleanWhether 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_blobToBlobblobThe UTF-8 bytes of the string, as a Blob.
split / split_separatorSplit / SplitSeparatorlistSplits by Unicode codepoint, or by separator (a singleton list if separator isn’t found).
b64_decodeBase64DecodeblobDecodes the bin as base64 text into a Blob. Raises OpNotApplicable if it isn’t valid base64.
regex_compareRegexComparebooleanMatches 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.

OperationExpression classDescription
insertInsertInserts value at codepoint offset.
overwriteOverwriteOverwrites the bin starting at codepoint offset with value.
concatConcatConcatenates additional string values onto the bin.
appendAppendAppends value to the bin (Unicode-aware).
prependPrependPrepends value to the bin (Unicode-aware).
pad_startPadStartPads the start of the bin to target_length using pad_string.
pad_endPadEndPads the end of the bin to target_length using pad_string.
repeatRepeatRepeats the bin count times.
replaceReplaceReplaces the first occurrence of find with replace.
replace_allReplaceAllReplaces all occurrences of find with replace.
snipSnipRemoves the codepoint range from from (inclusive) to to (exclusive).
trimTrimRemoves leading and trailing Unicode whitespace.
trim_startTrimStartRemoves leading Unicode whitespace.
trim_endTrimEndRemoves trailing Unicode whitespace.
upperUpperConverts the bin to uppercase.
lowerLowerConverts the bin to lowercase.
case_foldCaseFoldApplies Unicode case folding for case-insensitive comparison.
normalize_nfcNormalizeNFCNormalizes the bin to Unicode NFC form.
regex_replaceRegexReplaceReplaces 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:

FlagApplies to
CASE_INSENSITIVEBoth
MULTILINEBoth
DOTALLBoth
UNIX_LINESBoth
GLOBALregex_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 False

A 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 list

Nested 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 aerospike
from aerospike_helpers import expressions as exp
from aerospike_helpers.expressions import string as str_expr
from aerospike_helpers.operations import expression_operations
from 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 codepoints
policy = {'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 bin
ops = [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