String operation examples
For the complete documentation index see: llms.txt
All documentation pages available in markdown.
Aerospike string operations run on the server, so text is normalized, searched, and reshaped without a fetch-modify-write round trip. Each operation has a detailed description with its own code examples in the String operations reference.
The patterns below combine several of them. To filter or project string values inside a query rather than on one record, see String expressions.
Examples
Normalizing an email address
Starting from an email bin holding a value with stray whitespace and mixed case:
" Ana@Corp.IO "Trim and lowercase it in one atomic operate call,
replacing a read-modify-write loop in the application. Modify operations return no value,
so the same call carries a read of the bin to get the normalized string back.
try (RecordStream rs = session.upsert(key) .bin("email").trim() .bin("email").lower() .bin("email").get() .execute()) { Record rec = rs.next().recordOrThrow(); String normalized = rec.operationResult(2).getString(); // normalized: "ana@corp.io"}stream = (session.upsert(key) .bin("email").str_trim() .bin("email").str_lower() .bin("email").get() .execute())normalized = stream.first_or_raise().record_or_raise().operation_result(2)# normalized: "ana@corp.io"// Requires: use aerospike::operations::{scalar, string as str_op};let policy = StringPolicy::default();let rec = client.operate(&WritePolicy::default(), &key, &[ str_op::trim(&policy, "email"), str_op::lower(&policy, "email"), scalar::get_bin("email"),]).await?;let normalized = rec.bins.get("email");// normalized: "ana@corp.io"Record rec = client.Operate(null, key, StringOperation.Trim(StringPolicy.Default, "email"), StringOperation.Lower(StringPolicy.Default, "email"), Operation.Get("email"));
string normalized = (string)rec.GetList("email")[2];// normalized: "ana@corp.io"// Requires: import as "github.com/aerospike/aerospike-client-go/v8"p := as.DefaultStringPolicyrec, err := client.Operate(nil, key, as.StrTrimOp(p, "email"), as.StrLowerOp(p, "email"), as.GetBinOp("email"))
normalized := rec.Bins["email"].(as.OpResults)[2].(string)// normalized: "ana@corp.io"const Aerospike = require('aerospike')const strings = Aerospike.stringsconst op = Aerospike.operations
const record = await client.operate(key, [ strings.trim('email'), strings.lower('email'), op.read('email')])const normalized = record.bins.email// normalized: "ana@corp.io"as_operations ops;as_operations_init(&ops, 3);as_operations_string_trim(&ops, "email", NULL, NULL);as_operations_string_lower(&ops, "email", NULL, NULL);as_operations_add_read(&ops, "email");
as_record* rec = NULL;aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec);as_operations_destroy(&ops);
// as_record_get() answers with the first entry for a bin name, which here is a// modify's nil. Walk rec->bins.entries and keep the last entry for "email".const char* normalized = NULL;for (uint16_t i = 0; i < rec->bins.size; i++) { as_bin* b = &rec->bins.entries[i]; if (strcmp(as_bin_get_name(b), "email") == 0) { as_val* v = (as_val*)as_bin_get_value(b); if (as_val_type(v) == AS_STRING) normalized = as_string_get((as_string*)v); }}// normalized: "ana@corp.io"Record rec = client.operate(null, key, StringOperation.trim(StringPolicy.Default, "email"), StringOperation.lower(StringPolicy.Default, "email"), Operation.get("email"));
String normalized = (String) rec.getList("email").get(2);// normalized: "ana@corp.io"from aerospike_helpers.operations import operations, string_operations as so
_, _, bins = client.operate(key, [ so.trim("email"), so.lower("email"), operations.read("email"),])normalized = bins["email"]# normalized: "ana@corp.io"Extracting the domain from an email address
Starting from an email bin holding:
"ana@corp.io"find returns the codepoint index of the
first @, or -1 when the needle is absent.
try (RecordStream rs = session.query(key) .bin("email").find("@") .execute()) { int at = (int) rs.next().recordOrThrow().operationResult(0).getLong(); // at: 3}stream = session.query(key).bin("email").str_find("@").execute()at = stream.first_or_raise().record_or_raise().operation_result(0)# at: 3// Requires: use aerospike::operations::string as str_op;let rec = client.operate(&WritePolicy::default(), &key, &[str_op::find("email", "@")]).await?;let at = rec.bins.get("email");// at: 3int at = client.Operate(null, key, StringOperation.Find("email", "@")).GetInt("email");// at: 3// Requires: import as "github.com/aerospike/aerospike-client-go/v8"rec, err := client.Operate(nil, key, as.StrFindOp("email", "@"))at := rec.Bins["email"].(int)// at: 3const Aerospike = require('aerospike')const strings = Aerospike.strings
const record = await client.operate(key, [strings.find('email', '@')])const at = record.bins.email// at: 3as_operations ops;as_operations_init(&ops, 1);as_operations_string_find(&ops, "email", NULL, "@");
as_record* rec = NULL;aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec);as_operations_destroy(&ops);
int64_t at = as_record_get_int64(rec, "email", -1); // at: 3int at = client.operate(null, key, StringOperation.find("email", "@")).getInt("email");// at: 3from aerospike_helpers.operations import string_operations as so
_, _, bins = client.operate(key, [so.find("email", "@")])at = bins["email"]# at: 3Then take the rest of the string from the codepoint after it. The single-argument form of
substr runs to the end of the value, so
no length is needed.
try (RecordStream rs = session.query(key) .bin("email").substr(at + 1) .execute()) { String domain = rs.next().recordOrThrow().operationResult(0).getString(); // domain: "corp.io"}stream = session.query(key).bin("email").str_substr(at + 1).execute()domain = stream.first_or_raise().record_or_raise().operation_result(0)# domain: "corp.io"// Requires: use aerospike::operations::string as str_op;let rec = client.operate(&WritePolicy::default(), &key, &[str_op::substr_from("email", at + 1)]).await?;let domain = rec.bins.get("email");// domain: "corp.io"string domain = client.Operate(null, key, StringOperation.Substr("email", at + 1)).GetString("email");// domain: "corp.io"// Requires: import as "github.com/aerospike/aerospike-client-go/v8"rec, err := client.Operate(nil, key, as.StrSubstrFromOp("email", at+1))domain := rec.Bins["email"].(string)// domain: "corp.io"const Aerospike = require('aerospike')const strings = Aerospike.strings
const record = await client.operate(key, [strings.substr('email', at + 1)])const domain = record.bins.email// domain: "corp.io"as_operations ops;as_operations_init(&ops, 1);as_operations_string_substr(&ops, "email", NULL, at + 1);
as_record* rec = NULL;aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec);as_operations_destroy(&ops);
const char* domain = as_record_get_str(rec, "email"); // domain: "corp.io"String domain = client.operate(null, key, StringOperation.substr("email", at + 1)).getString("email");// domain: "corp.io"from aerospike_helpers.operations import string_operations as so
_, _, bins = client.operate(key, [so.substr("email", at + 1)])domain = bins["email"]# domain: "corp.io"Both round trips collapse into one if the result feeds an expression instead: compose
find and substr in an expression_read projection. See
String expressions.
Classifying a value with a regular expression
Starting from a sku bin holding:
"A-1024"regex_compare returns a boolean
rather than the match, so it classifies a record without moving the value to the client.
try (RecordStream rs = session.query(key) .bin("sku").regexCompare("^A-[0-9]+$") .execute()) { boolean matched = rs.next().recordOrThrow().operationResult(0).getBoolean(); // matched: true}stream = session.query(key).bin("sku").str_regex_compare(r"^A-[0-9]+$").execute()matched = stream.first_or_raise().record_or_raise().operation_result(0)# matched: True// Requires: use aerospike::operations::string as str_op;let rec = client.operate(&WritePolicy::default(), &key, &[str_op::regex_compare("sku", r"^A-[0-9]+$")]).await?;let matched = rec.bins.get("sku");// matched: trueRecord rec = client.Operate(null, key, StringOperation.RegexCompare("sku", "^A-[0-9]+$"));bool matched = (bool)rec.GetValue("sku");// matched: true// Requires: import as "github.com/aerospike/aerospike-client-go/v8"rec, err := client.Operate(nil, key, as.StrRegexCompareOp("sku", `^A-[0-9]+$`))matched := rec.Bins["sku"].(bool)// matched: trueconst Aerospike = require('aerospike')const strings = Aerospike.strings
const record = await client.operate(key, [ strings.regexCompare('sku', '^A-[0-9]+$')])const matched = record.bins.sku// matched: trueas_operations ops;as_operations_init(&ops, 1);as_operations_string_regex_compare(&ops, "sku", NULL, "^A-[0-9]+$");
as_record* rec = NULL;aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec);as_operations_destroy(&ops);
bool matched = as_record_get_bool(rec, "sku"); // matched: trueRecord rec = client.operate(null, key, StringOperation.regexCompare("sku", "^A-[0-9]+$"));boolean matched = (boolean) rec.getValue("sku");// matched: truefrom aerospike_helpers.operations import string_operations as so
_, _, bins = client.operate(key, [so.regex_compare("sku", r"^A-[0-9]+$")])matched = bins["sku"]# matched: TruePrefer regex_compare over the legacy cmp_regex comparison, which uses POSIX regex and is not Unicode-aware.
See regex_compare.
Modifying a bin whose type is uncertain
A string modify against a non-String bin fails with AS_ERR_INCOMPATIBLE_TYPE (12), and
NO_FAIL does not suppress it. Starting from a maybe_string bin that holds an integer:
42Gate the write with a bin-type filter, so the server skips records whose bin is not a String. The two SDKs take the test as AEL text; the other clients build the same comparison from expression calls. Clients differ in how they report the skip — some raise, some return an empty result, some say nothing — so the reliable signal is that the bin is unchanged.
// Returns an empty stream when the filter skips the record.try (RecordStream rs = session.upsert(key) .where("$.maybe_string.type() == STRING") .bin("maybe_string").upper() .execute()) { RecordResult result = rs.next(); // null: the record was skipped}# first() is None when the filter skips the record.stream = (session.upsert(key).where("$.maybe_string.type() == STRING") .bin("maybe_string").str_upper().execute())// Requires: use aerospike::expressions as exp;let mut gated = WritePolicy::default();gated.base_policy.filter_expression = Some(exp::eq( exp::bin_type("maybe_string".to_string()), exp::int_val(3),));
// Returns Err with ResultCode::FilteredOut when the record is skipped.client.operate(&gated, &key, &[str_op::upper(&StringPolicy::default(), "maybe_string")]).await?;WritePolicy wp = new WritePolicy();wp.filterExp = Exp.Build( Exp.EQ(Exp.BinType("maybe_string"), Exp.Val((int)ParticleType.STRING)));
// Skipped silently; set wp.failOnFilteredOut to be told instead.client.Operate(wp, key, StringOperation.Upper(StringPolicy.Default, "maybe_string"));// Requires: import as "github.com/aerospike/aerospike-client-go/v8"wp := as.NewWritePolicy(0, 0)wp.FilterExpression = as.ExpEq(as.ExpBinType("maybe_string"), as.ExpIntVal(3))
// Returns an error matching FILTERED_OUT when the record is skipped._, err := client.Operate(wp, key, as.StrUpperOp(as.DefaultStringPolicy, "maybe_string"))const Aerospike = require('aerospike')const exp = Aerospike.expconst strings = Aerospike.strings
const policy = new Aerospike.OperatePolicy({ filterExpression: exp.eq(exp.binType('maybe_string'), exp.int(3))})
try { await client.operate(key, [strings.upper('maybe_string')], null, policy)} catch (error) { if (error.code !== Aerospike.status.FILTERED_OUT) throw error // Bin is missing or not a String; the record is unchanged.}as_exp_build(is_string, as_exp_cmp_eq(as_exp_bin_type("maybe_string"), as_exp_int(AS_BYTES_STRING)));
as_policy_operate gated;as_policy_operate_init(&gated);gated.base.filter_exp = is_string;
as_operations ops;as_operations_init(&ops, 1);as_operations_string_upper(&ops, "maybe_string", NULL, NULL);
// Returns AEROSPIKE_FILTERED_OUT when the record is skipped.as_record* rec = NULL;aerospike_key_operate(&as, &err, &gated, &key, &ops, &rec);as_operations_destroy(&ops);as_exp_destroy(is_string);WritePolicy wp = new WritePolicy(client.writePolicyDefault);wp.filterExp = Exp.build( Exp.eq(Exp.binType("maybe_string"), Exp.val(ParticleType.STRING)));
// Skipped silently; set wp.failOnFilteredOut to be told instead.client.operate(wp, key, StringOperation.upper(StringPolicy.Default, "maybe_string"));import aerospikefrom aerospike.exception import FilteredOutfrom aerospike_helpers import expressions as expfrom aerospike_helpers.operations import string_operations as so
policy = {"expressions": exp.Eq(exp.BinType("maybe_string"), aerospike.AS_BYTES_STRING).compile()}try: client.operate(key, [so.upper("maybe_string")], policy=policy)except FilteredOut: pass # Bin is missing or not a String; the record is unchanged.Or send the modify ungated and handle the type error, which costs a round trip on every
record with the wrong type but needs no expression. Ask for
error detail at verbosity 2
and the server says which operation wanted what — string_upper requires string bin, got integer — rather than leaving the handler to interpret code 12 on its own.
Behavior verbose = Behavior.DEFAULT.deriveWithChanges("verbose", b -> b.on(Selectors.all(), ops -> ops.errorDetailVerbosity(ErrorDetailVerbosity.MESSAGE)));Session session = cluster.createSession(verbose);
try (RecordStream rs = session.upsert(key) .bin("maybe_string").upper() .execute()) { rs.next().recordOrThrow();} catch (AerospikeException e) { String reason = e.getBaseMessage(); // reason: "string_upper requires string bin, got integer"}from aerospike_sdk.exceptions import BinTypeErrorfrom aerospike_sdk.policy.behavior_settings import Settings
session = cluster.create_session(Behavior.DEFAULT.derive_with_changes( "verbose", writes=Settings(error_detail_verbosity=2)))
try: session.upsert(key).bin("maybe_string").str_upper().execute()except BinTypeError as e: reason = e.server_message # reason: "string_upper requires string bin, got integer"// Requires: use aerospike::ResultCode;let mut policy = WritePolicy::default();policy.base_policy.error_detail_verbosity = 2; // 2 = subcode + message
if let Err(e) = client.operate(&policy, &key, &[str_op::upper(&StringPolicy::default(), "maybe_string")]).await{ let reason = e.server_message(); // e.server_result_code() is Some(ResultCode::BinTypeError) // reason: Some("string_upper requires string bin, got integer")}WritePolicy policy = new WritePolicy();policy.errorDetailVerbosity = 2; // 2 = subcode + message
try{ client.Operate(policy, key, StringOperation.Upper(StringPolicy.Default, "maybe_string"));}catch (AerospikeException e) when (e.Result == ResultCode.BIN_TYPE_ERROR){ string reason = e.BaseMessage; // reason: "string_upper requires string bin, got integer"}// Requires: import as "github.com/aerospike/aerospike-client-go/v8"// and "errors" and "github.com/aerospike/aerospike-client-go/v8/types"wp := as.NewWritePolicy(0, 0)wp.ErrorDetailVerbosity = 2 // 2 = subcode + message
_, err := client.Operate(wp, key, as.StrUpperOp(as.DefaultStringPolicy, "maybe_string"))
var ae *as.AerospikeErrorif errors.As(err, &ae) && ae.Matches(types.BIN_TYPE_ERROR) { // ae.ServerMessage: "string_upper requires string bin, got integer"}const Aerospike = require('aerospike')const strings = Aerospike.strings
const policy = new Aerospike.OperatePolicy({ errorDetailVerbosity: 2 // 2 = subcode + message})
try { await client.operate(key, [strings.upper('maybe_string')], null, policy)} catch (error) { if (error.code !== Aerospike.status.ERR_BIN_INCOMPATIBLE_TYPE) throw error const reason = error.message // This client prepends the node address: // reason: "127.0.0.1:3000 string_upper requires string bin, got integer"}as_policy_operate policy;as_policy_operate_init(&policy);policy.base.error_detail_verbosity = 2; // 2 = subcode + message
as_operations ops;as_operations_init(&ops, 1);as_operations_string_upper(&ops, "maybe_string", NULL, NULL);
as_record* rec = NULL;as_status status = aerospike_key_operate(&as, &err, &policy, &key, &ops, &rec);as_operations_destroy(&ops);
if (status == AEROSPIKE_ERR_BIN_INCOMPATIBLE_TYPE) { // This client prepends the node address: // err.message: "127.0.0.1:3000 string_upper requires string bin, got integer"}WritePolicy policy = new WritePolicy(client.writePolicyDefault);policy.errorDetailVerbosity = 2; // 2 = subcode + message
try { client.operate(policy, key, StringOperation.upper(StringPolicy.Default, "maybe_string"));} catch (AerospikeException e) { String reason = e.getBaseMessage(); // reason: "string_upper requires string bin, got integer"}from aerospike.exception import BinIncompatibleTypefrom aerospike_helpers.operations import string_operations as so
try: client.operate(key, [so.upper("maybe_string")], policy={"error_detail_verbosity": 2}) # 2 = subcode + messageexcept BinIncompatibleType as e: reason = e.msg # This client prepends the node address: # reason: "127.0.0.1:3000 string_upper requires string bin, got integer"