Skip to content

AEL (Aerospike Expression Language) reference

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().

Full AEL syntax reference: docs/ael-documentation.md

Using AEL in the SDK

Filter expressions with .where()

session.query(users)
.where("$.age > 21 and $.status == 'active'")
.execute();
stream = await session.query(users).where("$.age > 21 and $.status == 'active'").execute()
# stream.close() is synchronous (not await)

Single-key and batch operations:

session.update(users.id("u1"))
.bin("lastSeen").setTo(System.currentTimeMillis())
.where("$.status == 'active'")
.execute();
import time
await (
session.update(users.id("u1"))
.bin("lastSeen").set_to(int(time.time() * 1000))
.where("$.status == 'active'")
.execute()
)

Parameterized expressions with PreparedAel

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

PreparedAel prepared = new PreparedAel("$.age > $1 and $.name == $2");
session.query(users)
.where(prepared, 21, "Tim")
.execute();
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()

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 $:

$.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:

PathInferred type
$.x.nameMap (first context is identifier)
$.x.[0]List (first context is [)
$.x > 5Scalar (no context)

Comparison operators

$.age > 21
$.name == 'Tim'
$.price >= 100.0
$.status != 'inactive'

Both sides can be bins (requires explicit type):

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

The in operator

$.name in ["Bob", "Mary", "Richard"]
"gold" in $.allowedStatuses

Logical operators

$.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:

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

Arithmetic

($.price * $.qty) > 1000
($.apples + 5) > 10
abs($.score - 50) < 10
max($.a, $.b, $.c) > 100

Both operands must be the same type. Use asInt() / asFloat() to convert:

$.intBin + $.floatBin.asInt()

Record metadata

$.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
FunctionReturnsDescription
$.ttl()INTTime-to-live in seconds
$.voidTime()INTAbsolute expiry (-1 = never)
$.lastUpdate()INTLast update (ns since epoch)
$.sinceUpdate()INTMs since last update
$.setName()STRINGRecord’s set name
$.keyExists()BOOLWhether user key is stored
$.isTombstone()BOOLWhether record is deleted
$.recordSize()INTTotal size in bytes
$.deviceSize()INTStorage size on device
$.memorySize()INTSize in memory
$.digestModulo(n)INTDigest modulo n

CDT path patterns

Map access

$.profile.name -- string key
$.profile.'special-key' -- quoted string key
$.m.1 -- integer key
$.m.{1} -- map by index
$.m.{=bb} -- map by value
$.m.{#1} -- map by rank

List access

$.scores.[0] -- by index
$.scores.[-1] -- last element
$.scores.[=42] -- by value
$.scores.[#0] -- by rank (lowest)

Plural selectors (leaf only)

$.m.{a-d} -- key range [a, d)
$.m.{a,b,c} -- key list
$.m.{0:3} -- index range
$.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

Inverted selections (prefix !)

$.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

$.binName.get(type: INT)
$.mapBin.{a,b}.get(return: KEY_VALUE)
$.listBin.[0:3].get(return: COUNT)
ParameterValues
typeINT, STRING, FLOAT, BOOL, BLOB, HLL, LIST, MAP, GEO
returnVALUE, COUNT, INDEX, RANK, NONE, EXISTS, KEY, KEY_VALUE, ORDERED_MAP, UNORDERED_MAP

count() — element count

$.listBin.[].count() -- list size
$.mapBin.{}.count() -- map size
$.listBin.[=4].count() > 0 -- count of elements equal to 4

exists() — existence check

$.binA.exists() and $.binB.exists()
$.mapBin.address.exists()

Control structures

Conditional: when

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

default clause is mandatory.

Variable binding: let ... then

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

Variables reference earlier variables with ${name}.

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.

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");
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()

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:

session.query(users)
.bin("ratio").selectFrom("$.a / $.b", opt -> opt.ignoreEvalFailure())
.execute();

Write expressions with insertFrom, updateFrom, upsertFrom

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

MethodBehavior
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
session.upsert(users.id("u1"))
.bin("total").upsertFrom("$.price * $.quantity")
.bin("discount").insertFrom("$.coupon * 0.1")
.execute();
await (
session.upsert(users.id("u1"))
.bin("total").upsert_from("$.price * $.quantity")
.bin("discount").insert_from("$.coupon * 0.1")
.execute()
)

Options for write expressions:

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();

Where clause integration patterns

On dataset queries

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

On single-key / batch operations

session.update(users.ids("u1", "u2"))
.bin("bonus").add(100)
.where("$.department == 'engineering'")
.execute();
await (
session.update(users.ids("u1", "u2"))
.bin("bonus").add(100)
.where("$.department == 'engineering'")
.execute()
)

On mixed batch chains (per-operation and default)

session
.update(users.id("u1")).bin("bonus").add(100)
.where("$.department == 'engineering'")
.update(users.id("u2")).bin("bonus").add(50)
.defaultWhere("$.active == true")
.execute();
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()
)

With PreparedAel for reuse

PreparedAel activeInDept = new PreparedAel("$.active == true and $.department == $1");
session.query(users)
.where(activeInDept, "engineering")
.execute();
session.query(users)
.where(activeInDept, "marketing")
.execute();
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()
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?