Overview
For the complete documentation index see: llms.txt
All documentation pages available in markdown.
Learn how to query Aerospike using the Developer SDK’s Aerospike Expression Language (AEL), a readable, intuitive syntax for filtering records.
What is AEL?
AEL lets you write filter expressions using natural, readable syntax instead of complex builder patterns:
import com.aerospike.client.sdk.Record;import com.aerospike.client.sdk.RecordStream;
// AEL: Clean, readable syntaxRecordStream stream = session.query(users) .where("$.status == 'active' and $.age >= 21") .execute();stream.forEach(result -> { Record row = result.recordOrThrow(); // Process row});stream.close();
// Compare to traditional expression builders (more verbose)// Expression filter = Exp.and(// Exp.eq(Exp.stringBin("status"), Exp.val("active")),// Exp.ge(Exp.intBin("age"), Exp.val(21))// );📖 API reference:
Session.query(DataSet)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.execute()|RecordStream.forEach(...)|RecordStream.close()|RecordResult.recordOrThrow()
# AEL: Clean, readable syntaxstream = await session.query(users).where( "$.status == 'active' and $.age >= 21").execute()async for row in stream: record = row.record_or_raise() pass # Process recordstream.close()
# Compare to traditional expression builders (more verbose)# filter = Exp.and_([# Exp.eq(Exp.string_bin("status"), val("active")),# Exp.ge(Exp.int_bin("age"), val(21)),# ])📖 API reference:
Session.query()|QueryBuilder.where()|RecordResult.record_or_raise()|RecordStream.close()|QueryBuilder.execute()
AEL parses your string into an optimized filter expression at query time, giving you the best of both worlds: developer ergonomics and Aerospike performance.
Basic syntax
AEL expressions are infix expressions:
left_operand operator right_operand// String comparisonsession.query(users).where("$.status == 'active'").execute();
// Numeric comparisonsession.query(users).where("$.age > 21").execute();
// Boolean checksession.query(users).where("$.verified == true").execute();📖 API reference:
Session.query(DataSet)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.execute()
# String comparisonawait session.query(users).where("$.status == 'active'").execute()
# Numeric comparisonawait session.query(users).where("$.age > 21").execute()
# Boolean checkawait session.query(users).where("$.verified == true").execute()📖 API reference:
Session.query()|QueryBuilder.where()|QueryBuilder.execute()
Comparison operators
| Operator | Meaning | Example |
|---|---|---|
== | Equal to | "$.status == 'active'" |
!= | Not equal to | "$.status != 'deleted'" |
> | Greater than | "$.age > 21" |
>= | Greater than or equal | "$.age >= 18" |
< | Less than | "$.score < 100" |
<= | Less than or equal | "$.score <= 50" |
String values
You can use either single or double quotes:
// Single or double quotes are both validsession.query(users).where("$.country == 'USA'").execute();session.query(users).where("$.country == \"USA\"").execute();session.query(users).where("$.email != 'test@example.com'").execute();
// Strings with spacessession.query(users).where("$.city == 'New York'").execute();📖 API reference:
Session.query(DataSet)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.execute()
# Single or double quotes are both validawait session.query(users).where("$.country == 'USA'").execute()await session.query(users).where("$.country == \"USA\"").execute()await session.query(users).where("$.email != 'test@example.com'").execute()
# Strings with spacesawait session.query(users).where("$.city == 'New York'").execute()📖 API reference:
Session.query()|QueryBuilder.where()|QueryBuilder.execute()
Numeric values
Use integers or decimals directly:
// Integerssession.query(orders).where("$.quantity > 10").execute();
// Decimalssession.query(products).where("$.price <= 99.99").execute();
// Negative numberssession.query(accounts).where("$.balance >= -100").execute();📖 API reference:
Session.query(DataSet)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.execute()
# Integersawait session.query(orders).where("$.quantity > 10").execute()
# Decimalsawait session.query(products).where("$.price <= 99.99").execute()
# Negative numbersawait session.query(accounts).where("$.balance >= -100").execute()📖 API reference:
Session.query()|QueryBuilder.where()|QueryBuilder.execute()
Boolean values
Use true or false (lowercase):
session.query(users).where("$.verified == true").execute();session.query(users).where("$.deleted == false").execute();📖 API reference:
Session.query(DataSet)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.execute()
await session.query(users).where("$.verified == true").execute()await session.query(users).where("$.deleted == false").execute()📖 API reference:
Session.query()|QueryBuilder.where()|QueryBuilder.execute()
Logical operators
Combine conditions using and, or, not, and exclusive(...):
| Operator | Meaning | Example |
|---|---|---|
and | Both conditions must be true | "$.age > 21 and $.status == 'active'" |
or | Either condition can be true | "$.role == 'admin' or $.role == 'moderator'" |
not | Negates a condition | "not ($.status == 'deleted')" |
exclusive(...) | Exactly one argument expression evaluates to true | "exclusive($.tier == 1, $.tier == 2, $.tier == 3)" |
Combining conditions
// AND: Both conditions requiredsession.query(users) .where("$.age >= 18 and $.status == 'active'") .execute();
// OR: Either condition matchessession.query(users) .where("$.role == 'admin' or $.role == 'superuser'") .execute();
// NOT: Exclude matchessession.query(users) .where("not ($.status == 'banned')") .execute();
// EXCLUSIVE: exactly one condition can matchsession.query(users) .where("exclusive($.tier == 1, $.tier == 2, $.tier == 3)") .execute();📖 API reference:
Session.query(DataSet)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.execute()
# AND: Both conditions required( session.query(users) .where("$.age >= 18 and $.status == 'active'") .execute())
# OR: Either condition matches( session.query(users) .where("$.role == 'admin' or $.role == 'superuser'") .execute())
# NOT: Exclude matches( session.query(users) .where("not ($.status == 'banned')") .execute())
# EXCLUSIVE: exactly one condition can match( session.query(users) .where("exclusive($.tier == 1, $.tier == 2, $.tier == 3)") .execute())📖 API reference:
Session.query()|QueryBuilder.where()|QueryBuilder.execute()
Complex expressions with parentheses
Use parentheses to control evaluation order:
// Without parentheses: AND has higher precedence than OR// "a or b and c" evaluates as "a or (b and c)"
// With parentheses: Explicit groupingsession.query(users) .where("($.role == 'admin' or $.role == 'moderator') and $.verified == true") .execute();
// Multiple levelssession.query(orders) .where("($.status == 'shipped' or $.status == 'delivered') and ($.total > 100 or $.priority == 'high')") .execute();📖 API reference:
Session.query(DataSet)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.execute()
# Without parentheses: AND has higher precedence than OR# "a or b and c" evaluates as "a or (b and c)"
# With parentheses: Explicit grouping( session.query(users) .where("($.role == 'admin' or $.role == 'moderator') and $.verified == true") .execute())
# Multiple levels( session.query(orders) .where("($.status == 'shipped' or $.status == 'delivered') and ($.total > 100 or $.priority == 'high')") .execute())📖 API reference:
Session.query()|QueryBuilder.where()|QueryBuilder.execute()
Collection operators
Work with lists and maps using special operators:
| Operator | Use Case | Example |
|---|---|---|
in | Membership test | "'sale' in $.tags" |
count() | Collection size/cardinality | "$.items.count() > 5" |
List membership
Check whether a value is present in a list:
// Find users with the 'premium' tagsession.query(users) .where("'premium' in $.tags") .execute();
// Find orders containing a specific productsession.query(orders) .where("'SKU-12345' in $.product_ids") .execute();📖 API reference:
Session.query(DataSet)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.execute()
# Find users with the 'premium' tag( session.query(users) .where("'premium' in $.tags") .execute())
# Find orders containing a specific product( session.query(orders) .where("'SKU-12345' in $.product_ids") .execute())📖 API reference:
Session.query()|QueryBuilder.where()|QueryBuilder.execute()
Collection size
Filter by the size of a list or map:
// Users with more than 5 orderssession.query(users) .where("$.order_history.count() > 5") .execute();
// Products with at least 3 imagessession.query(products) .where("$.images.count() >= 3") .execute();📖 API reference:
Session.query(DataSet)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.execute()
# Users with more than 5 orders( session.query(users) .where("$.order_history.count() > 5") .execute())
# Products with at least 3 images( session.query(products) .where("$.images.count() >= 3") .execute())📖 API reference:
Session.query()|QueryBuilder.where()|QueryBuilder.execute()
Working with nested data
Access nested map values using dot notation:
// Given record:// { "address": { "city": "San Francisco", "zip": "94102" } }
// Query nested fieldsession.query(users) .where("$.address.city == 'San Francisco'") .execute();
// Multiple nesting levels// { "profile": { "settings": { "theme": "dark" } } }session.query(users) .where("$.profile.settings.theme == 'dark'") .execute();📖 API reference:
Session.query(DataSet)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.execute()
# Given record:# { "address": { "city": "San Francisco", "zip": "94102" } }
# Query nested field( session.query(users) .where("$.address.city == 'San Francisco'") .execute())
# Multiple nesting levels# { "profile": { "settings": { "theme": "dark" } } }( session.query(users) .where("$.profile.settings.theme == 'dark'") .execute())📖 API reference:
Session.query()|QueryBuilder.where()|QueryBuilder.execute()
Combining nested access with other operators
// Nested field with comparisonsession.query(users) .where("$.profile.age >= 21 and $.address.country == 'USA'") .execute();
// Nested list containssession.query(users) .where("'email' in $.preferences.notifications") .execute();📖 API reference:
Session.query(DataSet)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.execute()
# Nested field with comparison( session.query(users) .where("$.profile.age >= 21 and $.address.country == 'USA'") .execute())
# Nested list contains( session.query(users) .where("'email' in $.preferences.notifications") .execute())📖 API reference:
Session.query()|QueryBuilder.where()|QueryBuilder.execute()
Existence checks
Use exists() to test whether a bin/path is present:
// Find records where email is not setsession.query(users) .where("not($.email.exists())") .execute();
// Find records where email existssession.query(users) .where("$.email.exists()") .execute();📖 API reference:
Session.query(DataSet)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.execute()
# Find records where email is not set( session.query(users) .where("not($.email.exists())") .execute())
# Find records where email exists( session.query(users) .where("$.email.exists()") .execute())📖 API reference:
Session.query()|QueryBuilder.where()|QueryBuilder.execute()
AEL vs filter expressions
The Developer SDK also supports raw filter expressions for advanced use cases:
| Approach | Best For | Example |
|---|---|---|
| AEL | Most queries, readable code | "$.age > 21 and $.status == 'active'" |
| Filter Expressions | Complex logic, programmatic building | Exp.and(Exp.gt(...), Exp.eq(...)) |
When to use filter expressions
Use raw expressions when you need:
- Programmatic expression composition/reuse in application code
- Compatibility with existing expression code
import com.aerospike.client.sdk.exp.Exp;
// AEL (recommended for most cases)session.query(users).where("$.age > 21").execute();
// Raw Exp API (for advanced cases)Exp exp = Exp.gt(Exp.intBin("age"), Exp.val(21));session.query(users).where(exp).execute();📖 API reference:
Session.query(DataSet)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.execute()
from aerospike_sdk import Exp, val
# AEL (recommended for most cases)await session.query(users).where("$.age > 21").execute()
# Raw expression (for advanced cases)filter = Exp.gt(Exp.int_bin("age"), val(21))await session.query(users).where(filter).execute()📖 API reference:
Session.query()|QueryBuilder.where()|QueryBuilder.execute()
AEL beyond full-set queries
AEL is not limited to session.query(dataSet) over an entire set. You can apply .where(...) anywhere the fluent API accepts expressions, including batch key reads and conditional writes.
// Batch key read with AEL filtersession.query(users.ids(1, 2, 3, 4)) .where("$.score > 90") .execute();
// Conditional single-record updatesession.upsert(users.id("task-1")) .bin("status").setTo("COMPLETED") .where("$.status == 'PENDING' and $.terminated == false") .execute();📖 API reference:
DataSet.ids(...)|DataSet.id(...)|Session.upsert(DataSet)|Session.upsert(Key)|Session.query(List)|ChainableOperationBuilder.bin(...)|ChainableQueryBuilder.bin(...)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.execute()
# Batch key read with AEL filterawait ( session.query(users.ids(1, 2, 3, 4)) .where("$.score > 90") .execute())
# Conditional single-record updateawait ( session.upsert(users.id("task-1")) .bin("status").set_to("COMPLETED") .where("$.status == 'PENDING' and $.terminated == false") .execute())📖 API reference:
DataSet.id()|DataSet.ids()|Session.query()|Session.upsert()|QueryBuilder.where()|QueryBuilder.bin()|WriteSegmentBuilder.set_to()|WriteSegmentBuilder.bin()|QueryBuilder.execute()|WriteSegmentBuilder.execute()
Performance considerations
Use secondary indexes
AEL expressions are parsed once and compiled before execution, then evaluated on candidate records. For large datasets, create secondary indexes on frequently-filtered bins.
Index choice is transparent to your code: keep the same AEL query, and the SDK/query planner can use an index when available (or scan when none exists).
// Without index: Scans ALL records, applies filter// ⚠️ Slow for large setssession.query(users) .where("$.status == 'active'") // Filter applied during scan .execute();
// With index on 'status': Planner can use the index transparently// ✅ Fast for selective queriessession.query(users) .where("$.status == 'active'") // Index lookup + filter .execute();📖 API reference:
Session.query(DataSet)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.execute()
# Without index: Scans ALL records, applies filter# ⚠️ Slow for large sets( session.query(users) .where("$.status == 'active'") .execute())
# With index on 'status': Planner can use the index transparently# ✅ Fast for selective queries( session.query(users) .where("$.status == 'active'") .execute())📖 API reference:
Session.query()|QueryBuilder.where()|QueryBuilder.execute()
Limit results
Always limit results when you don’t need everything:
import com.aerospike.client.sdk.Record;import com.aerospike.client.sdk.RecordStream;
// Get only the first 100 matching records (server-side limit)RecordStream stream = session.query(users) .where("$.status == 'active'") .limit(100) .execute();stream.forEach(result -> { Record row = result.recordOrThrow(); // Process row});stream.close();📖 API reference:
Session.query(DataSet)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.limit(...)|ChainableQueryBuilder.execute()|RecordStream.forEach(...)|RecordStream.close()|RecordResult.recordOrThrow()
# Get only the first 100 matching records (server-side limit)stream = await session.query(users).where("$.status == 'active'").limit(100).execute()async for row in stream: record = row.record_or_raise() pass # Process recordstream.close()📖 API reference:
Session.query()|QueryBuilder.where()|QueryBuilder.limit()|RecordResult.record_or_raise()|RecordStream.close()|QueryBuilder.execute()
Keep expressions readable
Complex expressions are valid, but readability and maintainability matter:
Hard to maintain:($.a and $.b) or ($.c and $.d) or ($.e and $.f) and not ($.g or $.h)
Prefer clear grouping and intent:($.status == 'active' and $.age > 21) or $.priority == 'high'The primary performance factor is index selectivity and record volume, not whether you wrote the filter as AEL text or an expression builder tree.
Use projections
Request only the bins you need:
import com.aerospike.client.sdk.Record;import com.aerospike.client.sdk.RecordStream;
// Only retrieve 'name' and 'email' bins (projection)RecordStream stream = session.query(users) .where("$.status == 'active'") .readingOnlyBins("name", "email") .execute();stream.forEach(result -> { Record row = result.recordOrThrow(); // Process row});stream.close();📖 API reference:
Session.query(DataSet)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.readingOnlyBins(...)|ChainableQueryBuilder.execute()|RecordStream.forEach(...)|RecordStream.close()|RecordResult.recordOrThrow()
# Only retrieve 'name' and 'email' bins (projection)stream = await session.query(users).where("$.status == 'active'").bins( ["name", "email"]).execute()async for row in stream: record = row.record_or_raise() pass # record.bins.get("name"), etc.stream.close()📖 API reference:
Session.query()|QueryBuilder.where()|QueryBuilder.bins()|RecordResult.record_or_raise()|RecordStream.close()|QueryBuilder.execute()
Complete example
import com.aerospike.client.sdk.Cluster;import com.aerospike.client.sdk.ClusterDefinition;import com.aerospike.client.sdk.DataSet;import com.aerospike.client.sdk.Record;import com.aerospike.client.sdk.RecordStream;import com.aerospike.client.sdk.Session;import com.aerospike.client.sdk.policy.Behavior;import java.util.concurrent.atomic.AtomicInteger;
public class AELQueryExample { public static void main(String[] args) { try (Cluster cluster = new ClusterDefinition("localhost", 3000).connect()) { Session session = cluster.createSession(Behavior.DEFAULT); DataSet products = DataSet.of("test", "products"); String p1 = "ael-product-1"; String p2 = "ael-product-2"; String p3 = "ael-product-3"; String p4 = "ael-product-4";
// Seed sample data so the example is repeatable. session.delete(products.ids(p1, p2, p3, p4)).execute().close(); session.insert(products) .bins("name", "category", "price", "rating", "tags", "metadata") .id(p1).values("TV", "electronics", 499.99, 4.6, java.util.List.of("sale"), java.util.Map.of("featured", true)) .id(p2).values("Headphones", "electronics", 39.99, 4.2, java.util.List.of("clearance"), java.util.Map.of("featured", false)) .id(p3).values("Notebook", "office", 12.50, 3.9, java.util.List.of("stationery"), java.util.Map.of("featured", false)) .id(p4).values("Monitor", "electronics", 199.99, 4.8, java.util.List.of("sale", "new"), java.util.Map.of("featured", true)) .execute();
// Simple equality AtomicInteger electronicsCount = new AtomicInteger(0); RecordStream s1 = session.query(products) .where("$.category == 'electronics'") .execute(); s1.forEach(result -> { Record row = result.recordOrThrow(); electronicsCount.incrementAndGet(); }); s1.close();
// Range query AtomicInteger affordableCount = new AtomicInteger(0); RecordStream s2 = session.query(products) .where("$.price >= 10 and $.price <= 50") .execute(); s2.forEach(result -> { Record row = result.recordOrThrow(); affordableCount.incrementAndGet(); }); s2.close();
// Complex filter with nested access AtomicInteger featuredCount = new AtomicInteger(0); RecordStream s3 = session.query(products) .where("$.metadata.featured == true and $.rating >= 4.0") .readingOnlyBins("name", "price", "rating") .limit(10) .execute(); s3.forEach(result -> { Record row = result.recordOrThrow(); featuredCount.incrementAndGet(); }); s3.close();
// List contains AtomicInteger taggedCount = new AtomicInteger(0); RecordStream s4 = session.query(products) .where("'sale' in $.tags or 'clearance' in $.tags") .execute(); s4.forEach(result -> { Record row = result.recordOrThrow(); taggedCount.incrementAndGet(); }); s4.close();
System.out.println("Found " + electronicsCount.get() + " electronics"); System.out.println("Found " + affordableCount.get() + " affordable products"); System.out.println("Found " + featuredCount.get() + " featured products"); System.out.println("Found " + taggedCount.get() + " sale/clearance products");
// Cleanup session.delete(products.ids(p1, p2, p3, p4)).execute().close(); } }}📖 API reference:
ClusterDefinition(String,int)|ClusterDefinition.connect()|Cluster.createSession(Behavior)|Cluster.close()|DataSet.of(...)|DataSet.ids(...)|Session.insert(DataSet)|Session.delete(List)|Session.delete(Key)|Session.query(DataSet)|OperationObjectBuilder.bins(...)|IdValuesBuilder.id(...)|IdValuesRowBuilder.values(...)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.readingOnlyBins(...)|ChainableQueryBuilder.limit(...)|ChainableQueryBuilder.execute()|ChainableNoBinsBuilder.execute()|RecordStream.forEach(...)|RecordStream.close()|RecordResult.recordOrThrow()
import asyncio
from aerospike_sdk import Behavior, ClusterDefinition, DataSet
async def main(): cluster_def = ClusterDefinition("localhost", 3000) # If your Dockerized server advertises an internal bridge IP, # map it back to localhost for host-based examples. cluster_def = cluster_def.with_ip_map({"172.17.0.2": "127.0.0.1"})
async with await cluster_def.connect() as cluster: session = cluster.create_session(Behavior.DEFAULT) products = DataSet.of("test", "products") p1 = products.id("ael-product-1") p2 = products.id("ael-product-2") p3 = products.id("ael-product-3") p4 = products.id("ael-product-4")
# Seed sample data so the example is repeatable. stream = await session.batch().delete(p1).delete(p2).delete(p3).delete(p4).execute() stream.close() await ( session.batch() .insert(p1).put({ "name": "TV", "category": "electronics", "price": 499.99, "rating": 4.6, "tags": ["sale"], "metadata": {"featured": True}, }) .insert(p2).put({ "name": "Headphones", "category": "electronics", "price": 39.99, "rating": 4.2, "tags": ["clearance"], "metadata": {"featured": False}, }) .insert(p3).put({ "name": "Notebook", "category": "office", "price": 12.50, "rating": 3.9, "tags": ["stationery"], "metadata": {"featured": False}, }) .insert(p4).put({ "name": "Monitor", "category": "electronics", "price": 199.99, "rating": 4.8, "tags": ["sale", "new"], "metadata": {"featured": True}, }) .execute() )
# Simple equality electronics_count = 0 stream = await session.query(products).where("$.category == 'electronics'").execute() async for row in stream: row.record_or_raise() electronics_count += 1 stream.close()
# Range query # Float bins need float literals in AEL — "$.price >= 10" against a # float bin returns no rows because the comparison types don't align. affordable_count = 0 stream = await session.query(products).where("$.price >= 10.0 and $.price <= 50.0").execute() async for row in stream: row.record_or_raise() affordable_count += 1 stream.close()
# Complex filter with nested access featured_count = 0 stream = await session.query(products).where( "$.metadata.featured == true and $.rating >= 4.0" ).bins(["name", "price", "rating"]).limit(10).execute() async for row in stream: row.record_or_raise() featured_count += 1 stream.close()
# List contains tagged_count = 0 stream = await session.query(products).where( "'sale' in $.tags or 'clearance' in $.tags" ).execute() async for row in stream: row.record_or_raise() tagged_count += 1 stream.close()
print(f"Found {electronics_count} electronics") print(f"Found {affordable_count} affordable products") print(f"Found {featured_count} featured products") print(f"Found {tagged_count} sale/clearance products")
# Cleanup stream = await session.batch().delete(p1).delete(p2).delete(p3).delete(p4).execute() stream.close()
asyncio.run(main())📖 API reference:
DataSet.of()|DataSet.id()|Behavior.DEFAULT|Session.query()|Session.batch()|QueryBuilder.where()|QueryBuilder.bins()|QueryBuilder.limit()|BatchOperationBuilder.insert()|BatchOperationBuilder.delete()|BatchKeyOperationBuilder.put()|RecordResult.record_or_raise()|RecordStream.close()|BatchOperationBuilder.execute()|QueryBuilder.execute()
AEL quick reference
| Expression | Meaning |
|---|---|
"$.age == 25" | Age equals 25 |
"$.price > 100" | Price greater than 100 |
"$.status != 'deleted'" | Status is not “deleted” |
"$.age >= 18 and $.age <= 65" | Age between 18 and 65 |
"$.role == 'admin' or $.role == 'mod'" | Role is admin or mod |
"not($.banned == true)" | Not banned |
"'vip' in $.tags" | Tags list contains “vip” |
"$.items.count() > 0" | Items list is not empty |
"$.address.city == 'NYC'" | Nested city equals NYC |
"$.email.exists()" | Email is set |
"let (total = $.price * $.qty) then (${total} > 1000)" | Variable binding with let |
"when ($.tier == 1 => 'gold', default => 'other')" | Conditional with when |
More AEL capabilities
This page focuses on common query filters, but AEL also supports more expressive constructs from the Java AEL reference and post-preview language design notes.
Variable binding with let (...) then (...)
Use let to define reusable intermediate values so long expressions stay readable.
// Reuse intermediate calculations in a single predicate.session.query(orders) .where("let (subtotal = $.price * $.qty, tax = ${subtotal} * 0.1) then (${subtotal} + ${tax} > 1000)") .execute();📖 API reference:
Session.query(DataSet)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.execute()
# Reuse intermediate calculations in a single predicate.await ( session.query(orders) .where("let (subtotal = $.price * $.qty, tax = ${subtotal} * 0.1) then (${subtotal} + ${tax} > 1000)") .execute())📖 API reference:
Session.query()|QueryBuilder.where()|QueryBuilder.execute()
Conditional logic with when (..., default => ...)
Use when for inline branching inside an expression.
// Compare a bin against a conditionally-computed value.session.query(users) .where("$.badge == (when ($.tier == 1 => 'gold', $.tier == 2 => 'silver', default => 'bronze'))") .execute();📖 API reference:
Session.query(DataSet)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.execute()
# Compare a bin against a conditionally-computed value.await ( session.query(users) .where("$.badge == (when ($.tier == 1 => 'gold', $.tier == 2 => 'silver', default => 'bronze'))") .execute())📖 API reference:
Session.query()|QueryBuilder.where()|QueryBuilder.execute()
String and map literals
String literals use single quotes. In map literals, keys and string values should also be quoted.
session.query(users) .where("$.name == 'Tim' and $.labels == {'role': 'user', 'tier': 2}") .execute();📖 API reference:
Session.query(DataSet)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.execute()
await ( session.query(users) .where("$.name == 'Tim' and $.labels == {'role': 'user', 'tier': 2}") .execute())📖 API reference:
Session.query()|QueryBuilder.where()|QueryBuilder.execute()
Arithmetic and bitwise operators
AEL supports arithmetic expressions (+, -, *, /) and integer bitwise operators (&, |, ^, <<, >>, >>>) in predicates.
// Arithmetic comparison.session.query(items) .where("($.unitPrice * $.quantity) - $.discount > 500") .execute();
// Bitmask check: confirm enabled flag bit is set.session.query(items) .where("(($.flags >> 2) & 1) == 1") .execute();📖 API reference:
Session.query(DataSet)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.execute()
# Arithmetic comparison.await ( session.query(items) .where("($.unitPrice * $.quantity) - $.discount > 500") .execute())
# Bitmask check: confirm enabled flag bit is set.await ( session.query(items) .where("(($.flags >> 2) & 1) == 1") .execute())📖 API reference:
Session.query()|QueryBuilder.where()|QueryBuilder.execute()
Record metadata functions
You can filter by record metadata such as remaining TTL, on-device size, and tombstone status.
session.query(events) .where("$.ttl() < 3600 and $.recordSize() > 1024") .execute();
session.query(events) .where("$.isTombstone() == true") .execute();📖 API reference:
Session.query(DataSet)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.execute()
await ( session.query(events) .where("$.ttl() < 3600 and $.recordSize() > 1024") .execute())
await ( session.query(events) .where("$.isTombstone() == true") .execute())📖 API reference:
Session.query()|QueryBuilder.where()|QueryBuilder.execute()
Richer CDT path reads and typed returns
Beyond simple dot navigation, AEL design work includes deeper path forms and typed getters, so complex nested list/map content can be filtered more precisely.
// Example of typed path access used inside a predicate.session.query(profiles) .where("$.prefs.theme.get(type: STRING) == 'dark' and $.prefs.flags.get(type: INT) > 0") .execute();📖 API reference:
Session.query(DataSet)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.execute()
# Example of typed path access used inside a predicate.await ( session.query(profiles) .where("$.prefs.theme.get(type: STRING) == 'dark' and $.prefs.flags.get(type: INT) > 0") .execute())📖 API reference:
Session.query()|QueryBuilder.where()|QueryBuilder.execute()
Post-preview roadmap examples
The post-preview AEL roadmap adds additional language areas. The examples below are syntax previews from the reference docs, not guaranteed to be available in every SDK release today.
// Regex filtering (ICU syntax).session.query(users).where("$.email =~ /@example\\.com$/i").execute();
// Deeper CDT path filtering/mutation forms.session.query(catalog).where("$.listbin.[=a:z].remove(return: NONE)").execute();session.query(catalog).where("$.mapbin.{a,c}.get(return: VALUE).[].count() == 2").execute();
// Method-style function families (string / blob-bit / HLL / geo).session.query(metrics).where("$.name.uppercase() == 'CPU'").execute();session.query(metrics).where("$.blob.bitGet(offset: 0, size: 8) > 0").execute();session.query(metrics).where("$.hll.hllCount() > 100").execute();session.query(metrics).where("geoCompare($.region, geoJson('{\"type\":\"Point\",\"coordinates\":[-122.4,37.8]}'))").execute();📖 API reference:
Session.query(DataSet)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.execute()
# Regex filtering (ICU syntax).await session.query(users).where("$.email =~ /@example\\.com$/i").execute()
# Deeper CDT path filtering/mutation forms.await session.query(catalog).where("$.listbin.[=a:z].remove(return: NONE)").execute()await session.query(catalog).where("$.mapbin.{a,c}.get(return: VALUE).[].count() == 2").execute()
# Method-style function families (string / blob-bit / HLL / geo).await session.query(metrics).where("$.name.uppercase() == 'CPU'").execute()await session.query(metrics).where("$.blob.bitGet(offset: 0, size: 8) > 0").execute()await session.query(metrics).where("$.hll.hllCount() > 100").execute()await session.query(metrics).where("geoCompare($.region, geoJson('{\"type\":\"Point\",\"coordinates\":[-122.4,37.8]}'))").execute()📖 API reference:
Session.query()|QueryBuilder.where()|QueryBuilder.execute()
For full details, see:
Next steps
Query Records
Explore pagination, sorting, and more query options.
Data Model
Understand the data structures AEL filters operate on.
Error Handling
Handle query errors gracefully.
Behaviors
Configure query timeouts and retries.