---
title: "AEL reference"
description: "AEL reference guide: syntax, operators, functions, and implementation in Java and Python Aerospike SDKs."
---

# AEL reference

> For the complete documentation index see: [llms.txt](https://aerospike.com/docs/llms.txt)
> 
> All documentation pages available in markdown.

AEL is a text-based DSL that compiles to Aerospike server expressions. It is used for filter expressions (`.where()`) and as the source of read/write expressions in both Java and Python: `.selectFrom()` / `.select_from()`, `.insertFrom()` / `.insert_from()`, `.updateFrom()` / `.update_from()`, and `.upsertFrom()` / `.upsert_from()`.

## Using AEL in the SDK

### Filter expressions with `.where()`

```java
session.query(users)

    .where("$.age > 21 and $.status == 'active'")

    .execute();
```

> 📖 **API reference**: [`Session.query(DataSet)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/Session.html#query%28com.aerospike.client.sdk.DataSet%29) | [`ChainableQueryBuilder.where(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#where%28java.lang.String%2Cjava.lang.Object...%29) | [`ChainableQueryBuilder.execute()`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#execute%28%29)

```python
stream = await session.query(users).where("$.age > 21 and $.status == 'active'").execute()

# stream.close() is synchronous (not await)
```

::: note
String comparisons are case-sensitive.
:::

> 📖 **API reference**: [`Session.query()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/session.html#aerospike%5Fsdk.aio.session.Session.query) | [`QueryBuilder.where()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/query.html#aerospike%5Fsdk.aio.operations.query.QueryBuilder.where) | [`RecordStream.close()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/record-stream.html#aerospike%5Fsdk.record%5Fstream.RecordStream.close) | [`QueryBuilder.execute()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/query.html#aerospike%5Fsdk.aio.operations.query.QueryBuilder.execute)

Single-key and batch operations:

```java
session.update(users.id("u1"))

    .bin("lastSeen").setTo(System.currentTimeMillis())

    .where("$.status == 'active'")

    .execute();
```

> 📖 **API reference**: [`DataSet.id(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/DataSet.html#id%28java.lang.String%29) | [`Session.update(DataSet)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/Session.html#update%28com.aerospike.client.sdk.DataSet%29) | [`ChainableOperationBuilder.bin(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableOperationBuilder.html#bin%28java.lang.String%29) | [`ChainableQueryBuilder.bin(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#bin%28java.lang.String%29) | [`ChainableQueryBuilder.where(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#where%28java.lang.String%2Cjava.lang.Object...%29) | [`ChainableQueryBuilder.execute()`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#execute%28%29)

```python
import time

await (

    session.update(users.id("u1"))

    .bin("lastSeen").set_to(int(time.time() * 1000))

    .where("$.status == 'active'")

    .execute()

)
```

> 📖 **API reference**: [`DataSet.id()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/dataset.html#aerospike%5Fsdk.dataset.DataSet.id) | [`Session.update()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/session.html#aerospike%5Fsdk.aio.session.Session.update) | [`QueryBuilder.where()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/query.html#aerospike%5Fsdk.aio.operations.query.QueryBuilder.where) | [`QueryBuilder.bin()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/query.html#aerospike%5Fsdk.aio.operations.query.QueryBuilder.bin) | [`WriteSegmentBuilder.set_to()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/write-segment.html#aerospike%5Fsdk.aio.operations.query.WriteSegmentBuilder.set%5Fto) | [`WriteSegmentBuilder.bin()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/write-segment.html#aerospike%5Fsdk.aio.operations.query.WriteSegmentBuilder.bin) | [`WriteSegmentBuilder.execute()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/write-segment.html#aerospike%5Fsdk.aio.operations.query.WriteSegmentBuilder.execute)

### Parameterized expressions with `PreparedAel`

Avoid string concatenation by using placeholders. Java uses `$1`, `$2`, … and Python uses `?0`, `?1`, … in the AEL string.

```java
PreparedAel prepared = new PreparedAel("$.age > $1 and $.name == $2");

session.query(users)

    .where(prepared, 21, "Tim")

    .execute();
```

> 📖 **API reference**: [`Session.query(DataSet)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/Session.html#query%28com.aerospike.client.sdk.DataSet%29) | [`ChainableQueryBuilder.where(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#where%28java.lang.String%2Cjava.lang.Object...%29) | [`ChainableQueryBuilder.execute()`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#execute%28%29)

```python
from aerospike_sdk import parse_ael

# Parameterized expression (?0, ?1, ... in the string; values are bound positionally)

expr = parse_ael("$.age > ?0 and $.name == ?1", 21, "Tim")

stream = await session.query(users).where(expr).execute()
```

> 📖 **API reference**: [`Session.query()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/session.html#aerospike%5Fsdk.aio.session.Session.query) | [`QueryBuilder.where()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/query.html#aerospike%5Fsdk.aio.operations.query.QueryBuilder.where) | [`QueryBuilder.execute()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/query.html#aerospike%5Fsdk.aio.operations.query.QueryBuilder.execute)

Java placeholders are 1-based in the string and 0-based in the params array; Python placeholders are 0-based in both the string and the bound values.

## Quick syntax reference

### Record paths

Every path starts with `$`:

```plaintext
$.binName             -- scalar bin

$.profile.name        -- map key access

$.scores.[0]          -- list index access

$.data.users.[2].name -- deeply nested
```

### Bin type inference

The first context element determines the bin type:

| Path | Inferred type |
| --- | --- |
| `$.x.name` | Map (first context is identifier) |
| `$.x.[0]` | List (first context is `[`) |
| `$.x > 5` | Scalar (no context) |

### Comparison operators

```plaintext
$.age > 21

$.name == 'Tim'

$.price >= 100.0

$.status != 'inactive'
```

Both sides can be bins (requires explicit type):

```plaintext
$.binA.get(type: INT) > $.binB.get(type: INT)
```

### The `in` operator

```plaintext
$.name in ["Bob", "Mary", "Richard"]

"gold" in $.allowedStatuses
```

### Logical operators

```plaintext
$.age > 21 and $.status == 'active'

$.role == 'admin' or $.role == 'super'

not($.age > 65)

exclusive($.a > 10, $.b > 5)
```

`and` binds tighter than `or`. Use parentheses to clarify:

```plaintext
$.a > 1 and ($.b > 2 or $.c > 3)
```

### Arithmetic

```plaintext
($.price * $.qty) > 1000

($.apples + 5) > 10

abs($.score - 50) < 10

max($.a, $.b, $.c) > 100

$.value % 2 == 0

$.base ** 2 > 100
```

Both operands must be the same type. Use `asInt()` / `asFloat()` to convert between numeric types, or to cast a numerically-valued bin before comparison:

```plaintext
$.intBin + $.floatBin.asInt()

$.count.asFloat() > 3.14

3.14.asInt() == 3
```

`asInt()` / `asFloat()` converts between the two numeric types (`INT` ↔ `FLOAT`). It does not parse string-typed bins. A bin stored as the string `"42"` cannot be compared numerically with `.asInt()`. Store the value as an integer-typed bin instead.

**Integer division and `%`**: When both operands of `/` are `INT` bins, Aerospike performs integer division only when the result is exact. A division that produces a remainder (such as `460 / 3`) is evaluated as a `FLOAT`. Because `%` (modulo) requires integer operands on both sides, using `%` after a non-exact division raises `ParameterError` on the server. To stay safe with `%`, either:

-   design fixture data so all divisions are exact (such as `10 / 2`, not `10 / 3`), or
-   cast back with `.asInt()` before applying `%`: `($.a / $.b).asInt() % 10`.

::: no bitwise operators in ael
AEL does not support C-style bitwise operators (`>>`, `<<`, `&`, `|`, `^`, `~`). Attempting to use them causes an `AelParseException`. To test bit patterns, use `%` (modulo) combined with integer arithmetic instead:

```plaintext
-- Check if bit 0 is set (value is odd)

$.flags % 2 == 1

-- Check if value falls in a bit-aligned range

$.flags % 8 >= 4
```
:::

### Record metadata

```plaintext
$.ttl() < 3600                  -- expires in < 1 hour

$.recordSize() > 1024           -- large records

$.sinceUpdate() < 7200000       -- updated in last 2 hours

$.isTombstone()                 -- deleted records

$.setName() == 'critical'

$.digestModulo(3) == 0          -- partition sampling
```

| Function | Returns | Description |
| --- | --- | --- |
| `$.ttl()` | INT | Time-to-live in seconds |
| `$.voidTime()` | INT | Absolute expiry (-1 = never) |
| `$.lastUpdate()` | INT | Last update (ns since epoch) |
| `$.sinceUpdate()` | INT | Ms since last update |
| `$.setName()` | STRING | Record’s set name |
| `$.keyExists()` | BOOL | Whether user key is stored |
| `$.isTombstone()` | BOOL | Whether record is deleted |
| `$.recordSize()` | INT | Total size in bytes |
| `$.deviceSize()` | INT | Storage size on device |
| `$.memorySize()` | INT | Size in memory |
| `$.digestModulo(n)` | INT | Digest modulo n |

## CDT path patterns

### Map access

```plaintext
$.profile.name                  -- string key (dot notation)

$.profile.["name"]              -- string key (bracket notation, equivalent)

$.profile.'special-key'         -- quoted string key (dot notation, for keys with special chars)

$.profile.["special-key"]       -- bracket notation alternative

$.m.1                           -- integer key

$.m.{1}                         -- map by index

$.m.{=bb}                       -- map by value

$.m.{#1}                        -- map by rank
```

**Reserved keywords require bracket notation.** Some identifiers are reserved in the AEL grammar (`type`, `count`, `exists`, `get`, `in`, `and`, `or`, `not`, etc.). Using them as map key names with dot notation causes an `AelParseException`. Always use bracket notation for such keys:

```plaintext
-- Fails if 'type' is a reserved word:

$.metadata.type == 'fixture'

-- Correct: bracket notation bypasses keyword parsing

$.metadata.["type"] == 'fixture'
```

### List access

```plaintext
$.scores.[0]                    -- by index

$.scores.[-1]                   -- last element

$.scores.[=42]                  -- by value

$.scores.[#0]                   -- by rank (lowest)
```

### Plural selectors (leaf only)

```plaintext
$.m.{a-d}                       -- key range [a, d)  ← dash separates KEY range bounds

$.m.{a,b,c}                     -- key list

$.m.{0:3}                       -- index range  ← colon separates INDEX range bounds

$.m.{=10:20}                    -- value range

$.m.{#-3:}                      -- top 3 by rank

$.l.[1:5]                        -- list index range

$.l.[=1,2,3]                    -- list value list

$.l.[#0:3]                      -- list rank range
```

**Key range vs index range: do not confuse the delimiters.** `{a-d}` uses a dash (`-`) and selects by key (string or integer keys in lexicographic/numeric order). `{0:3}` uses a colon (`:`) and selects by position index. Using `:` with string bounds like `{room1:room3}` is a parse error; the correct form is `{room1-room3}`.

```plaintext
-- String key range from "room1" up to (but not including) "room3":

$.rooms.{room1-room3}

-- Count of entries in that range:

$.rooms.{room1-room3}.count()

-- WRONG: colon is for index ranges, not key ranges:

$.rooms.{room1:room3}   ← parse error
```

### Inverted selections (prefix `!`)

```plaintext
$.m.{!a-d}                      -- everything except keys a-c

$.l.[!0:3]                      -- everything except indices 0-2

$.m.{!=temp,draft}              -- everything except these keys
```

## Path functions

### `get()` — explicit type and return

```plaintext
$.binName.get(type: INT)

$.mapBin.{a,b}.get(return: KEY_VALUE)

$.listBin.[0:3].get(return: COUNT)
```

| Parameter | Values |
| --- | --- |
| `type` | `INT`, `STRING`, `FLOAT`, `BOOL`, `BLOB`, `HLL`, `LIST`, `MAP`, `GEO` |
| `return` | `VALUE`, `COUNT`, `INDEX`, `RANK`, `NONE`, `EXISTS`, `KEY`, `KEY_VALUE`, `ORDERED_MAP`, `UNORDERED_MAP` |

### `count()` — element count

```plaintext
$.listBin.[].count()             -- list size

$.mapBin.{}.count()              -- map size

$.listBin.[=4].count() > 0      -- count of elements equal to 4
```

### `exists()` — existence check

```plaintext
$.binA.exists() and $.binB.exists()

$.mapBin.address.exists()
```

### GeoJSON

Compare two GeoJSON values with `geoCompare(a, b)`. Both parameters can be either a bin path or a `geoJson('...')` literal. Returns **true** if the left is either contained within or contains the right.

```plaintext
geoCompare($.loc, geoJson('{"type":"Point","coordinates":[-122.349,47.620]}'))

geoCompare(geoJson('{"type":"AeroCircle","coordinates":[[-122.0,37.4],3000.0]}'), $.loc)
```

Bins typed as `GEO` are recognized automatically inside `geoCompare(...)`. Use with a [GEO2DSPHERE secondary index](https://aerospike.com/docs/develop/data-types/geospatial) on the bin. Write GeoJSON bins with `set_to_geo_json(...)`. See [Update records](https://aerospike.com/docs/develop/client/sdk/usage/update) for more information about updating records.

### HyperLogLog (HLL)

HLL path functions operate on bins typed as `HLL`. They mirror the server [HyperLogLog read operations](https://aerospike.com/docs/develop/data-types/hll) and the [HLL bin expressions](https://aerospike.com/docs/develop/expressions/hll-bin) reference (`hll_get_count`, `hll_get_union_count`, and so on). Use them in `where(...)` filters and other Boolean AEL contexts.

The receiver path (`$.h` in the table) is the HLL bin the function runs against.

#### Read functions

| Function | Parameters | Returns | Description |
| --- | --- | --- | --- |
| `$.h.hllCount()` | — | `INT` | Estimated number of unique entries in the sketch. Uses the cached count; see [`refresh_count`](https://aerospike.com/docs/develop/data-types/hll#refresh_count) on the underlying HLL type if the bin was modified since the last count. |
| `$.h.hllDescribe()` | — | `LIST` | Two-element list `[index_bit_count, min_hash_bit_count]` describing the sketch precision. |
| `$.h.hllMayContain(list)` | `LIST` of values to check | `INT` (`1` or `0`) | Returns `1` if the sketch **may** contain all listed elements (probabilistic membership test), otherwise `0`. |
| `$.h.hllUnion(list)` | `LIST` of HLL values | `HLL` | HLL value that is the union of the receiver bin and every HLL in `list`. |
| `$.h.hllUnionCount(list)` | `LIST` of HLL values | `INT` | Estimated cardinality of the union of the receiver bin and the HLL values in `list`. |
| `$.h.hllIntersectCount(list)` | `LIST` of HLL values | `INT` | Estimated cardinality of the intersection of the receiver bin and the HLL values in `list`. When minhash bits are `0`, at most two HLLs can participate; with minhash enabled, more than two are allowed. |
| `$.h.hllSimilarity(list)` | `LIST` of HLL values | `FLOAT` | Estimated [Jaccard similarity](https://en.wikipedia.org/wiki/Jaccard_index) between the receiver bin and the HLL values in `list` (typically `0.0`–`1.0`). Same minhash constraint as `hllIntersectCount`. |

For `hllUnion`, `hllUnionCount`, `hllIntersectCount`, and `hllSimilarity`, pass a **list of HLL values**. A single bin path such as `$.cohort_a` is accepted; AEL wraps it into a one-element list. To combine multiple bins, pass an explicit list: `[ $.cohort_a, $.cohort_b ]`.

#### Write functions (not supported in Developer SDK AEL)

The AEL grammar also defines modify functions that return an updated HLL value. The Developer SDK does **not** support write-side HLL in AEL filters. Initialize and update sketches with the builder API (`hll_init`, `hll_add` in Python; `hllInit`, `hllAdd` in Java) instead. See [Update records](https://aerospike.com/docs/develop/client/sdk/usage/update).

| Function | Parameters | Returns | Description |
| --- | --- | --- | --- |
| `$.h.hllInit(indexBits: [, minHashBits:])` | `INT` \[, `INT`\] | `HLL` | Create or reset the sketch with the given index (and optional minhash) bit counts. |
| `$.h.hllAdd(list [, indexBits: [, minHashBits:]])` | `LIST` \[, `INT` \[, `INT`\]\] | `HLL` | Add the list elements to the sketch. Optional `indexBits` / `minHashBits` create the bin if it does not exist. |

#### Examples

```plaintext
$.h.hllCount() > 1000000

$.h.hllDescribe() == [14, 0]

$.h.hllMayContain(['alice', 'bob']) == 1

$.h.hllUnionCount($.cohort_a) > 50000

$.h.hllUnionCount([$.cohort_a, $.cohort_b]) > 50000

$.h.hllIntersectCount($.cohort_a) > 100

$.h.hllSimilarity($.cohort_a) >= 0.8

$.h.hllUnion($.cohort_a) == ?0
```

Compare estimated counts and similarities against thresholds in filters (for example high-cardinality segments or overlapping audiences). For modify-then-read composition with server expressions, see the [HLL bin expressions](https://aerospike.com/docs/develop/expressions/hll-bin) reference.

## Control structures

### Conditional: `when`

```plaintext
when ($.tier == 1 => 'gold', $.tier == 2 => 'silver', default => 'bronze')
```

`default` clause is mandatory.

### Variable binding: `let ... then`

```plaintext
let (total = $.price * $.qty, discount = ${total} / 10) then (${total} - ${discount} > 400)
```

Variables reference earlier variables with `${name}`.

#### Float literals in `let` expressions cause silent failures

If any value in a `let` binding is a float literal (such as `0.1`, `1.5`) or produces a `FLOAT` result, but the bin it is combined with is `INT`, the expression silently returns no matches. There is no parse error. This also applies to `${var} * 0.1` where `${var}` holds an integer.

Use only integer arithmetic in `let` expressions unless all operands are explicitly floats.

```plaintext
-- Safe: all INT arithmetic

let (total = $.price * $.qty, discount = ${total} / 10) then (${total} - ${discount} > 400)

-- Silent failure: INT * float literal

let (total = $.price * $.qty, tax = ${total} * 0.1) then (${total} + ${tax} > 900)

-- Fixed: keep everything INT (scale by 1000 or use integer percentages)

let (total = $.price * $.qty, tax_pct = 10, tax = ${total} / ${tax_pct}) then (${total} + ${tax} > 990)
```

**Type consistency in `let` bindings.** All arithmetic within a `let` expression must use consistent types. Mixing `INT` and `FLOAT` values in the same arithmetic expression (such as `${total}` is `INT` but `$.discount_rate` is `FLOAT`) causes the expression to silently return no matches. There is no parse error, but the filter produces zero results.

To avoid this:

-   Store all fixture bins used in `let` arithmetic as the same type (all `INT` or all `FLOAT`), or
    
-   Use `.asFloat()` / `.asInt()` explicitly to cast before combining.
    

```plaintext
-- All INT arithmetic:

let (total = $.price * $.qty, discount = ${total} / 10) then (${total} - ${discount} > 400)

-- Mixed types (NOTE: INT * FLOAT silently fails if types don't align):

let (total = $.price * $.qty, discount = ${total} * $.discount_rate) then (${total} - ${discount} > 400)

-- Fixed with explicit cast:

let (total = $.price * $.qty, discount = ${total}.asFloat() * $.discount_rate) then (${total}.asFloat() - ${discount} > 400.0)
```

## Read expressions with `selectFrom`

`selectFrom` evaluates an AEL expression server-side and returns the result as a virtual bin. The bin doesn’t need to exist — the expression can reference any bins on the record.

```java
Record rec = session.query(users.id("u1"))

    .bin("total").selectFrom("$.price * $.quantity")

    .bin("ageIn10Years").selectFrom("$.age + 10")

    .execute()

    .getFirstRecord();

long total = rec.getLong("total");

long futureAge = rec.getLong("ageIn10Years");
```

> 📖 **API reference**: [`DataSet.id(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/DataSet.html#id%28java.lang.String%29) | [`Session.query(Key)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/Session.html#query%28com.aerospike.client.sdk.Key%29) | [`ChainableQueryBuilder.bin(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#bin%28java.lang.String%29) | [`ChainableQueryBuilder.execute()`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#execute%28%29) | [`RecordStream.getFirstRecord()`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/RecordStream.html#getFirstRecord%28%29) | [`Record.getLong(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/Record.html#getLong%28java.lang.String%29)

```python
stream = await (

    session.query(users.id("u1"))

    .bin("total").select_from("$.price * $.quantity")

    .bin("ageIn10Years").select_from("$.age + 10")

    .execute()

)

first = await stream.first_or_raise()

total = first.record.bins["total"]

stream.close()
```

> 📖 **API reference**: [`DataSet.id()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/dataset.html#aerospike%5Fsdk.dataset.DataSet.id) | [`Session.query()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/session.html#aerospike%5Fsdk.aio.session.Session.query) | [`QueryBuilder.bin()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/query.html#aerospike%5Fsdk.aio.operations.query.QueryBuilder.bin) | [`WriteSegmentBuilder.bin()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/write-segment.html#aerospike%5Fsdk.aio.operations.query.WriteSegmentBuilder.bin) | [`RecordStream.first_or_raise()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/record-stream.html#aerospike%5Fsdk.record%5Fstream.RecordStream.first%5For%5Fraise) | [`RecordStream.close()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/record-stream.html#aerospike%5Fsdk.record%5Fstream.RecordStream.close) | [`QueryBuilder.execute()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/query.html#aerospike%5Fsdk.aio.operations.query.QueryBuilder.execute)

The result bin name is whatever you pass to `.bin(...)` — it doesn’t need to match any existing bin. Use `ignoreEvalFailure()` to skip records where the expression can’t evaluate:

```java
session.query(users)

    .bin("ratio").selectFrom("$.a / $.b", opt -> opt.ignoreEvalFailure())

    .execute();
```

> 📖 **API reference**: [`Session.query(DataSet)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/Session.html#query%28com.aerospike.client.sdk.DataSet%29) | [`ChainableQueryBuilder.bin(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#bin%28java.lang.String%29) | [`ChainableQueryBuilder.execute()`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#execute%28%29)

## Write expressions with `insertFrom`, `updateFrom`, `upsertFrom`

These evaluate an AEL expression server-side and write the result into a bin, with different existence semantics:

| Method | Behavior |
| --- | --- |
| `upsertFrom(ael)` | Creates or overwrites the bin |
| `insertFrom(ael)` | Creates only — fails with `BIN_EXISTS_ERROR` if bin exists |
| `updateFrom(ael)` | Updates only — fails with `BIN_NOT_FOUND` if bin doesn’t exist |

```java
session.upsert(users.id("u1"))

    .bin("total").upsertFrom("$.price * $.quantity")

    .bin("discount").insertFrom("$.coupon * 0.1")

    .execute();
```

> 📖 **API reference**: [`DataSet.id(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/DataSet.html#id%28java.lang.String%29) | [`Session.upsert(DataSet)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/Session.html#upsert%28com.aerospike.client.sdk.DataSet%29) | [`Session.upsert(Key)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/Session.html#upsert%28com.aerospike.client.sdk.Key%29) | [`ChainableOperationBuilder.bin(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableOperationBuilder.html#bin%28java.lang.String%29) | [`ChainableQueryBuilder.bin(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#bin%28java.lang.String%29) | [`ChainableQueryBuilder.execute()`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#execute%28%29)

```python
await (

    session.upsert(users.id("u1"))

    .bin("total").upsert_from("$.price * $.quantity")

    .bin("discount").insert_from("$.coupon * 0.1")

    .execute()

)
```

> 📖 **API reference**: [`DataSet.id()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/dataset.html#aerospike%5Fsdk.dataset.DataSet.id) | [`Session.upsert()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/session.html#aerospike%5Fsdk.aio.session.Session.upsert) | [`QueryBuilder.bin()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/query.html#aerospike%5Fsdk.aio.operations.query.QueryBuilder.bin) | [`WriteSegmentBuilder.bin()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/write-segment.html#aerospike%5Fsdk.aio.operations.query.WriteSegmentBuilder.bin) | [`WriteSegmentBuilder.execute()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/write-segment.html#aerospike%5Fsdk.aio.operations.query.WriteSegmentBuilder.execute)

Options for write expressions:

```java
session.upsert(users.id("u1"))

    .bin("discount").upsertFrom("$.coupon", opt -> opt

        .deleteIfNull()         // delete the bin if expression returns null

        .ignoreEvalFailure())   // don't fail if expression can't evaluate

    .bin("bonus").insertFrom("$.base * 1.5", opt -> opt

        .ignoreOpFailure())     // don't fail if bin already exists

    .execute();
```

> 📖 **API reference**: [`DataSet.id(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/DataSet.html#id%28java.lang.String%29) | [`Session.upsert(DataSet)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/Session.html#upsert%28com.aerospike.client.sdk.DataSet%29) | [`Session.upsert(Key)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/Session.html#upsert%28com.aerospike.client.sdk.Key%29) | [`ChainableOperationBuilder.bin(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableOperationBuilder.html#bin%28java.lang.String%29) | [`ChainableQueryBuilder.bin(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#bin%28java.lang.String%29) | [`ChainableQueryBuilder.execute()`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#execute%28%29)

## Where clause integration patterns

### On dataset queries

```java
session.query(users)

    .where("$.age > 21")

    .execute();
```

> 📖 **API reference**: [`Session.query(DataSet)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/Session.html#query%28com.aerospike.client.sdk.DataSet%29) | [`ChainableQueryBuilder.where(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#where%28java.lang.String%2Cjava.lang.Object...%29) | [`ChainableQueryBuilder.execute()`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#execute%28%29)

```python
stream = await session.query(users).where("$.age > 21").execute()
```

> 📖 **API reference**: [`Session.query()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/session.html#aerospike%5Fsdk.aio.session.Session.query) | [`QueryBuilder.where()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/query.html#aerospike%5Fsdk.aio.operations.query.QueryBuilder.where) | [`QueryBuilder.execute()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/query.html#aerospike%5Fsdk.aio.operations.query.QueryBuilder.execute)

### On single-key / batch operations

```java
session.update(users.ids("u1", "u2"))

    .bin("bonus").add(100)

    .where("$.department == 'engineering'")

    .execute();
```

> 📖 **API reference**: [`DataSet.ids(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/DataSet.html#ids%28java.lang.String...%29) | [`Session.update(DataSet)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/Session.html#update%28com.aerospike.client.sdk.DataSet%29) | [`ChainableOperationBuilder.bin(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableOperationBuilder.html#bin%28java.lang.String%29) | [`ChainableQueryBuilder.bin(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#bin%28java.lang.String%29) | [`BinBuilder.add(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/BinBuilder.html#add%28int%29) | [`ChainableQueryBuilder.where(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#where%28java.lang.String%2Cjava.lang.Object...%29) | [`ChainableQueryBuilder.execute()`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#execute%28%29)

```python
await (

    session.update(users.ids("u1", "u2"))

    .bin("bonus").add(100)

    .where("$.department == 'engineering'")

    .execute()

)
```

> 📖 **API reference**: [`DataSet.ids()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/dataset.html#aerospike%5Fsdk.dataset.DataSet.ids) | [`Session.update()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/session.html#aerospike%5Fsdk.aio.session.Session.update) | [`QueryBuilder.where()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/query.html#aerospike%5Fsdk.aio.operations.query.QueryBuilder.where) | [`QueryBuilder.bin()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/query.html#aerospike%5Fsdk.aio.operations.query.QueryBuilder.bin) | [`WriteSegmentBuilder.bin()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/write-segment.html#aerospike%5Fsdk.aio.operations.query.WriteSegmentBuilder.bin) | [`WriteSegmentBuilder.execute()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/write-segment.html#aerospike%5Fsdk.aio.operations.query.WriteSegmentBuilder.execute)

### On mixed batch chains (per-operation and default)

```java
session

    .update(users.id("u1")).bin("bonus").add(100)

        .where("$.department == 'engineering'")

    .update(users.id("u2")).bin("bonus").add(50)

    .defaultWhere("$.active == true")

    .execute();
```

> 📖 **API reference**: [`DataSet.id(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/DataSet.html#id%28java.lang.String%29) | [`ChainableQueryBuilder.bin(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#bin%28java.lang.String%29) | [`BinBuilder.add(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/BinBuilder.html#add%28int%29) | [`ChainableQueryBuilder.where(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#where%28java.lang.String%2Cjava.lang.Object...%29) | [`ChainableQueryBuilder.execute()`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#execute%28%29)

```python
await (

    session.update(users.id("u1")).bin("bonus").add(100).where("$.department == 'engineering'")

    .update(users.id("u2")).bin("bonus").add(50)

    .default_where("$.active == true")

    .execute()

)
```

> 📖 **API reference**: [`DataSet.id()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/dataset.html#aerospike%5Fsdk.dataset.DataSet.id) | [`Session.update()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/session.html#aerospike%5Fsdk.aio.session.Session.update) | [`QueryBuilder.where()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/query.html#aerospike%5Fsdk.aio.operations.query.QueryBuilder.where) | [`QueryBuilder.bin()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/query.html#aerospike%5Fsdk.aio.operations.query.QueryBuilder.bin) | [`WriteSegmentBuilder.bin()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/write-segment.html#aerospike%5Fsdk.aio.operations.query.WriteSegmentBuilder.bin) | [`WriteSegmentBuilder.execute()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/write-segment.html#aerospike%5Fsdk.aio.operations.query.WriteSegmentBuilder.execute)

### With `PreparedAel` for reuse

```java
PreparedAel activeInDept = new PreparedAel("$.active == true and $.department == $1");

session.query(users)

    .where(activeInDept, "engineering")

    .execute();

session.query(users)

    .where(activeInDept, "marketing")

    .execute();
```

> 📖 **API reference**: [`Session.query(DataSet)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/Session.html#query%28com.aerospike.client.sdk.DataSet%29) | [`ChainableQueryBuilder.where(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#where%28java.lang.String%2Cjava.lang.Object...%29) | [`ChainableQueryBuilder.execute()`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#execute%28%29)

```python
from aerospike_sdk import parse_ael

# Same template string; bind parameters per query via parse_ael(...)

expr_eng = parse_ael("$.active == true and $.department == ?0", "engineering")

stream_eng = await session.query(users).where(expr_eng).execute()

stream_eng.close()  # sync, not await

expr_mkt = parse_ael("$.active == true and $.department == ?0", "marketing")

stream_mkt = await session.query(users).where(expr_mkt).execute()

stream_mkt.close()
```

> 📖 **API reference**: [`Session.query()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/session.html#aerospike%5Fsdk.aio.session.Session.query) | [`QueryBuilder.where()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/query.html#aerospike%5Fsdk.aio.operations.query.QueryBuilder.where) | [`RecordStream.close()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/record-stream.html#aerospike%5Fsdk.record%5Fstream.RecordStream.close) | [`QueryBuilder.execute()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/query.html#aerospike%5Fsdk.aio.operations.query.QueryBuilder.execute)