Skip to content

Context for operations on nested elements

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_CREATE or LIST_INDEX_CREATE to create the path as you go. See map context examples.

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. Most operations in the List and Map API take an optional context parameter.

The context is a list of element selectors targeting a specific nested element. Each element selector includes a context type and a value.

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 2

We 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)])
Record record = client.operate(null, key,
ListOperation.append("l", Value.get(100), CTX.listIndex(-1)),
Operation.get("l")
);

Result:

[0, 1, [2, [3, 4, 100], 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)
Record record = client.operate(null, key,
ListOperation.append("l", Value.get(100)),
Operation.get("l")
);

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 26 — no change
Record record = client.operate(null, key,
ListOperation.append("l", Value.get(100), CTX.listIndex(0)),
Operation.get("l")
);
// throws AerospikeException: Error 26 OP_NOT_APPLICABLE

Error: error code 26 OP_NOT_APPLICABLE

A list context selector must target a list element, and a map context selector must target a map element. Applying a list operation to a scalar value fails.


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)])
Record record = client.operate(null, key,
ListOperation.append("l", Value.get(100),
CTX.listIndex(2), CTX.listIndex(1)),
Operation.get("l")
);

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 1

We 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])])
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")
);

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 runnable
map_increment('m', 'jokes', 317, context=[MAP_KEY_CREATE('stats'), MAP_KEY_CREATE('accolades')])
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")
);

Result:

{"name": "chuck norris", "stats": {"accolades": {"jokes": 317}}}

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 to true. 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"));
Record record = client.operate(null, key,
CdtOperation.selectByPath("vehicles", SelectFlags.VALUE,
CTX.allChildrenWithFilter(isTesla),
CTX.mapKey(Value.get("license"))));

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 SQL WHERE 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. You can only have one expression at any level, so AND_FILTER:
    • Cannot be chained after a previous AND_FILTER(exp).
    • Cannot be used after an ALL_CHILDREN or ALL_CHILDREN_WITH_FILTER context.
    • Not supported inside expression-wrapped operations (CdtExp.selectByPath). Use direct CDT operations (CdtOperation.selectByPath / CdtOperation.modifyByPath) instead.
CdtOperation.selectByPath("myBin", 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 level

For usage examples and performance comparisons, see path expressions performance.

Language-specific client APIs

Feedback

Was this page helpful?

What type of feedback are you giving?

What would you like us to know?

+Capture screenshot

Can we reach out to you?