Overview
For the complete documentation index see: llms.txt
All documentation pages available in markdown.
Concept overview: write readable Aerospike Expression Language (AEL) filter strings for queries, batch reads, and conditional writes in the Java and Python Developer SDKs.
Applies to
- Aerospike Developer SDKs (Java 21+ and Python 3.10+)
- Aerospike Database 8.2.0.0 or later (AEL filter APIs with
.where(...); AEL is compiled and parsed on the server, so there is no client-side AEL parser)
Audience
Application developers new to AEL or migrating from expression builders (Beginner to Intermediate).
Prerequisites
- A connected
session - Basic familiarity with SDK data model
Outcome
You can read and write common AEL filter expressions (comparisons, logic, collections, nested paths) and know when to use indexes and projections.
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 lets you write a query’s filter as a string. On Database 8.2.0 and later, the query optimizer reads that AEL and uses it to select a readable secondary index, then the server evaluates the filter on the records that index returns.
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 requiredstream = await ( session.query(users) .where("$.age >= 18 and $.status == 'active'") .execute())stream.close()
# OR: Either condition matchesstream = await ( session.query(users) .where("$.role == 'admin' or $.role == 'superuser'") .execute())stream.close()
# NOT: Exclude matchesstream = await ( session.query(users) .where("not($.status == 'banned')") .execute())stream.close()
# EXCLUSIVE: exactly one condition can matchstream = await ( session.query(users) .where("exclusive($.tier == 1, $.tier == 2, $.tier == 3)") .execute())stream.close()📖 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.0 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 groupingstream = await ( session.query(users) .where("($.role == 'admin' or $.role == 'moderator') and $.verified == true") .execute())stream.close()
# Multiple levelsstream = await ( session.query(orders) .where("($.status == 'shipped' or $.status == 'delivered') and ($.total > 100.0 or $.priority == 'high')") .execute())stream.close()📖 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. Pin the container type with :LIST or :MAP | "$.items:LIST.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' tagstream = await ( session.query(users) .where("'premium' in $.tags") .execute())stream.close()
# Find orders containing a specific productstream = await ( session.query(orders) .where("'SKU-12345' in $.product_ids") .execute())stream.close()📖 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:LIST.count() > 5") .execute();
// Products with at least 3 imagessession.query(products) .where("$.images:LIST.count() >= 3") .execute();📖 API reference:
Session.query(DataSet)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.execute()
# Users with more than 5 ordersstream = await ( session.query(users) .where("$.order_history:LIST.count() > 5") .execute())stream.close()
# Products with at least 3 imagesstream = await ( session.query(products) .where("$.images:LIST.count() >= 3") .execute())stream.close()📖 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 fieldstream = await ( session.query(users) .where("$.address.city == 'San Francisco'") .execute())stream.close()
# Multiple nesting levels# { "profile": { "settings": { "theme": "dark" } } }stream = await ( session.query(users) .where("$.profile.settings.theme == 'dark'") .execute())stream.close()📖 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 comparisonstream = await ( session.query(users) .where("$.profile.age >= 21 and $.address.country == 'USA'") .execute())stream.close()
# Nested list containsstream = await ( session.query(users) .where("'email' in $.preferences.notifications") .execute())stream.close()📖 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 setstream = await ( session.query(users) .where("not($.email.exists())") .execute())stream.close()
# Find records where email existsstream = await ( session.query(users) .where("$.email.exists()") .execute())stream.close()📖 API reference:
Session.query()|QueryBuilder.where()|QueryBuilder.execute()
AEL vs expressions
The Developer SDK also supports raw expressions for advanced use cases. Canonical AEL grammar is documented in AEL reference.
| Approach | Best For | Example |
|---|---|---|
| AEL | Most queries, readable code | "$.age > 21 and $.status == 'active'" |
| 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("user-1", "user-2", "user-3", "user-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("user-1", "user-2", "user-3", "user-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 evaluated on candidate records. For large datasets, create secondary indexes on frequently-filtered bins.
The query optimizer reads your AEL and selects a readable secondary index (SI) that can serve it.
import com.aerospike.client.sdk.Record;import com.aerospike.client.sdk.RecordStream;
// With an index on 'status': the planner uses the index for the lookup,// then evaluates the remaining condition as a filter.try (RecordStream stream = session.query(users) .where("$.status == 'active' and $.tier == 'gold'") .execute()) { stream.forEach(result -> { Record row = result.recordOrThrow(); // Process row });}📖 API reference:
Session.query(DataSet)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.execute()|RecordStream.forEach(...)|RecordStream.close()|RecordResult.recordOrThrow()
# With an index on 'status': the planner uses the index for the lookup,# then evaluates the remaining condition as a filter.stream = await ( session.query(users) .where("$.status == 'active' and $.tier == 'gold'") .execute())async for row in stream: record = row.record_or_raise() pass # Process recordstream.close()📖 API reference:
Session.query()|QueryBuilder.where()|RecordResult.record_or_raise()|RecordStream.close()|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"), record.bins.get("email")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.0 and $.price <= 50.0") .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, DataSetfrom aerospike_sdk.policy import Settings
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: # This example demonstrates AEL syntax rather than index selection, and # creates no secondary indexes. Allow the queries to run against the # primary index so they return rows instead of failing with # AS_ERR_SINDEX_NOT_FOUND (201). session = cluster.create_session( Behavior.DEFAULT.derive_with_changes( "ael-example", reads_query=Settings(allow_scans_with_where=True), ) ) 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. seed = [ (p1, { "name": "TV", "category": "electronics", "price": 499.99, "rating": 4.6, "tags": ["sale"], "metadata": {"featured": True}, }), (p2, { "name": "Headphones", "category": "electronics", "price": 39.99, "rating": 4.2, "tags": ["clearance"], "metadata": {"featured": False}, }), (p3, { "name": "Notebook", "category": "office", "price": 12.50, "rating": 3.9, "tags": ["stationery"], "metadata": {"featured": False}, }), (p4, { "name": "Monitor", "category": "electronics", "price": 199.99, "rating": 4.8, "tags": ["sale", "new"], "metadata": {"featured": True}, }), ] for key, bins in seed: await session.upsert(key).put(bins).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 for key in (p1, p2, p3, p4): await session.delete(key).execute()
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.0" | 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:LIST.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.0)" | Variable binding with let |
"when ($.tier == 1 => 'gold', default => 'other')" | Conditional with when |
Operation expressions (read and write)
Requires Aerospike Database 8.2.0.0 or later.
Beyond filters, pass AEL text for read projection (.bin("out").selectFrom("...") / .select_from(...)) and write-side compute (upsertFrom, insertFrom, updateFrom). Write expressions apply to single-key and batch operate chains, not to query projection, which is read-only. This 8.2.0.0-or-later requirement applies the same way across SDKs. For more information, see:
Advanced AEL syntax
This overview covers common query filters and day-to-day patterns. For filter and operation-expression authoring on single-key, batch, and query commands, see Author AEL expressions. For variable binding (let), conditional when, CDT path patterns, and the full grammar, see AEL reference.
API reference summary
| Method | Description | API reference |
|---|---|---|
.where(...) | Attach a Boolean AEL filter to a query or write chain | Java | Python |
.limit(...) | Cap the number of matching records returned | Java | Python |
.readingOnlyBins(...) / .bins(...) | Project specific bins in query results | Java | Python |
Next steps
Filters, read projection, and write expressions on single-key, batch, and query commands.
Canonical grammar, CDT paths, metadata functions, and let / when syntax.
Parse errors, source coordinates, and filter-decision traces.