Context for operations on nested elements
For the complete documentation index see: llms.txt
All documentation pages available in markdown.
Every List and Map operation targets a specific element within a list or map bin. For elements at the top level of the collection, no extra addressing is needed. For nested elements, a context provides the path from the bin to the target, one selector per nesting level.
- Top-level elements: no context is needed.
- Nested elements: supply a context with one selector per nesting level.
- Missing intermediate elements: use
MAP_KEY_CREATEorLIST_INDEX_CREATEto create the path as you go. See Map context examples. - Depth limit: as of Aerospike Database 8.2.0, Lists and Maps support up to 64 levels of nesting. Keep a context path within that depth, including one that creates missing levels. See Nesting depth limit for how depth is counted.
CDT context API
The following describes the CDT context API in generic terms. Each language
client might have slightly different terms to express the same concepts, such as
the Java client’s CTX class.
The context is a list of element selectors targeting a specific nested element. Each element selector includes a context type and a value.
Which operations take a context
Most operations in the List, Map, and String APIs take an optional context parameter, on both reads and writes. See String context examples.
Blob and HLL operations do not. They act on the value of the bin itself, so a blob or an HLL stored inside a list or a map cannot be modified in place — read the element out, change it, and write it back.
Element selectors
The following element selectors can be applied starting from the top level of the collection, forming an increasingly deeper path into it.
BY_LIST_INDEX(index)BY_LIST_RANK(rank)BY_LIST_VALUE(value)BY_MAP_INDEX(index)BY_MAP_RANK(rank)BY_MAP_KEY(key)BY_MAP_VALUE(value)
Each element selector must identify exactly one element. BY_LIST_VALUE and
BY_MAP_VALUE require an exact value, so WILDCARD
cannot be used here because it might match multiple elements. WILDCARD is only
valid in list and
map *_by_value and *_by_value_list operations.
To select and operate on multiple elements at once, use
path expression contexts.
Create-if-missing selectors
The following selectors create an element if it does not exist, then select it.
This is similar to how mkdir -p creates intermediate directories.
MAP_KEY_CREATE(key)LIST_INDEX_CREATE(index)
See the map context example for a worked example.
List context examples
Consider the following list stored in bin ‘l’:
[0, 1, [2, [3, 4], 5, 6], 7, [8, 9]]This list can be visualized as:
[0, 1, [ ], 7, [ ]] depth 0 (top level) 2, [ ], 5, 6 8, 9 depth 1 3, 4 depth 2We can operate on the list element [3, 4] by identifying a context for the
operation using [BY_LIST_INDEX(2), BY_LIST_INDEX(1)].
- The first selector
BY_LIST_INDEX(2)selects the third element of the top level list. The element selected by it is the list[2, [3, 4], 5, 6]. - The second selector
BY_LIST_INDEX(1)selects the element at index position 1. The element selected by it is the list[3, 4]. - A list API operation can now be applied to one of the elements within this nested list.
At the top level we have five elements, three of them scalar integer values, two of them are list values.
The list value at index position 2 has four elements: the integer value 2, a list element, then the integer values 5 and 6. Its list element at index position 1 has two elements, the integers 3 and 4.
Append at depth 1
Append the value 100 to the list nested at the last element of the top level.
# Pseudocode — not runnable# [0, 1, [2, [3, 4], 5, 6], 7, [8, 9]]list_append('l', 100, context=[BY_LIST_INDEX(-1)])try (RecordStream rs = session.upsert(key) .bin("l").onListIndex(-1).listAppend(100) .execute()) { Record rec = rs.next().recordOrThrow();}stream = (session.upsert(key) .bin("l").on_list_index(-1).list_append_items([100]) .execute())rec = stream.first_or_raise().record_or_raise()// Requires: use aerospike::operations::{self, cdt_context::ctx_list_index, lists};let record = client.operate(&WritePolicy::default(), &key, &[ lists::append(&ListPolicy::default(), "l", as_val!(100)).context(vec![ctx_list_index(-1)]), operations::get_bin("l"),]).await?;Record record = client.Operate(null, key, ListOperation.Append("l", Value.Get(100), CTX.ListIndex(-1)), Operation.Get("l"));// Requires: import as "github.com/aerospike/aerospike-client-go/v8"record, err := client.Operate(nil, key, as.ListAppendWithPolicyContextOp(as.DefaultListPolicy(), "l", []*as.CDTContext{as.CtxListIndex(-1)}, 100), as.GetBinOp("l"),)const Aerospike = await import("aerospike");const lists = Aerospike.lists;
const ops = [ lists.append('l', 100).withContext(ctx => ctx.addListIndex(-1)), Aerospike.operations.read('l')]const result = await client.operate(key, ops)as_cdt_ctx ctx;as_cdt_ctx_inita(&ctx, 1);as_cdt_ctx_add_list_index(&ctx, -1);
as_operations ops;as_operations_inita(&ops, 2);
as_integer v;as_integer_init(&v, 100);as_operations_list_append(&ops, "l", &ctx, NULL, (as_val*)&v);as_operations_add_read(&ops, "l");
as_record* rec = NULL;aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec);as_record_destroy(rec);as_operations_destroy(&ops);Record record = client.operate(null, key, ListOperation.append("l", Value.get(100), CTX.listIndex(-1)), Operation.get("l"));ops = [ list_operations.list_append("l", 100, None, [cdt_ctx.cdt_ctx_list_index(-1)]), operations.read("l")]_, _, bins = client.operate(key, ops)Result:
[0, 1, [2, [3, 4], 5, 6], 7, [8, 9, 100]]Without the context we are appending to the top level list.
# Pseudocode — not runnable# [0, 1, [2, [3, 4], 5, 6], 7, [8, 9]]list_append('l', 100)try (RecordStream rs = session.upsert(key) .bin("l").listAppend(100) .execute()) { Record rec = rs.next().recordOrThrow();}stream = (session.upsert(key) .bin("l").list_append(100) .execute())rec = stream.first_or_raise().record_or_raise()let record = client.operate(&WritePolicy::default(), &key, &[ lists::append(&ListPolicy::default(), "l", as_val!(100)), operations::get_bin("l"),]).await?;Record record = client.Operate(null, key, ListOperation.Append("l", Value.Get(100)), Operation.Get("l"));// Requires: import as "github.com/aerospike/aerospike-client-go/v8"record, err := client.Operate(nil, key, as.ListAppendOp("l", 100), as.GetBinOp("l"),)const ops = [ lists.append('l', 100), Aerospike.operations.read('l')]const result = await client.operate(key, ops)as_operations ops;as_operations_inita(&ops, 2);
as_integer v;as_integer_init(&v, 100);as_operations_list_append(&ops, "l", NULL, NULL, (as_val*)&v);as_operations_add_read(&ops, "l");
as_record* rec = NULL;aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec);as_record_destroy(rec);as_operations_destroy(&ops);Record record = client.operate(null, key, ListOperation.append("l", Value.get(100)), Operation.get("l"));ops = [ list_operations.list_append("l", 100), operations.read("l")]_, _, bins = client.operate(key, ops)Result:
[0, 1, [2, [3, 4], 5, 6], 7, [8, 9], 100]Error: selector targets wrong type
Append the value 100 to a list element at index position 0. There is an integer value at index 0, not a list.
# Pseudocode — not runnable# [0, 1, [2, [3, 4], 5, 6], 7, [8, 9]]list_append('l', 100, context=[BY_LIST_INDEX(0)])# Error 12 — no changetry (RecordStream rs = session.upsert(key) .bin("l").onListIndex(0).listAppend(100) .execute()) { Record rec = rs.next().recordOrThrow();}// recordOrThrow() throws AerospikeException: result code 12 (ResultCode.BIN_TYPE_ERROR)stream = (session.upsert(key) .bin("l").on_list_index(0).list_append_items([100]) .execute())rec = stream.first_or_raise().record_or_raise()# raises BinTypeError: result code 12let result = client.operate(&WritePolicy::default(), &key, &[ lists::append(&ListPolicy::default(), "l", as_val!(100)).context(vec![ctx_list_index(0)]), operations::get_bin("l"),]).await;// result is Err: result code 12 (ResultCode::BinTypeError)Record record = client.Operate(null, key, ListOperation.Append("l", Value.Get(100), CTX.ListIndex(0)), Operation.Get("l"));// throws AerospikeException: result code 12 (ResultCode.BIN_TYPE_ERROR)// Requires: import as "github.com/aerospike/aerospike-client-go/v8"// Requires: import "github.com/aerospike/aerospike-client-go/v8/types"record, err := client.Operate(nil, key, as.ListAppendWithPolicyContextOp(as.DefaultListPolicy(), "l", []*as.CDTContext{as.CtxListIndex(0)}, 100), as.GetBinOp("l"),)// err: result code 12 (types.BIN_TYPE_ERROR)const ops = [ lists.append('l', 100).withContext(ctx => ctx.addListIndex(0)), Aerospike.operations.read('l')]// throws AerospikeError: code 12 (status.ERR_BIN_INCOMPATIBLE_TYPE)const result = await client.operate(key, ops)as_cdt_ctx ctx;as_cdt_ctx_inita(&ctx, 1);as_cdt_ctx_add_list_index(&ctx, 0);
as_operations ops;as_operations_inita(&ops, 2);
as_integer v;as_integer_init(&v, 100);as_operations_list_append(&ops, "l", &ctx, NULL, (as_val*)&v);as_operations_add_read(&ops, "l");
as_record* rec = NULL;// returns AEROSPIKE_ERR_BIN_INCOMPATIBLE_TYPE (12)aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec);as_record_destroy(rec);as_operations_destroy(&ops);Record record = client.operate(null, key, ListOperation.append("l", Value.get(100), CTX.listIndex(0)), Operation.get("l"));// throws AerospikeException: result code 12 (ResultCode.BIN_TYPE_ERROR)ops = [ list_operations.list_append("l", 100, None, [cdt_ctx.cdt_ctx_list_index(0)]), operations.read("l")]# raises BinIncompatibleType: result code 12_, _, bins = client.operate(key, ops)Error: error code 12 AS_ERR_INCOMPATIBLE_TYPE
BY_LIST_INDEX(0) selects the integer 0; the append fails because it needs a list.
Append at depth 2
Append the value 100 to the deepest list, which is at depth 2. This requires a context with two selectors to navigate to that list.
# Pseudocode — not runnable# [0, 1, [2, [3, 4], 5, 6], 7, [8, 9]]list_append('l', 100, context=[BY_LIST_INDEX(2), BY_LIST_INDEX(1)])try (RecordStream rs = session.upsert(key) .bin("l").onListIndex(2).onListIndex(1).listAppend(100) .execute()) { Record rec = rs.next().recordOrThrow();}stream = (session.upsert(key) .bin("l").on_list_index(2).on_list_index(1).list_append_items([100]) .execute())rec = stream.first_or_raise().record_or_raise()let record = client.operate(&WritePolicy::default(), &key, &[ lists::append(&ListPolicy::default(), "l", as_val!(100)) .context(vec![ctx_list_index(2), ctx_list_index(1)]), operations::get_bin("l"),]).await?;Record record = client.Operate(null, key, ListOperation.Append("l", Value.Get(100), CTX.ListIndex(2), CTX.ListIndex(1)), Operation.Get("l"));// Requires: import as "github.com/aerospike/aerospike-client-go/v8"record, err := client.Operate(nil, key, as.ListAppendWithPolicyContextOp(as.DefaultListPolicy(), "l", []*as.CDTContext{as.CtxListIndex(2), as.CtxListIndex(1)}, 100), as.GetBinOp("l"),)const ops = [ lists.append('l', 100) .withContext(ctx => ctx.addListIndex(2).addListIndex(1)), Aerospike.operations.read('l')]const result = await client.operate(key, ops)as_cdt_ctx ctx;as_cdt_ctx_inita(&ctx, 2);as_cdt_ctx_add_list_index(&ctx, 2);as_cdt_ctx_add_list_index(&ctx, 1);
as_operations ops;as_operations_inita(&ops, 2);
as_integer v;as_integer_init(&v, 100);as_operations_list_append(&ops, "l", &ctx, NULL, (as_val*)&v);as_operations_add_read(&ops, "l");
as_record* rec = NULL;aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec);as_record_destroy(rec);as_operations_destroy(&ops);Record record = client.operate(null, key, ListOperation.append("l", Value.get(100), CTX.listIndex(2), CTX.listIndex(1)), Operation.get("l"));ops = [ list_operations.list_append("l", 100, None, [cdt_ctx.cdt_ctx_list_index(2), cdt_ctx.cdt_ctx_list_index(1)]), operations.read("l")]_, _, bins = client.operate(key, ops)Result:
[0, 1, [2, [3, 4, 100], 5, 6], 7, [8, 9]]Select by value with duplicates
In this example bin ‘l’ contains a list of tuples, including duplicates:
[[1, 1], [2, 2], [2, 2, 2], [2, 2], [1, 1]]This list can be visualized as:
[ [ ], [ ], [ ], [ ], [ ] ] depth 0 (top level) 1, 1 2, 2 2, 2, 2 2, 2 1, 1 depth 1We select the sub-list [2, 2] using BY_LIST_VALUE with an exact value.
Because there are multiple matching elements, the selector picks the first
one encountered in list order.
# Pseudocode — not runnable# [[1, 1], [2, 2], [2, 2, 2], [2, 2], [1, 1]]list_append('l', 3, context=[BY_LIST_VALUE([2, 2])])try (RecordStream rs = session.upsert(key) .bin("l").onListValue(List.of(2, 2)).listAppend(3) .execute()) { Record rec = rs.next().recordOrThrow();}stream = (session.upsert(key) .bin("l").on_list_value([2, 2]).list_append_items([3]) .execute())rec = stream.first_or_raise().record_or_raise()// Requires: use aerospike::operations::cdt_context::ctx_list_value;let record = client.operate(&WritePolicy::default(), &key, &[ lists::append(&ListPolicy::default(), "l", as_val!(3)) .context(vec![ctx_list_value(as_list!(2, 2))]), operations::get_bin("l"),]).await?;IList<Value> matchValue = new List<Value> { Value.Get(2), Value.Get(2) };
Record record = client.Operate(null, key, ListOperation.Append("l", Value.Get(3), CTX.ListValue(Value.Get(matchValue))), Operation.Get("l"));// Requires: import as "github.com/aerospike/aerospike-client-go/v8"record, err := client.Operate(nil, key, as.ListAppendWithPolicyContextOp(as.DefaultListPolicy(), "l", []*as.CDTContext{ as.CtxListValue(as.NewValue([]int{2, 2})), }, 3), as.GetBinOp("l"),)const ops = [ lists.append('l', 3) .withContext(ctx => ctx.addListValue([2, 2])), Aerospike.operations.read('l')]const result = await client.operate(key, ops)as_arraylist match_val;as_arraylist_inita(&match_val, 2);as_arraylist_append_int64(&match_val, 2);as_arraylist_append_int64(&match_val, 2);
as_cdt_ctx ctx;as_cdt_ctx_inita(&ctx, 1);as_cdt_ctx_add_list_value(&ctx, (as_val*)&match_val);
as_operations ops;as_operations_inita(&ops, 2);
as_integer v;as_integer_init(&v, 3);as_operations_list_append(&ops, "l", &ctx, NULL, (as_val*)&v);as_operations_add_read(&ops, "l");
as_record* rec = NULL;aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec);as_record_destroy(rec);as_operations_destroy(&ops);List<Value> matchValue = List.of(Value.get(2), Value.get(2));
Record record = client.operate(null, key, ListOperation.append("l", Value.get(3), CTX.listValue(Value.get(matchValue))), Operation.get("l"));ops = [ list_operations.list_append("l", 3, None, [cdt_ctx.cdt_ctx_list_value([2, 2])]), operations.read("l")]_, _, bins = client.operate(key, ops)Result:
The sub-list [2, 2] at index 1 is selected and gets the appended element.
The duplicate [2, 2] at index 3 is not modified.
[[1, 1], [2, 2, 3], [2, 2, 2], [2, 2], [1, 1]]Map context examples
Create a nested map path
The MAP_KEY_CREATE selector creates intermediate map entries that do not
yet exist, then selects them. This is useful when building up nested structures
incrementally.
In the following example we want to add accolades to the stats of an actor. The data is in bin ‘m’:
{"name": "chuck norris"}We want to increment
a jokes accolade by 317, but neither "stats", "accolades", nor "jokes"
exists yet. Using MAP_KEY_CREATE, the operation creates the full path and
succeeds.
# Pseudocode — not runnablemap_increment('m', 'jokes', 317, context=[MAP_KEY_CREATE('stats'), MAP_KEY_CREATE('accolades')])try (RecordStream rs = session.upsert(key) .bin("m").onMapKey("stats", MapOrder.UNORDERED) .onMapKey("accolades", MapOrder.UNORDERED) .onMapKey("jokes").add(317) .execute()) { Record rec = rs.next().recordOrThrow();}from aerospike_sdk import MapOrder
stream = (session.upsert(key) .bin("m").on_map_key("stats", create_type=MapOrder.UNORDERED) .on_map_key("accolades", create_type=MapOrder.UNORDERED) .on_map_key("jokes").add(317) .execute())rec = stream.first_or_raise().record_or_raise()// Requires: use aerospike::operations::{cdt_context::ctx_map_key_create, maps::{self, MapOrder}};let record = client.operate(&WritePolicy::default(), &key, &[ maps::increment_value(&MapPolicy::default(), "m", as_val!("jokes"), as_val!(317)) .context(vec![ ctx_map_key_create(as_val!("stats"), MapOrder::Unordered), ctx_map_key_create(as_val!("accolades"), MapOrder::Unordered), ]), operations::get_bin("m"),]).await?;Record record = client.Operate(null, key, MapOperation.Increment(MapPolicy.Default, "m", Value.Get("jokes"), Value.Get(317), CTX.MapKeyCreate(Value.Get("stats"), MapOrder.UNORDERED), CTX.MapKeyCreate(Value.Get("accolades"), MapOrder.UNORDERED)), Operation.Get("m"));// Requires: import as "github.com/aerospike/aerospike-client-go/v8"record, err := client.Operate(nil, key, as.MapIncrementOp(as.DefaultMapPolicy(), "m", "jokes", 317, as.CtxMapKeyCreate(as.StringValue("stats"), as.MapOrder.UNORDERED), as.CtxMapKeyCreate(as.StringValue("accolades"), as.MapOrder.UNORDERED)), as.GetBinOp("m"),)const maps = Aerospike.maps;
const context = new Aerospike.cdt.Context() .addMapKeyCreate('stats') .addMapKeyCreate('accolades')
const ops = [ maps.increment('m', 'jokes', 317) .withContext(context), Aerospike.operations.read('m')]const result = await client.operate(key, ops)as_cdt_ctx ctx;as_cdt_ctx_inita(&ctx, 2);as_cdt_ctx_add_map_key_create(&ctx, (as_val*)as_string_new("stats", false), AS_MAP_UNORDERED);as_cdt_ctx_add_map_key_create(&ctx, (as_val*)as_string_new("accolades", false), AS_MAP_UNORDERED);
as_map_policy mp;as_map_policy_init(&mp);
as_operations ops;as_operations_inita(&ops, 2);
as_string mk;as_string_init(&mk, "jokes", false);as_integer mv;as_integer_init(&mv, 317);as_operations_map_increment(&ops, "m", &ctx, &mp, (as_val*)&mk, (as_val*)&mv);as_operations_add_read(&ops, "m");
as_record* rec = NULL;aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec);as_record_destroy(rec);as_operations_destroy(&ops);Record record = client.operate(null, key, MapOperation.increment(MapPolicy.Default, "m", Value.get("jokes"), Value.get(317), CTX.mapKeyCreate(Value.get("stats"), MapOrder.UNORDERED), CTX.mapKeyCreate(Value.get("accolades"), MapOrder.UNORDERED)), Operation.get("m"));ops = [ map_operations.map_increment("m", "jokes", 317, None, [cdt_ctx.cdt_ctx_map_key_create("stats", aerospike.MAP_UNORDERED), cdt_ctx.cdt_ctx_map_key_create("accolades", aerospike.MAP_UNORDERED)]), operations.read("m")]_, _, bins = client.operate(key, ops)Result:
{"name": "chuck norris", "stats": {"accolades": {"jokes": 317}}}String context examples
Uppercase a nested string
String operations reach a String nested in a List or
Map through the same context path as List and Map operations. Every string operation except
to_string accepts one.
The data is in bin ‘v’:
[{"license": "abc-123"}]We want to upper the license of the first
vehicle. LIST_INDEX selects the element, MAP_KEY selects the field, and the string
operation transforms the value in place.
# Pseudocode — not runnable# [{"license": "abc-123"}]string_upper('v', context=[LIST_INDEX(0), MAP_KEY('license')])try (RecordStream rs = session.upsert(key) .bin("v").onListIndex(0).onMapKey("license").upper() .execute()) { Record rec = rs.next().recordOrThrow();}stream = (session.upsert(key) .bin("v").on_list_index(0).on_map_key("license").str_upper() .execute())rec = stream.first_or_raise().record_or_raise()// Requires: use aerospike::operations::{cdt_context::ctx_map_key, string::{self as str_op, StringPolicy}};let record = client.operate(&WritePolicy::default(), &key, &[ str_op::upper(&StringPolicy::default(), "v") .context(vec![ctx_list_index(0), ctx_map_key(as_val!("license"))]), operations::get_bin("v"),]).await?;Record record = client.Operate(null, key, StringOperation.Upper(StringPolicy.Default, "v", CTX.ListIndex(0), CTX.MapKey(Value.Get("license"))), Operation.Get("v"));// Requires: import as "github.com/aerospike/aerospike-client-go/v8"record, err := client.Operate(nil, key, as.StrUpperOp(nil, "v", as.CtxListIndex(0), as.CtxMapKey(as.NewValue("license"))), as.GetBinOp("v"),)const strings = Aerospike.strings;
const ops = [ strings.upper('v').withContext(ctx => ctx.addListIndex(0).addMapKey('license')), Aerospike.operations.read('v')]const result = await client.operate(key, ops)as_cdt_ctx ctx;as_cdt_ctx_inita(&ctx, 2);as_cdt_ctx_add_list_index(&ctx, 0);as_cdt_ctx_add_map_key(&ctx, (as_val*)as_string_new("license", false));
as_operations ops;as_operations_inita(&ops, 2);as_operations_string_upper(&ops, "v", &ctx, NULL);as_operations_add_read(&ops, "v");
as_record* rec = NULL;aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec);as_record_destroy(rec);as_operations_destroy(&ops);Record record = client.operate(null, key, StringOperation.upper(StringPolicy.Default, "v", CTX.listIndex(0), CTX.mapKey(Value.get("license"))), Operation.get("v"));ops = [ string_operations.upper("v", None, [cdt_ctx.cdt_ctx_list_index(0), cdt_ctx.cdt_ctx_map_key("license")]), operations.read("v")]_, _, bins = client.operate(key, ops)The bin now holds [{"license": "ABC-123"}].
If the path does not resolve — no element at index 0, or no license key — the operation
returns AS_ERR_OP_NOT_APPLICABLE. Unlike a missing bin, a missing nested path is not created,
even by the string operations that create a missing bin. Set NO_FAIL to turn an unresolved
path into a success that writes nothing.
Path expression contexts
The contexts described above select a single element at each level. Starting
with Aerospike Database 8.1.1,
path expressions extend this model to select
and filter multiple elements at once, using selectByPath and
modifyByPath instead of traditional single-element CDT operations.
Matching and filtering (8.1.1+)
ALL_CHILDREN- matches all children of the current Map or List without filtering.ALL_CHILDREN_WITH_FILTER(exp)- matches children of the current Map or List where the filter expression evaluates totrue. The filter can assign an aspect of the current element (its value, key or index) to a loop variable.
The following example uses the vehicles data
model, a list of maps within a bin called vehicles. It reads the license plates
of all vehicles where make is "Tesla":
Exp isTesla = Exp.eq( MapExp.getByKey(MapReturnType.VALUE, Exp.Type.STRING, Exp.val("make"), Exp.mapLoopVar(LoopVarPart.VALUE)), Exp.val("Tesla"));
try (RecordStream rs = session.query(key) .bin("vehicles").onEachChild(isTesla).onMapKey("license").collectValues() .execute()) { Record rec = rs.next().recordOrThrow();}from aerospike_sdk import Exp, ExpType, LoopVarPart, MapReturnType
is_tesla = Exp.eq( Exp.map_get_by_key(MapReturnType.VALUE, ExpType.STRING, Exp.val("make"), Exp.map_loop_var(LoopVarPart.VALUE), []), Exp.val("Tesla"))
stream = (session.query(key) .bin("vehicles").on_each_child_where(is_tesla) .on_map_key("license").collect_values() .execute())rec = stream.first_or_raise().record_or_raise()// Requires: use aerospike::expressions::{eq, exp_map_loop_var, maps as map_exp, string_val, ExpType, LoopVarPart};// Requires: use aerospike::operations::{cdt_context::ctx_all_children_with_filter, path::{self, SelectFlag}};let is_tesla = eq( map_exp::get_by_key(MapReturnType::Value, ExpType::STRING, string_val("make".to_string()), exp_map_loop_var(LoopVarPart::VALUE), &[]), string_val("Tesla".to_string()));
let record = client.operate(&WritePolicy::default(), &key, &[ path::select_by_path("vehicles", SelectFlag::VALUE, vec![ ctx_all_children_with_filter(is_tesla), ctx_map_key(as_val!("license")), ]),]).await?;Exp isTesla = Exp.EQ( MapExp.GetByKey(MapReturnType.VALUE, Exp.Type.STRING, Exp.Val("make"), Exp.MapLoopVar(LoopVarPart.VALUE)), Exp.Val("Tesla"));
Record record = client.Operate(null, key, CDTOperation.SelectByPath("vehicles", SelectFlag.VALUE, CTX.AllChildrenWithFilter(isTesla), CTX.MapKey(Value.Get("license"))));// Requires: import as "github.com/aerospike/aerospike-client-go/v8"isTesla := as.ExpEq( as.ExpMapGetByKey( as.MapReturnType.VALUE, as.ExpTypeSTRING, as.ExpStringVal("make"), as.ExpMapLoopVar(as.VALUE), ), as.ExpStringVal("Tesla"),)
record, err := client.Operate(nil, key, as.SelectByPath("vehicles", as.EXP_PATH_SELECT_VALUE, as.CtxAllChildrenWithFilter(isTesla), as.CtxMapKey(as.NewStringValue("license"))),)const exp = Aerospike.exp;const Context = Aerospike.cdt.Context;
const isTesla = exp.eq( exp.maps.getByKey( exp.loopVarMap(exp.loopVarPart.VALUE), exp.str('make'), exp.type.STR, maps.returnType.VALUE ), exp.str('Tesla'))
const context = new Context() .addAllChildrenWithFilter(isTesla) .addMapKey('license')
const ops = [ Aerospike.operations.selectByPath('vehicles', exp.pathSelectFlags.VALUE, context)]const result = await client.operate(key, ops)as_exp_build(is_tesla, as_exp_cmp_eq( as_exp_map_get_by_key(NULL, AS_MAP_RETURN_VALUE, AS_EXP_TYPE_STR, as_exp_str("make"), as_exp_loopvar_map(AS_EXP_LOOPVAR_VALUE)), as_exp_str("Tesla")));
as_cdt_ctx ctx;as_cdt_ctx_inita(&ctx, 2);as_cdt_ctx_add_all_children_with_filter(&ctx, is_tesla);as_cdt_ctx_add_map_key(&ctx, (as_val*)as_string_new("license", false));
as_operations ops;as_operations_inita(&ops, 1);as_operations_select_by_path(&err, &ops, "vehicles", &ctx, AS_EXP_PATH_SELECT_VALUE);
as_record* rec = NULL;aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec);as_record_destroy(rec);as_operations_destroy(&ops);as_exp_destroy(is_tesla);Exp isTesla = Exp.eq( MapExp.getByKey(MapReturnType.VALUE, Exp.Type.STRING, Exp.val("make"), Exp.mapLoopVar(LoopVarPart.VALUE)), Exp.val("Tesla"));
Record record = client.operate(null, key, CdtOperation.selectByPath("vehicles", SelectFlags.VALUE, CTX.allChildrenWithFilter(isTesla), CTX.mapKey(Value.get("license"))));is_tesla = exp.Eq( exp.MapGetByKey( ctx=None, return_type=aerospike.MAP_RETURN_VALUE, value_type=exp.ResultType.STRING, key=exp.Val("make"), bin=exp.LoopVarMap(aerospike.EXP_LOOPVAR_VALUE) ), exp.Val("Tesla")).compile()
ctx = [ cdt_ctx.cdt_ctx_all_children_with_filter(is_tesla), cdt_ctx.cdt_ctx_map_key("license"),]ops = [ operations.select_by_path( bin_name="vehicles", ctx=ctx, flags=aerospike.EXP_PATH_SELECT_VALUE)]_, _, bins = client.operate(key, ops)Expected output: ["6ABC123"]
Key selection and combined filtering (8.1.2+)
-
MAP_KEYS_IN(keys...)- select map entries whose keys match any of the provided values. This is equivalent to a SQLWHERE key IN (k1, k2, ...)clause and uses the map’s internal index for efficient lookup. -
AND_FILTER(exp)- apply an additional filter expression at the same level as the preceding context. Entries must satisfy both the preceding context and this filter to be included.AND_FILTER:- Cannot be the first context, because it filters what the preceding context selects.
- Cannot be chained after a previous
AND_FILTER(exp), because you can only have one expression at any level. - Cannot be used after an
ALL_CHILDRENorALL_CHILDREN_WITH_FILTERcontext, which already carries that level’s expression.
The server rejects each of these with error 4 (
AS_ERR_PARAMETER), or with error 26 (AS_ERR_OP_NOT_APPLICABLE) when the path runs inside an expression.
The following example uses the booking data model, a map bin doc keyed by room ID. It selects rooms 10001 and 10003, keeps the ones that are
not deleted and have a time after 1780000000, and returns each one’s rates whose beta is
above 0:
from aerospike_sdk import Exp, ExpType, LoopVarPart, MapReturnType
def field(name, value_type): return Exp.map_get_by_key(MapReturnType.VALUE, value_type, Exp.val(name), Exp.map_loop_var(LoopVarPart.VALUE), [])
time_filter = Exp.gt(field("time", ExpType.INT), Exp.val(1780000000))deleted_filter = Exp.eq(field("isDeleted", ExpType.BOOL), Exp.val(False))rate_filter = Exp.gt(field("beta", ExpType.FLOAT), Exp.val(0.0))
stream = (session.query(key) .bin("doc").on_map_keys_in([10001, 10003]) # select rooms by ID .and_filter(Exp.and_([time_filter, deleted_filter])) # additional room-level filters .on_each_child() # descend into each room's children .on_each_child_where(rate_filter) # filter at the rates level .collect_matching_tree(no_fail=True) .execute())rec = stream.first_or_raise().record_or_raise()// Requires: use aerospike::expressions::{and, bool_val, float_val, gt, int_val};// Requires: use aerospike::operations::cdt_context::{ctx_all_children, ctx_and_filter, ctx_map_keys_in};let field = |name: &str, value_type: ExpType| map_exp::get_by_key(MapReturnType::Value, value_type, string_val(name.to_string()), exp_map_loop_var(LoopVarPart::VALUE), &[]);let time_filter = gt(field("time", ExpType::INT), int_val(1780000000));let deleted_filter = eq(field("isDeleted", ExpType::BOOL), bool_val(false));let rate_filter = gt(field("beta", ExpType::FLOAT), float_val(0.0));
let record = client.operate(&WritePolicy::default(), &key, &[ path::select_by_path("doc", SelectFlag(SelectFlag::MATCHING_TREE.0 | SelectFlag::NO_FAIL.0), vec![ ctx_map_keys_in(vec![10001_i64, 10003]), // select rooms by ID ctx_and_filter(and(vec![time_filter, deleted_filter])), // additional room-level filters ctx_all_children(), // descend into each room's children ctx_all_children_with_filter(rate_filter), // filter at the rates level ]),]).await?;Exp timeFilter = Exp.GT( MapExp.GetByKey(MapReturnType.VALUE, Exp.Type.INT, Exp.Val("time"), Exp.MapLoopVar(LoopVarPart.VALUE)), Exp.Val(1780000000L));Exp deletedFilter = Exp.EQ( MapExp.GetByKey(MapReturnType.VALUE, Exp.Type.BOOL, Exp.Val("isDeleted"), Exp.MapLoopVar(LoopVarPart.VALUE)), Exp.Val(false));Exp rateFilter = Exp.GT( MapExp.GetByKey(MapReturnType.VALUE, Exp.Type.FLOAT, Exp.Val("beta"), Exp.MapLoopVar(LoopVarPart.VALUE)), Exp.Val(0.0));
Record record = client.Operate(null, key, CDTOperation.SelectByPath("doc", SelectFlag.MATCHING_TREE | SelectFlag.NO_FAIL, CTX.MapKeysIn(10001L, 10003L), // select rooms by ID CTX.AndFilter(Exp.And(timeFilter, deletedFilter)), // additional room-level filters CTX.AllChildren(), // descend into each room's children CTX.AllChildrenWithFilter(rateFilter)) // filter at the rates level);// Requires: import as "github.com/aerospike/aerospike-client-go/v8"field := func(name string, valueType as.ExpType) *as.Expression { return as.ExpMapGetByKey(as.MapReturnType.VALUE, valueType, as.ExpStringVal(name), as.ExpMapLoopVar(as.VALUE))}timeFilter := as.ExpGreater(field("time", as.ExpTypeINT), as.ExpIntVal(1780000000))deletedFilter := as.ExpEq(field("isDeleted", as.ExpTypeBOOL), as.ExpBoolVal(false))rateFilter := as.ExpGreater(field("beta", as.ExpTypeFLOAT), as.ExpFloatVal(0.0))
record, err := client.Operate(nil, key, as.SelectByPath("doc", as.EXP_PATH_SELECT_MATCHING_TREE|as.EXP_PATH_SELECT_NO_FAIL, as.CtxMapIntKeysIn(10001, 10003), // select rooms by ID as.CtxAndFilter(as.ExpAnd(timeFilter, deletedFilter)), // additional room-level filters as.CtxAllChildren(), // descend into each room's children as.CtxAllChildrenWithFilter(rateFilter)), // filter at the rates level)const field = (name, type) => exp.maps.getByKey( exp.loopVarMap(exp.loopVarPart.VALUE), exp.str(name), type, maps.returnType.VALUE)const timeFilter = exp.gt(field('time', exp.type.INT), exp.int(1780000000))const deletedFilter = exp.eq(field('isDeleted', exp.type.BOOL), exp.bool(false))const rateFilter = exp.gt(field('beta', exp.type.FLOAT), exp.float(0.0))
const context = new Context() .addMapKeysIn([10001, 10003]) // select rooms by ID .addAndFilter(exp.and(timeFilter, deletedFilter)) // additional room-level filters .addAllChildren() // descend into each room's children .addAllChildrenWithFilter(rateFilter) // filter at the rates level
const ops = [ Aerospike.operations.selectByPath('doc', exp.pathSelectFlags.MATCHING_TREE | exp.pathSelectFlags.NO_FAIL, context)]const result = await client.operate(key, ops)as_exp_build(room_level_filter, as_exp_and( as_exp_cmp_gt( as_exp_map_get_by_key(NULL, AS_MAP_RETURN_VALUE, AS_EXP_TYPE_INT, as_exp_str("time"), as_exp_loopvar_map(AS_EXP_LOOPVAR_VALUE)), as_exp_int(1780000000)), as_exp_cmp_eq( as_exp_map_get_by_key(NULL, AS_MAP_RETURN_VALUE, AS_EXP_TYPE_BOOL, as_exp_str("isDeleted"), as_exp_loopvar_map(AS_EXP_LOOPVAR_VALUE)), as_exp_bool(false))));as_exp_build(rate_filter, as_exp_cmp_gt( as_exp_map_get_by_key(NULL, AS_MAP_RETURN_VALUE, AS_EXP_TYPE_FLOAT, as_exp_str("beta"), as_exp_loopvar_map(AS_EXP_LOOPVAR_VALUE)), as_exp_float(0.0)));
as_arraylist room_ids;as_arraylist_inita(&room_ids, 2);as_arraylist_append_int64(&room_ids, 10001);as_arraylist_append_int64(&room_ids, 10003);
as_cdt_ctx ctx;as_cdt_ctx_inita(&ctx, 4);as_cdt_ctx_add_map_keys_in(&ctx, (as_list*)&room_ids); // select rooms by IDas_cdt_ctx_add_and_filter(&ctx, room_level_filter); // additional room-level filtersas_cdt_ctx_add_all_children(&ctx); // descend into each room's childrenas_cdt_ctx_add_all_children_with_filter(&ctx, rate_filter); // filter at the rates level
as_operations ops;as_operations_inita(&ops, 1);as_operations_select_by_path(&err, &ops, "doc", &ctx, AS_EXP_PATH_SELECT_MATCHING_TREE | AS_EXP_PATH_SELECT_NO_FAIL);
as_record* rec = NULL;aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec);as_record_destroy(rec);as_operations_destroy(&ops);as_exp_destroy(room_level_filter);as_exp_destroy(rate_filter);Exp timeFilter = Exp.gt( MapExp.getByKey(MapReturnType.VALUE, Exp.Type.INT, Exp.val("time"), Exp.mapLoopVar(LoopVarPart.VALUE)), Exp.val(1780000000L));Exp deletedFilter = Exp.eq( MapExp.getByKey(MapReturnType.VALUE, Exp.Type.BOOL, Exp.val("isDeleted"), Exp.mapLoopVar(LoopVarPart.VALUE)), Exp.val(false));Exp rateFilter = Exp.gt( MapExp.getByKey(MapReturnType.VALUE, Exp.Type.FLOAT, Exp.val("beta"), Exp.mapLoopVar(LoopVarPart.VALUE)), Exp.val(0.0));
Record record = client.operate(null, key, CdtOperation.selectByPath("doc", SelectFlags.MATCHING_TREE | SelectFlags.NO_FAIL, CTX.mapKeysIn(10001L, 10003L), // select rooms by ID CTX.andFilter(Exp.and(timeFilter, deletedFilter)), // additional room-level filters CTX.allChildren(), // descend into each room's children CTX.allChildrenWithFilter(rateFilter))); // filter at the rates leveldef field(name, value_type): return exp.MapGetByKey(None, aerospike.MAP_RETURN_VALUE, value_type, name, exp.LoopVarMap(aerospike.EXP_LOOPVAR_VALUE))
time_filter = exp.GT(field("time", exp.ResultType.INTEGER), 1780000000)deleted_filter = exp.Eq(field("isDeleted", exp.ResultType.BOOLEAN), False)rate_filter = exp.GT(field("beta", exp.ResultType.FLOAT), 0.0).compile()
ctx = [ cdt_ctx.cdt_ctx_map_keys_in([10001, 10003]), # select rooms by ID cdt_ctx.cdt_ctx_and_filter( exp.And(time_filter, deleted_filter).compile()), # additional room-level filters cdt_ctx.cdt_ctx_all_children(), # descend into each room's children cdt_ctx.cdt_ctx_all_children_with_filter(rate_filter), # filter at the rates level]ops = [ operations.select_by_path( bin_name="doc", ctx=ctx, flags=aerospike.EXP_PATH_SELECT_MATCHING_TREE | aerospike.EXP_PATH_SELECT_NO_FAIL),]_, _, bins = client.operate(key, ops)Expected output: room 10001, with only its first rate.
For usage examples and performance comparisons, see path expressions performance.