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 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 server
client, 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, write
record, 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:

SurfaceNamingUsed withArgument order
OperationStr*Opclient.Operate()Bin name first: aerospike.StrLenOp(binName, ctx...)
ExpressionExpString*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 name
aerospike.StrUpperOp(aerospike.DefaultStringPolicy, "text")
// Expression: policy, then source expression
aerospike.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)
FlagValueEffect
StringWriteDefault0No create/update restriction. See the create-capability note in the following paragraph for what happens against a missing bin.
StringWriteCreateOnly1Applies 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.
StringWriteUpdateOnly2Applies the operation only to an existing bin, disabling bin creation. Valid on every modify operation. Cannot be combined with StringWriteCreateOnly.
StringWriteNoFail4Suppress 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).

OperationGo buildersReturnsDescription
strlenStrLenOp / ExpStringLenintegerCodepoint count.
byte_lengthStrByteLengthOp / ExpStringByteLengthintegerUTF-8 byte count. Differs from strlen for non-ASCII text.
substrStrSubstrFromOp, StrSubstrOp / ExpStringSubstrFrom, ExpStringSubstrstringSubstring from start to the end, or the half-open range [start, end).
char_atStrCharAtOp / ExpStringCharAtstringThe one-codepoint string at index.
findStrFindOp, StrFindNthOp / ExpStringFind, ExpStringFindNthintegerCodepoint index of needle, or of a specific occurrence (1 = first, -1 = last). -1 if not found.
containsStrContainsOp / ExpStringContainsbooleanWhether the bin contains needle.
starts_withStrStartsWithOp / ExpStringStartsWithbooleanWhether the bin begins with prefix.
ends_withStrEndsWithOp / ExpStringEndsWithbooleanWhether the bin ends with suffix.
to_integerStrToIntegerOp / ExpStringToIntegerintegerParses the string as an int64. Fails if it doesn’t parse. See Type conversion.
to_doubleStrToDoubleOp / ExpStringToDoublefloatParses the string as a 64-bit float. Fails if it doesn’t parse.
is_numericStrIsNumericOp, StrIsNumericTypedOp / ExpStringIsNumeric, ExpStringIsNumericTypedbooleanWhether the bin’s spelling matches an optional StringNumericType (StringNumericAny, StringNumericInt, or StringNumericFloat).
is_upper / is_lowerStrIsUpperOp, StrIsLowerOp / ExpStringIsUpper, ExpStringIsLowerbooleanWhether 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_blobStrToBlobOp / ExpStringToBlobblobThe UTF-8 bytes of the string, as a Blob.
splitStrSplitOp, StrSplitBySeparatorOp / ExpStringSplit, ExpStringSplitBySeparatorlistSplits by Unicode codepoint, or by separator (a singleton list if separator isn’t found).
b64_decodeStrB64DecodeOp / ExpStringB64DecodeblobDecodes the bin as base64 text into a Blob. Fails if it isn’t valid base64.
regex_compareStrRegexCompareOp, StrRegexCompareWithFlagsOp / ExpStringRegexCompare, ExpStringRegexCompareWithFlagsbooleanMatches 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.

OperationGo buildersDescription
insertStrInsertOp / ExpStringInsertSplices value in at codepoint index.
overwriteStrOverwriteOp / ExpStringOverwriteOverwrites codepoints starting at index with value.
concatStrConcatOp, StrConcatListOp / ExpStringConcatAppends one string, or each element of a list of strings, in order.
appendStrAppendOp / ExpStringAppendAppends value. Unicode/DBCS-aware (handles double-byte character sets correctly), unlike the legacy AppendOp.
prependStrPrependOp / ExpStringPrependPrepends value. Unicode/DBCS-aware, unlike the legacy PrependOp.
pad_startStrPadStartOp / ExpStringPadStartLeft-pads with padString up to targetLength codepoints. No-op if the bin already meets or exceeds targetLength codepoints.
pad_endStrPadEndOp / ExpStringPadEndRight-pads with padString up to targetLength codepoints.
repeatStrRepeatOp / ExpStringRepeatRepeats the bin count times.
snipStrSnipOp / ExpStringSnipRemoves the half-open codepoint range [start, end). Both start and end are required arguments.
replaceStrReplaceOp / ExpStringReplaceReplaces the first occurrence of needle with replacement.
replace_allStrReplaceAllOp / ExpStringReplaceAllReplaces every occurrence of needle with replacement.
upper / lowerStrUpperOp, StrLowerOp / ExpStringUpper, ExpStringLowerUppercases or lowercases the bin.
case_foldStrCaseFoldOp / ExpStringCaseFoldApplies locale-independent case folding, for comparison keys.
normalize_nfcStrNormalizeNFCOp / ExpStringNormalizeNFCNormalizes the bin to Unicode NFC form. Already-normalized strings are unchanged.
trim / trim_start / trim_endStrTrimOp, StrTrimStartOp, StrTrimEndOp / ExpStringTrim, ExpStringTrimStart, ExpStringTrimEndRemoves Unicode whitespace from both ends, the start, or the end.
regex_replaceStrRegexReplaceOp / ExpStringRegexReplaceReplaces 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:

FlagApplies to
StringRegexCaseInsensitiveBoth
StringRegexMultilineBoth
StringRegexDotAllBoth
StringRegexUnixLinesBoth
StringRegexGlobalregexReplace 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 false

Multiple 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 2
if 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 codepoints
readPolicy := aerospike.NewPolicy()
readPolicy.FilterExpression = isLong
record, 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 bin
record, 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) and PrependOp(bin) are deprecated for String bins only, in favor of StrAppendOp/StrPrependOp, which are Unicode/DBCS-aware. The legacy pair does a raw byte concatenation and doesn’t support StringPolicy or CDTContext.
  • 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 of ExpStringRegexCompare/ExpStringRegexCompareWithFlags, which are Unicode-aware (ICU regex).

Next steps