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 Go client surface for developers already using client.Operate() and expressions: the Str*Op builders for Operate() calls, and the ExpString* builders for expressions. After reading this page, you can choose the right builder for a task, configure a StringPolicy, and interpret Operate() results, including grouped OpResults.
It requires Aerospike Database 8.2.0 or later and Go client 8.9.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 ( "fmt" "log"
"github.com/aerospike/aerospike-client-go/v8")
// Establishes a connection to the serverclient, err := aerospike.NewClient("127.0.0.1", 3000)if err != nil { log.Fatal(err)}defer client.Close()
// Creates a key with the namespace "sandbox", set "users", and user key "jdoe123"key, err := aerospike.NewKey("sandbox", "users", "jdoe123")if err != nil { log.Fatal(err)}Round-trip elimination
Without String operations, normalizing a bin takes a read, an application-side edit, and a write:
import "strings"
// Before: fetch, modify, writerecord, err := client.Get(nil, key)if err != nil { log.Fatal(err)}email := strings.ToLower(strings.TrimSpace(record.Bins["email"].(string)))if err := client.Put(nil, key, aerospike.BinMap{"email": email}); err != nil { log.Fatal(err)}Str*Op runs the same edit inside a single Operate call, on the server:
policy := aerospike.DefaultStringPolicy
// After: one round trip_, err := client.Operate(nil, key, aerospike.StrTrimOp(policy, "email"), aerospike.StrLowerOp(policy, "email"))if err != nil { log.Fatal(err)}
// Verify: " Jane.Doe@Example.com " becomes "jane.doe@example.com"record, err := client.Get(nil, key)if err != nil { log.Fatal(err)}fmt.Println(record.Bins["email"])Two surfaces
Every String operation is available in two forms, both exported directly at package level:
| Surface | Naming | Used with | Argument order |
|---|---|---|---|
| Operation | Str*Op | client.Operate() | Bin name first: aerospike.StrLenOp(binName, ctx...) |
| Expression | ExpString* | WritePolicy.FilterExpression/Policy.FilterExpression, operation expressions (ExpReadOp/ExpWriteOp) | Source expression first, right after policy where present: aerospike.ExpStringFind(src, needle) |
Str*Op builders read or modify a bin directly, returning an *Operation for client.Operate(). ExpString* builders return an *Expression node that composes inside a larger expression, with no separate build or compile step, unlike the Java and Python clients. Assign the result directly to FilterExpression, or pass it to ExpReadOp/ExpWriteOp.
A modify-style ExpString* builder (ExpStringUpper, ExpStringReplace, ExpStringTrim, 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, write it back with ExpWriteOp, or use the Str*Op equivalent instead. See Nested strings for a worked filter and projection example.
Modify operations also take a policy as their first argument, ahead of the bin name or source expression:
// Operation: policy, then bin nameaerospike.StrUpperOp(aerospike.DefaultStringPolicy, "text")
// Expression: policy, then source expressionaerospike.ExpStringUpper(aerospike.DefaultStringPolicy, aerospike.ExpStringBin("text"))String write policy
Modify operations take a *StringPolicy, which wraps a StringWriteFlags value:
policy := aerospike.DefaultStringPolicy // DEFAULT (0)custom := aerospike.NewStringPolicy(aerospike.StringWriteNoFail) // NO_FAIL (4)| Flag | Value | Effect |
|---|---|---|
StringWriteDefault | 0 | No create/update restriction. See the create-capability note in the following paragraph for what happens against a missing bin. |
StringWriteCreateOnly | 1 | Applies the operation only if the bin doesn’t already exist. Valid only on the eight create-capable operations named below, and every other modify operation rejects it. Cannot be combined with StringWriteUpdateOnly, and rejected on an operation carrying a CDTContext. |
StringWriteUpdateOnly | 2 | Applies the operation only to an existing bin, disabling bin creation. Valid on every modify operation. Cannot be combined with StringWriteCreateOnly. |
StringWriteNoFail | 4 | Suppress the error if the operation can’t be applied to the bin, leaving the bin at its prior value and returning a nil result for that operation. |
Against a missing bin, StringWriteDefault never fails, but it also doesn’t make every operation create one. Only 8 of the 19 modify operations can create a bin from nothing: insert, overwrite, concat, append, prepend, pad_start, pad_end, and repeat. Calling one of those against a missing bin creates it, seeded from an empty string. The other 11 modify operations (trim, upper, replace, and similar) can’t create a bin at all. Against a missing bin, they leave the record unchanged and still return success, so the absence of an error doesn’t mean the operation did anything. Read the bin back if you need to confirm a write happened, or use StringWriteCreateOnly/StringWriteUpdateOnly below to have the client enforce that guarantee for you.
StringWriteCreateOnly and StringWriteUpdateOnly narrow that default behavior:
import "github.com/aerospike/aerospike-client-go/v8/types"
createOnly := aerospike.NewStringPolicy(aerospike.StringWriteCreateOnly)
_, err := client.Operate(nil, key, aerospike.StrAppendOp(createOnly, "note", "first entry"))if err != nil { if err.Matches(types.BIN_EXISTS_ERROR) { // Expected: "note" already exists } else { log.Fatal(err) }}StringWriteCreateOnly is valid only on the eight create-capable operations named above, and every other modify operation rejects it with PARAMETER_ERROR (server status AS_ERR_PARAMETER, code 4). It’s also invalid combined with StringWriteUpdateOnly, and invalid on an operation carrying a CDTContext. Both of those are PARAMETER_ERROR too. The server resolves all three of these cases while parsing the operation’s arguments, before any no-fail check runs, so StringWriteNoFail doesn’t suppress them.
StringWriteUpdateOnly is valid on every modify operation. Against a missing bin it’s a no-op rather than a create, and the call still returns success, so the same “success doesn’t mean it did anything” caveat from the StringWriteDefault case above applies.
StringPolicy is a per-operation argument, not client configuration: there’s no string-policy field on ClientPolicy. Pass a NewStringPolicy result to each call that needs a non-default flag.
In a multi-operation Operate() call, StringWriteNoFail only suppresses the affected operation. Sibling operations in the same call still commit, and the client receives no error either way. Read the bin back to confirm which operations applied rather than assuming success from the absence of an error.
regex_replace/ExpStringRegexReplace takes two independent flag arguments: regexFlags (StringRegexFlags) for regex behavior, and policy (*StringPolicy) for write semantics. Both apply on the wire. See Modify operations for how the write flags behave for this operation specifically.
Read operations
All read operations take the bin name as the first Str*Op argument, or the source expression as the first ExpString* argument. Both also take an optional CDTContext (Go’s collection-data-type nested-path type) path to a value nested in a List or Map, covered in Nested strings. Every operation requires the target to already be a String; calling one against another bin type fails with BIN_TYPE_ERROR (server status AS_ERR_INCOMPATIBLE_TYPE, code 12. See Error codes).
Index and length values count Unicode codepoints, not bytes. Most characters are one codepoint, but some emoji use multiple codepoints for one visible character (a grapheme cluster), and characters outside the Basic Multilingual Plane (code points above U+FFFF, including many emoji and historic scripts) can also differ. Negative indexes count from the end of the string. regexCompare uses ICU regex syntax.
Substring matching in find, contains, starts_with, and ends_with (and in replace/replace_all under Modify operations) treats canonically equivalent text as equal, so a precomposed é (U+00E9) matches e followed by a combining acute accent (U+0301).
| Operation | Go builders | Returns | Description |
|---|---|---|---|
strlen | StrLenOp / ExpStringLen | integer | Codepoint count. |
byte_length | StrByteLengthOp / ExpStringByteLength | integer | UTF-8 byte count. Differs from strlen for non-ASCII text. |
substr | StrSubstrFromOp, StrSubstrOp / ExpStringSubstrFrom, ExpStringSubstr | string | Substring from start to the end, or the half-open range [start, end). |
char_at | StrCharAtOp / ExpStringCharAt | string | The one-codepoint string at index. |
find | StrFindOp, StrFindNthOp / ExpStringFind, ExpStringFindNth | integer | Codepoint index of needle, or of a specific occurrence (1 = first, -1 = last). -1 if not found. |
contains | StrContainsOp / ExpStringContains | boolean | Whether the bin contains needle. |
starts_with | StrStartsWithOp / ExpStringStartsWith | boolean | Whether the bin begins with prefix. |
ends_with | StrEndsWithOp / ExpStringEndsWith | boolean | Whether the bin ends with suffix. |
to_integer | StrToIntegerOp / ExpStringToInteger | integer | Parses the string as an int64. Fails if it doesn’t parse. See Type conversion. |
to_double | StrToDoubleOp / ExpStringToDouble | float | Parses the string as a 64-bit float. Fails if it doesn’t parse. |
is_numeric | StrIsNumericOp, StrIsNumericTypedOp / ExpStringIsNumeric, ExpStringIsNumericTyped | boolean | Whether the bin’s spelling matches an optional StringNumericType (StringNumericAny, StringNumericInt, or StringNumericFloat). |
is_upper / is_lower | StrIsUpperOp, StrIsLowerOp / ExpStringIsUpper, ExpStringIsLower | boolean | Whether every codepoint is an uppercase/lowercase letter. Digits, spaces, and punctuation aren’t cased letters, so any of them makes the result false. An empty string returns true. |
to_blob | StrToBlobOp / ExpStringToBlob | blob | The UTF-8 bytes of the string, as a Blob. |
split | StrSplitOp, StrSplitBySeparatorOp / ExpStringSplit, ExpStringSplitBySeparator | list | Splits by Unicode codepoint, or by separator (a singleton list if separator isn’t found). |
b64_decode | StrB64DecodeOp / ExpStringB64Decode | blob | Decodes the bin as base64 text into a Blob. Fails if it isn’t valid base64. |
regex_compare | StrRegexCompareOp, StrRegexCompareWithFlagsOp / ExpStringRegexCompare, ExpStringRegexCompareWithFlags | boolean | Matches an ICU regex pattern against the bin, optionally with StringRegexFlags. |
Seven read operations (contains, startsWith, endsWith, isNumeric, isUpper, isLower, regexCompare) return a native boolean, not an integer 0/1. See Reading operate results.
Modify operations
Modify operations write a transformed value back to the bin (Str*Op) or return it as an expression value (ExpString*, which does not mutate the underlying bin). Every modify operation accepts StringWriteDefault, StringWriteUpdateOnly, or StringWriteNoFail. StringWriteCreateOnly is valid only on the eight operations that can create a missing bin. See String write policy.
| Operation | Go builders | Description |
|---|---|---|
insert | StrInsertOp / ExpStringInsert | Splices value in at codepoint index. |
overwrite | StrOverwriteOp / ExpStringOverwrite | Overwrites codepoints starting at index with value. |
concat | StrConcatOp, StrConcatListOp / ExpStringConcat | Appends one string, or each element of a list of strings, in order. |
append | StrAppendOp / ExpStringAppend | Appends value. Unicode/DBCS-aware (handles double-byte character sets correctly), unlike the legacy AppendOp. |
prepend | StrPrependOp / ExpStringPrepend | Prepends value. Unicode/DBCS-aware, unlike the legacy PrependOp. |
pad_start | StrPadStartOp / ExpStringPadStart | Left-pads with padString up to targetLength codepoints. No-op if the bin already meets or exceeds targetLength codepoints. |
pad_end | StrPadEndOp / ExpStringPadEnd | Right-pads with padString up to targetLength codepoints. |
repeat | StrRepeatOp / ExpStringRepeat | Repeats the bin count times. |
snip | StrSnipOp / ExpStringSnip | Removes the half-open codepoint range [start, end). Both start and end are required arguments. |
replace | StrReplaceOp / ExpStringReplace | Replaces the first occurrence of needle with replacement. |
replace_all | StrReplaceAllOp / ExpStringReplaceAll | Replaces every occurrence of needle with replacement. |
upper / lower | StrUpperOp, StrLowerOp / ExpStringUpper, ExpStringLower | Uppercases or lowercases the bin. |
case_fold | StrCaseFoldOp / ExpStringCaseFold | Applies locale-independent case folding, for comparison keys. |
normalize_nfc | StrNormalizeNFCOp / ExpStringNormalizeNFC | Normalizes the bin to Unicode NFC form. Already-normalized strings are unchanged. |
trim / trim_start / trim_end | StrTrimOp, StrTrimStartOp, StrTrimEndOp / ExpStringTrim, ExpStringTrimStart, ExpStringTrimEnd | Removes Unicode whitespace from both ends, the start, or the end. |
regex_replace | StrRegexReplaceOp / ExpStringRegexReplace | Replaces the first regex match, or every match when StringRegexGlobal is set. Honors StringWriteDefault, StringWriteUpdateOnly, and StringWriteNoFail, and rejects StringWriteCreateOnly like the other 11 non-creating modify operations. See String write policy. |
Type conversion
StrToStringOp/ExpStringToString converts an Integer, Float, Boolean, String, or Blob bin to its string representation. It fails with BIN_TYPE_ERROR for any other bin type, and with OP_NOT_APPLICABLE (server status AS_ERR_OP_NOT_APPLICABLE, code 26) if a Blob bin’s bytes aren’t valid UTF-8.
record, err := client.Operate(nil, key, aerospike.StrToStringOp("n"))if err != nil { log.Fatal(err)}fmt.Println(record.Bins["n"])StrToStringOp is the only operation that does not accept a CDTContext. It’s a separate server operation that always reads the whole bin and can’t carry a context path in its payload.
To convert a value nested inside a List or Map, extract the nested string first with ListGetByIndexOp/MapGetByKeyOp (using the same CDTContext), then convert it client-side. Or compose ExpStringToString with ExpListGetByIndex/ExpMapGetByKey inside an expression.
to_integer/to_double parse failures and to_string’s invalid-UTF-8 case both surface as OP_NOT_APPLICABLE, per the String operations error codes.
Regex and numeric-type flags
StringRegexFlags (combine with bitwise OR) controls regexCompare and regexReplace:
| Flag | Applies to |
|---|---|
StringRegexCaseInsensitive | Both |
StringRegexMultiline | Both |
StringRegexDotAll | Both |
StringRegexUnixLines | Both |
StringRegexGlobal | regexReplace only. Replaces every match instead of only the first. |
StringNumericType narrows isNumeric: StringNumericAny (default), StringNumericInt, or StringNumericFloat. StringNumericFloat requires a literal . followed by a digit, so StrIsNumericTypedOp(bin, StringNumericFloat) against "5" returns false even though "5" parses as a double.
Reading operate results
Booleans decode as booleans
contains, startsWith, endsWith, isNumeric, isUpper, isLower, and regexCompare decode as a native bool, not an integer 0/1:
record, err := client.Operate(nil, key, aerospike.StrContainsOp("email", "@"))if err != nil { log.Fatal(err)}hasAt := record.Bins["email"].(bool) // true or falseMultiple operations on one bin return a list
When more than one operation in a single Operate() call targets the same bin, the client groups that bin’s results into OpResults, a slice ([]interface{}) with one entry per operation. See Returning from operate() in Bin operations for the general grouping rule and WritePolicy.RespondPerEachOp.
record, err := client.Operate(nil, key, aerospike.StrTrimOp(aerospike.DefaultStringPolicy, "email"), // modify: entry 0 (nil) aerospike.StrLenOp("email"), // read: entry 1 aerospike.StrSubstrOp("email", 0, 5)) // read: entry 2if err != nil { log.Fatal(err)}
results := record.Bins["email"].(aerospike.OpResults)length := results[1].(int)head := results[2].(string)A single string operation on a bin, with nothing else targeting that bin, returns its value directly, with no OpResults wrapper.
Nested strings
Str*Op builders take an optional trailing CDTContext (or a list of them) 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 BIN_TYPE_ERROR. An invalid path, such as an out-of-bounds list index or a missing map key, also fails. See nested context for general CDTContext error behavior.
// Uppercase a string nested in a list bin "items" at index 0._, err := client.Operate(nil, key, aerospike.StrUpperOp(aerospike.DefaultStringPolicy, "items", aerospike.CtxListIndex(0)))if err != nil { log.Fatal(err)}
// Read strlen of a string nested under a map key.record, err := client.Operate(nil, key, aerospike.StrLenOp("profile", aerospike.CtxMapKey(aerospike.StringValue("bio"))))if err != nil { log.Fatal(err)}ExpString* builders don’t take a CDTContext at all. To apply a string expression to a nested value, project the value first with ExpListGetByIndex/ExpMapGetByKey (which do take CDTContext), then pass the result as the src argument. The following example builds an ExpStringLen condition, then uses it two ways: as a read filter, and as a projected read value.
import "github.com/aerospike/aerospike-client-go/v8/types"
bio := aerospike.ExpMapGetByKey(aerospike.MapReturnType.VALUE, aerospike.ExpTypeSTRING, aerospike.ExpStringVal("bio"), aerospike.ExpMapBin("profile"))isLong := aerospike.ExpGreater(aerospike.ExpStringLen(bio), aerospike.ExpIntVal(280))
// As a filter: fetch the record only if its bio is over 280 codepointsreadPolicy := aerospike.NewPolicy()readPolicy.FilterExpression = isLongrecord, err := client.Get(readPolicy, key)if err != nil { if err.Matches(types.FILTERED_OUT) { // Expected: the filter excluded the record } else { log.Fatal(err) }}
// As a projection: always fetch the record, with the condition's result in a computed binrecord, err = client.Operate(nil, key, aerospike.ExpReadOp("isLong", isLong, aerospike.ExpReadFlagDefault))if err != nil { log.Fatal(err)}bioIsLong := record.Bins["isLong"].(bool)StrToStringOp/ExpStringToString never accepts a CDTContext, on either surface. See Type conversion.
Version requirements
String operations require Aerospike Database 8.2.0 or later on every node, and Go client 8.9.0 or later. A server prior to 8.2.0 doesn’t recognize the string opcodes and returns a generic parameter error, not a distinct “unsupported feature” code. 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 go list -m github.com/aerospike/aerospike-client-go/v8 to confirm the installed client version.
Deprecations
- The legacy
AppendOp(bin)andPrependOp(bin)are deprecated for String bins only, in favor ofStrAppendOp/StrPrependOp, which are Unicode/DBCS-aware. The legacy pair does a raw byte concatenation and doesn’t supportStringPolicyorCDTContext. - Both legacy operations also accept Blob bins, which the string package can’t target. For a Blob bin, keep using
AppendOp/PrependOp. There’s no string-module replacement, and the legacy behavior is unchanged and fully supported for that case. - The legacy
ExpRegexCompare(POSIX regex) is deprecated in favor ofExpStringRegexCompare/ExpStringRegexCompareWithFlags, which are Unicode-aware (ICU regex).
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 - Go
- Expressions - Go: building and using expressions, filter policies, and
ExpReadOp/ExpWriteOp - Error handling - Go: the
AerospikeError/Error.Matches()pattern - Error codes: full server status code list, including String-operation-specific entries
- API reference (Go)