---
title: "Expressions Tutorial: Filter Expressions"
description: "Use filter expressions to select records for queries and scans, with a full runnable Java, Python, Go, C#, Node.js, and Rust example."
---

# Filter expressions

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

This page is for developers using Aerospike client libraries. Complete [Setup](https://aerospike.com/docs/develop/tutorials/operations/expressions/setup) first. After reading this page, you can attach filter expressions to all Aerospike operations to select records server-side.

Filter expressions are so named because they are used as a condition to select or discard a record. They always evaluate to a boolean value to indicate whether the record is selected (true) or filtered out (false). A filter expression is sent to the server through the API’s policy object parameter.

-   [Java](#tab-panel-5625)
-   [Python](#tab-panel-5626)
-   [Go](#tab-panel-5627)
-   [C#](#tab-panel-5628)
-   [Node.js](#tab-panel-5629)
-   [Rust](#tab-panel-5630)

```java
Policy policy = new Policy();

policy.filterExp = Exp.build( // sent through filterExp attribute of policy

                        Exp.eq(

                            Exp.intBin("a"),

                            Exp.val(11)));

...

client.query(policy, stmt) // policy is specified as a parameter in API calls
```

```python
query_filter = exp.Eq(

    exp.IntBin("a"),

    11).compile()

policy = {"expressions": query_filter}  # sent through the "expressions" policy field

...

client.query(namespace, set, policy=policy)  # policy is specified as a parameter in API calls
```

```go
policy := as.NewPolicy()

policy.FilterExpression = as.ExpEq( // sent through the FilterExpression field

    as.ExpIntBin("a"),

    as.ExpIntVal(11))

// ...

client.Query(policy, stmt) // policy is specified as a parameter in API calls
```

```csharp
Policy policy = new Policy();

policy.filterExp = Exp.Build( // sent through filterExp attribute of policy

    Exp.EQ(

        Exp.IntBin("a"),

        Exp.Val(11)));

// ...

client.Query(policy, stmt); // policy is specified as a parameter in API calls
```

```js
const query = client.query(namespace, set);

// ...

const filterExpression = exp.eq( // sent through the filterExpression policy field

    exp.binInt('a'),

    exp.int(11));

const policy = new Aerospike.QueryPolicy({ filterExpression });

const stream = query.foreach(policy); // policy is specified as a parameter in API calls
```

```rust
use aerospike::expressions::{eq, int_bin, int_val};

let mut policy = QueryPolicy::default();

policy.base_policy.filter_expression = Some(eq( // sent through the base policy's filter_expression field

    int_bin("a".to_string()),

    int_val(11)));

// ...

client.query(&policy, PartitionFilter::all(), stmt).await // policy is specified as a parameter in API calls
```

With the Developer SDK, the same filter is [AEL](https://aerospike.com/docs/develop/client/sdk/concepts/ael/reference) text passed to `.where()`:

-   [Java](#tab-panel-5631)
-   [Python](#tab-panel-5632)
-   [Go](#tab-panel-5633)
-   [C#](#tab-panel-5634)
-   [Node.js](#tab-panel-5635)
-   [Rust](#tab-panel-5636)

```java
DataSet records = DataSet.of(NAMESPACE, SET_NAME);

RecordStream stream = session.query(records)

    .where("$.a == 11")

    .execute();
```

```python
records = DataSet.of(NAMESPACE, SET_NAME)

stream = await session.query(records) \

    .where("$.a == 11") \

    .execute()
```

::: note
AEL text authoring is available for the Developer SDK (Java and Python) only. Go continues to use the `Exp*` builder shown above.
:::

::: note
AEL text authoring is available for the Developer SDK (Java and Python) only. C# continues to use the `Exp` builder shown above.
:::

::: note
AEL text authoring is available for the Developer SDK (Java and Python) only. Node.js continues to use the `exp` builder shown above.
:::

::: note
AEL text authoring is available for the Developer SDK (Java and Python) only. Rust continues to use the `aerospike::expressions` builder shown above.
:::

## Code example

The following example illustrates the capabilities of filtering on metadata and use of List APIs.

In this illustrative example the filter selects:

-   recently updated (sinceUpdate < 2) records
-   with list bin having values that range from max to min greater than 1000.

1.  Populate the test data with 20 records with an integer bin “bin1” values 1-20 and a list bin having 3 randomly selected numbers in the range 0 to 1999.
2.  Sleep for 2 seconds,
3.  Touch the even numbered records.
4.  Run the query with the filter.

The results should only contain even valued bin1 and bin2 with value range > 1000.

-   [Java](#tab-panel-5637)
-   [Python](#tab-panel-5638)
-   [Go](#tab-panel-5639)
-   [C#](#tab-panel-5640)
-   [Node.js](#tab-panel-5641)
-   [Rust](#tab-panel-5642)

```java
import java.util.ArrayList;

import java.util.Random;

import com.aerospike.client.AerospikeException;

import com.aerospike.client.Bin;

import com.aerospike.client.Key;

import com.aerospike.client.policy.WritePolicy;

import com.aerospike.client.policy.QueryPolicy;

import com.aerospike.client.exp.Exp;

import com.aerospike.client.exp.ListExp;

import com.aerospike.client.Operation;

import com.aerospike.client.task.ExecuteTask;

import com.aerospike.client.query.Statement;

import com.aerospike.client.query.RecordSet;

import com.aerospike.client.Record;

import com.aerospike.client.cdt.ListReturnType;

// start with a clean state

truncateTestData();

// 1. Populate the test data with 20 records with an integer bin "bin1" values 1-20

//    and a list bin having 3 randomly selected numbers in the range 1 to 2000.

Random rand = new Random(1);

final int LIST_RANGE = 2000;

WritePolicy wpolicy = client.copyWritePolicy();

wpolicy.sendKey = true;

for (int i = 1; i <= 20; i++) {

    Key key = new Key(NAMESPACE, SET_NAME, "id-"+i);

    Bin bin1 = new Bin("bin1", i);

    List<Integer> intList = List.of(

        rand.nextInt(LIST_RANGE),

        rand.nextInt(LIST_RANGE),

        rand.nextInt(LIST_RANGE));

    Bin bin2 = new Bin("bin2", intList);

    client.put(wpolicy, key, bin1, bin2);

}

System.out.println("Test data populated.");;

// 2. Sleep for 2 seconds,

Thread.sleep(2000);

// 3. Touch the even numbered records.

Statement stmt = new Statement();

stmt.setNamespace(NAMESPACE);

stmt.setSetName(SET_NAME);

WritePolicy policy = client.copyWritePolicy();

policy.filterExp = Exp.build(

                        Exp.eq(

                            Exp.mod(Exp.intBin("bin1"), Exp.val(2)),

                            Exp.val(0)));

ExecuteTask task = client.execute(policy, stmt, Operation.touch());

task.waitTillComplete(500, 1000);

System.out.println("Touched even numbered records.");;

// 4. Run the query with the filter.

//    records updated in last 2 seconds and whose list value range is more than 1000

stmt = new Statement();

stmt.setNamespace(NAMESPACE);

stmt.setSetName(SET_NAME);

// expression filter is specifed in the operation policy

QueryPolicy policy = client.copyQueryPolicy();

policy.filterExp = Exp.build(

    Exp.and(

        Exp.lt(Exp.sinceUpdate(), Exp.val(2000)),   // updated in last 2s

        Exp.gt(   // range of values in bin2 greater than 1000

            Exp.sub(ListExp.getByRank(ListReturnType.VALUE, Exp.Type.INT, Exp.val(-1), Exp.listBin("bin2")),   // largest

                    ListExp.getByRank(ListReturnType.VALUE, Exp.Type.INT, Exp.val(0), Exp.listBin("bin2"))),   // smallest

            Exp.val(1000))));

RecordSet rs = client.query(policy, stmt);

System.out.println("Results of filter expression query (all even records with bin2 max-min > 1000):");

while (rs.next()) {

    Key key = rs.getKey();

    Record record = rs.getRecord();

    System.out.format("key=%s bins=%s\n", key.userKey, record.bins);

}

rs.close();
```

Output:

```text
Test data populated.

Touched even numbered records.

Results of filter expression query (all even records with bin2 max-min > 1000):

key=id-4 bins={bin1=4, bin2=[1748, 569, 473]}

key=id-10 bins={bin1=10, bin2=[153, 1437, 1302]}

key=id-18 bins={bin1=18, bin2=[333, 1676, 55]}

key=id-16 bins={bin1=16, bin2=[592, 220, 1888]}
```

This assumes the same setup as the Java example above, translated to the classic Python client: `client = aerospike.client(config).connect()`, with `NAMESPACE = "test"` and `SET_NAME = "expressions"`.

```python
import random

import time

from aerospike_helpers import expressions as exp

from aerospike_helpers.expressions.list import ListGetByRank

from aerospike_helpers.operations import operations

# start with a clean state

truncate_test_data()

# 1. Populate the test data with 20 records with an integer bin "bin1" values 1-20

#    and a list bin having 3 randomly selected numbers in the range 1 to 2000.

random.seed(1)

LIST_RANGE = 2000

write_policy = {"key": aerospike.POLICY_KEY_SEND}

for i in range(1, 21):

    key = (NAMESPACE, SET_NAME, "id-" + str(i))

    bin2 = [random.randint(0, LIST_RANGE - 1) for _ in range(3)]

    client.put(key, {"bin1": i, "bin2": bin2}, policy=write_policy)

print("Test data populated.")

# 2. Sleep for 2 seconds,

time.sleep(2)

# 3. Touch the even numbered records.

touch_filter = exp.Eq(exp.Mod(exp.IntBin("bin1"), 2), 0).compile()

touch_policy = {"expressions": touch_filter}

touch_query = client.query(NAMESPACE, SET_NAME)

touch_query.add_ops([operations.touch()])

touch_query.execute_background(policy=touch_policy)

print("Touched even numbered records.")

# 4. Run the query with the filter.

#    records updated in last 2 seconds and whose list value range is more than 1000

query_filter = exp.And(

    exp.LT(exp.SinceUpdateTime(), 2000),   # updated in last 2s

    exp.GT(   # range of values in bin2 greater than 1000

        exp.Sub(

            ListGetByRank(None, aerospike.LIST_RETURN_VALUE, exp.ResultType.INTEGER, -1, exp.ListBin("bin2")),   # largest

            ListGetByRank(None, aerospike.LIST_RETURN_VALUE, exp.ResultType.INTEGER, 0, exp.ListBin("bin2")),    # smallest

        ),

        1000,

    ),

).compile()

query_policy = {"expressions": query_filter}

print("Results of filter expression query (all even records with bin2 max-min > 1000):")

query = client.query(NAMESPACE, SET_NAME)

for key, _, bins in query.results(query_policy):

    print("key={} bins={}".format(key[2], bins))
```

::: note
Output values depend on randomly generated test data. Python’s `random` module and Java’s `java.util.Random` produce different sequences even from the same seed, so the printed keys and `bin2` values will differ from the Java output above, though the shape of the result is the same.
:::

This assumes the same setup as the Java example above, translated to the Go client: `client, err := as.NewClient("localhost", 3000)`, with `NAMESPACE = "test"` and `SET_NAME = "expressions"`.

```go
import (

    "fmt"

    "log"

    "math/rand"

    "time"

    as "github.com/aerospike/aerospike-client-go/v6"

)

// start with a clean state

truncateTestData()

// 1. Populate the test data with 20 records with an integer bin "bin1" values 1-20

//    and a list bin having 3 randomly selected numbers in the range 1 to 2000.

r := rand.New(rand.NewSource(1))

const ListRange = 2000

wpolicy := as.NewWritePolicy(0, 0)

wpolicy.SendKey = true

for i := 1; i <= 20; i++ {

    key, _ := as.NewKey(NAMESPACE, SET_NAME, fmt.Sprintf("id-%d", i))

    bin2 := []interface{}{r.Intn(ListRange), r.Intn(ListRange), r.Intn(ListRange)}

    if err := client.Put(wpolicy, key,

        as.NewBin("bin1", i),

        as.NewBin("bin2", bin2)); err != nil {

        log.Fatal(err)

    }

}

fmt.Println("Test data populated.")

// 2. Sleep for 2 seconds,

time.Sleep(2 * time.Second)

// 3. Touch the even numbered records.

touchStmt := as.NewStatement(NAMESPACE, SET_NAME)

touchPolicy := as.NewWritePolicy(0, 0)

touchPolicy.FilterExpression = as.ExpEq(

    as.ExpNumMod(as.ExpIntBin("bin1"), as.ExpIntVal(2)),

    as.ExpIntVal(0))

task, err := client.QueryExecute(nil, touchPolicy, touchStmt, as.TouchOp())

if err != nil {

    log.Fatal(err)

}

<-task.OnComplete()

fmt.Println("Touched even numbered records.")

// 4. Run the query with the filter.

//    records updated in last 2 seconds and whose list value range is more than 1000

queryStmt := as.NewStatement(NAMESPACE, SET_NAME)

// expression filter is specified in the query policy

queryPolicy := as.NewQueryPolicy()

queryPolicy.FilterExpression = as.ExpAnd(

    as.ExpLess(as.ExpSinceUpdate(), as.ExpIntVal(2000)), // updated in last 2s

    as.ExpGreater( // range of values in bin2 greater than 1000

        as.ExpNumSub(

            as.ExpListGetByRank(as.ListReturnTypeValue, as.ExpTypeINT, as.ExpIntVal(-1), as.ExpListBin("bin2")), // largest

            as.ExpListGetByRank(as.ListReturnTypeValue, as.ExpTypeINT, as.ExpIntVal(0), as.ExpListBin("bin2"))), // smallest

        as.ExpIntVal(1000)))

recordSet, err := client.Query(queryPolicy, queryStmt)

if err != nil {

    log.Fatal(err)

}

defer recordSet.Close()

fmt.Println("Results of filter expression query (all even records with bin2 max-min > 1000):")

for res := range recordSet.Results() {

    if res.Err != nil {

        log.Fatal(res.Err)

    }

    fmt.Printf("key=%v bins=%v\n", res.Record.Key.Value(), res.Record.Bins)

}
```

::: note
Output values depend on randomly generated test data. Go’s `math/rand` produces a different sequence than Python’s `random` or Java’s `java.util.Random` even from the same seed, so the printed keys and `bin2` values will differ from the Java output above, though the shape of the result is the same.
:::

This assumes the same setup as the Java example above, translated to the C# client: `AerospikeClient client = new AerospikeClient(null, host)`, with `NAMESPACE = "test"` and `SET_NAME = "expressions"`.

```csharp
using Aerospike.Client;

// start with a clean state

TruncateTestData();

// 1. Populate the test data with 20 records with an integer bin "bin1" values 1-20

//    and a list bin having 3 randomly selected numbers in the range 1 to 2000.

Random rand = new Random(1);

const int ListRange = 2000;

WritePolicy wpolicy = new WritePolicy();

wpolicy.sendKey = true;

for (int i = 1; i <= 20; i++)

{

    Key key = new Key(NAMESPACE, SET_NAME, "id-" + i);

    Bin bin1 = new Bin("bin1", i);

    List<int> intList = new List<int> {

        rand.Next(ListRange), rand.Next(ListRange), rand.Next(ListRange)

    };

    Bin bin2 = new Bin("bin2", intList);

    client.Put(wpolicy, key, bin1, bin2);

}

Console.WriteLine("Test data populated.");

// 2. Sleep for 2 seconds,

Thread.Sleep(2000);

// 3. Touch the even numbered records.

Statement touchStmt = new Statement();

touchStmt.SetNamespace(NAMESPACE);

touchStmt.SetSetName(SET_NAME);

WritePolicy touchPolicy = new WritePolicy();

touchPolicy.filterExp = Exp.Build(

    Exp.EQ(

        Exp.Mod(Exp.IntBin("bin1"), Exp.Val(2)),

        Exp.Val(0)));

ExecuteTask task = client.Execute(touchPolicy, touchStmt, Operation.Touch());

task.Wait();

Console.WriteLine("Touched even numbered records.");

// 4. Run the query with the filter.

//    records updated in last 2 seconds and whose list value range is more than 1000

Statement queryStmt = new Statement();

queryStmt.SetNamespace(NAMESPACE);

queryStmt.SetSetName(SET_NAME);

// expression filter is specified in the query policy

QueryPolicy queryPolicy = new QueryPolicy(client.QueryPolicyDefault);

queryPolicy.filterExp = Exp.Build(

    Exp.And(

        Exp.LT(Exp.SinceUpdate(), Exp.Val(2000)),   // updated in last 2s

        Exp.GT(   // range of values in bin2 greater than 1000

            Exp.Sub(

                ListExp.GetByRank(ListReturnType.VALUE, Exp.Type.INT, Exp.Val(-1), Exp.ListBin("bin2")),   // largest

                ListExp.GetByRank(ListReturnType.VALUE, Exp.Type.INT, Exp.Val(0), Exp.ListBin("bin2"))),   // smallest

            Exp.Val(1000))));

RecordSet rs = client.Query(queryPolicy, queryStmt);

Console.WriteLine("Results of filter expression query (all even records with bin2 max-min > 1000):");

try

{

    while (rs.Next())

    {

        Key key = rs.Key;

        Record record = rs.Record;

        Console.WriteLine("key={0} bins={1}", key.userKey, record.ToString().Split("bins:")[1]);

    }

}

finally

{

    rs.Close();

}
```

::: note
Output values depend on randomly generated test data. .NET’s `Random` produces a different sequence than Python’s `random`, Java’s `java.util.Random`, or Go’s `math/rand` even from the same seed, so the printed keys and `bin2` values will differ from the Java output above, though the shape of the result is the same.
:::

This assumes the same setup as the Java example above, translated to the Node.js client: `const client = await Aerospike.connect(config)`, with `NAMESPACE = 'test'` and `SET_NAME = 'expressions'`.

```js
const Aerospike = require('aerospike');

const exp = Aerospike.exp;

const op = Aerospike.operations;

const lists = Aerospike.lists;

// start with a clean state

await truncateTestData();

// 1. Populate the test data with 20 records with an integer bin "bin1" values 1-20

//    and a list bin having 3 randomly selected numbers in the range 1 to 2000.

const ListRange = 2000;

const writePolicy = new Aerospike.WritePolicy({ key: Aerospike.policy.key.SEND });

for (let i = 1; i <= 20; i++) {

    const key = new Aerospike.Key(NAMESPACE, SET_NAME, `id-${i}`);

    const bin2 = [

        Math.floor(Math.random() * ListRange),

        Math.floor(Math.random() * ListRange),

        Math.floor(Math.random() * ListRange),

    ];

    await client.put(key, { bin1: i, bin2 }, {}, writePolicy);

}

console.log('Test data populated.');

// 2. Sleep for 2 seconds,

await new Promise((resolve) => setTimeout(resolve, 2000));

// 3. Touch the even numbered records.

const touchPolicy = new Aerospike.QueryPolicy({

    filterExpression: exp.eq(exp.mod(exp.binInt('bin1'), exp.int(2)), exp.int(0)),

});

const touchQuery = client.query(NAMESPACE, SET_NAME);

const touchJob = await touchQuery.operate([op.touch()], touchPolicy);

await touchJob.wait();

console.log('Touched even numbered records.');

// 4. Run the query with the filter.

//    records updated in last 2 seconds and whose list value range is more than 1000

const queryPolicy = new Aerospike.QueryPolicy({

    filterExpression: exp.and(

        exp.lt(exp.sinceUpdate(), exp.int(2000)), // updated in last 2s

        exp.gt( // range of values in bin2 greater than 1000

            exp.sub(

                exp.lists.getByRank(exp.binList('bin2'), exp.int(-1), exp.type.INT, lists.returnType.VALUE), // largest

                exp.lists.getByRank(exp.binList('bin2'), exp.int(0), exp.type.INT, lists.returnType.VALUE)), // smallest

            exp.int(1000))),

});

console.log('Results of filter expression query (all even records with bin2 max-min > 1000):');

const query = client.query(NAMESPACE, SET_NAME);

const stream = query.foreach(queryPolicy);

stream.on('data', (record) => {

    console.log(`key=${record.key.key} bins=${JSON.stringify(record.bins)}`);

});

stream.on('error', (error) => {

    throw error;

});
```

::: note
Output values depend on randomly generated test data. JavaScript’s `Math.random()` is unseeded, so the printed keys and `bin2` values will differ from the Java output above (and from run to run), though the shape of the result is the same.
:::

This assumes the same setup as the Java example above, translated to the Rust client: `Client::new(&ClientPolicy::default(), &"127.0.0.1:3000".to_string()).await?`, with `NAMESPACE = "test"` and `SET = "expressions"`. This example requires the `rand` crate (`cargo add rand`).

```rust
use aerospike::expressions::lists::get_by_rank;

use aerospike::expressions::{and, gt, int_bin, int_val, list_bin, lt, num_sub, since_update, ExpType};

use aerospike::query::PartitionFilter;

use aerospike::{as_bin, as_key, as_list, Bins, ListReturnType, QueryPolicy, Statement, WritePolicy};

use futures::stream::StreamExt;

use rand::{rngs::StdRng, Rng, SeedableRng};

// start with a clean state

truncate_test_data(&client).await;

// 1. Populate the test data with 20 records with an integer bin "bin1" values 1-20

//    and a list bin having 3 randomly selected numbers in the range 1 to 2000.

let mut rng = StdRng::seed_from_u64(1);

const LIST_RANGE: i64 = 2000;

let mut wpolicy = WritePolicy::default();

wpolicy.send_key = true;

for i in 1..=20 {

    let key = as_key!(NAMESPACE, SET, format!("id-{i}"));

    let (v0, v1, v2) = (

        rng.gen_range(0..LIST_RANGE),

        rng.gen_range(0..LIST_RANGE),

        rng.gen_range(0..LIST_RANGE),

    );

    let bins = vec![as_bin!("bin1", i), as_bin!("bin2", as_list!(v0, v1, v2))];

    client.put(&wpolicy, &key, &bins).await?;

}

println!("Test data populated.");

// 2. Sleep for 2 seconds,

tokio::time::sleep(std::time::Duration::from_secs(2)).await;

// 3. Touch the even numbered records. The Rust client has no

//    background query-execute for arbitrary operations (only UDFs),

//    so touch each even record directly instead.

for i in (2..=20).step_by(2) {

    let key = as_key!(NAMESPACE, SET, format!("id-{i}"));

    client.touch(&WritePolicy::default(), &key).await?;

}

println!("Touched even numbered records.");

// 4. Run the query with the filter.

//    records updated in last 2 seconds and whose list value range is more than 1000

let mut qpolicy = QueryPolicy::default();

qpolicy.base_policy.filter_expression = Some(and(vec![

    lt(since_update(), int_val(2000)), // updated in last 2s

    gt( // range of values in bin2 greater than 1000

        num_sub(vec![

            get_by_rank(ListReturnType::Values, ExpType::INT, int_val(-1), list_bin("bin2".to_string()), &[]), // largest

            get_by_rank(ListReturnType::Values, ExpType::INT, int_val(0), list_bin("bin2".to_string()), &[]),  // smallest

        ]),

        int_val(1000),

    ),

]));

let stmt = Statement::new(NAMESPACE, SET, Bins::All);

let rs = client.query(&qpolicy, PartitionFilter::all(), stmt).await?;

let mut stream = rs.into_stream();

println!("Results of filter expression query (all even records with bin2 max-min > 1000):");

while let Some(result) = stream.next().await {

    let record = result?;

    let key = record.key.and_then(|k| k.user_key);

    println!("key={:?} bins={:?}", key, record.bins);

}
```

::: note
Output values depend on randomly generated test data. Rust’s `rand` crate produces a different sequence than Python’s `random`, Java’s `java.util.Random`, Go’s `math/rand`, or .NET’s `Random` even from the same seed, so the printed keys and `bin2` values will differ from the Java output above, though the shape of the result is the same.
:::

You may view the state of the database and ensure correctness of the output by running the following command in the terminal tab:

`aql -c "select * from test.expressions"`

### AEL text equivalent

The Developer SDK (Java or Python) can author both filters above as [AEL](https://aerospike.com/docs/develop/client/sdk/concepts/ael/reference) text instead of building an `Exp` tree. The touch filter (even-valued `bin1`) is:

```text
$.bin1 % 2 == 0
```

The query filter (updated in the last 2 seconds, with `bin2`’s value range greater than 1000) uses the same `[#0]`/`[#-1]` rank selectors as `ListExp.getByRank` — rank `0` is the smallest value and rank `-1` is the largest:

```text
$.timeSinceLastUpdate() < 2000 and ($.bin2.[#-1] - $.bin2.[#0]) > 1000
```

For example, the query filter with `.where()`:

-   [Java](#tab-panel-5643)
-   [Python](#tab-panel-5644)
-   [Go](#tab-panel-5645)
-   [C#](#tab-panel-5646)
-   [Node.js](#tab-panel-5647)
-   [Rust](#tab-panel-5648)

```java
DataSet records = DataSet.of(NAMESPACE, SET_NAME);

RecordStream stream = session.query(records)

    .where("$.timeSinceLastUpdate() < 2000 and ($.bin2.[#-1] - $.bin2.[#0]) > 1000")

    .execute();
```

```python
records = DataSet.of(NAMESPACE, SET_NAME)

stream = await (

    session.query(records)

    .where("$.timeSinceLastUpdate() < 2000 and ($.bin2.[#-1] - $.bin2.[#0]) > 1000")

    .execute()

)
```

::: note
AEL text authoring is available for the Developer SDK (Java and Python) only. Go continues to use the `Exp*` builder shown above.
:::

::: note
AEL text authoring is available for the Developer SDK (Java and Python) only. C# continues to use the `Exp` builder shown above.
:::

::: note
AEL text authoring is available for the Developer SDK (Java and Python) only. Node.js continues to use the `exp` builder shown above.
:::

::: note
AEL text authoring is available for the Developer SDK (Java and Python) only. Rust continues to use the `aerospike::expressions` builder shown above.
:::

## Next

Continue to [Operation expressions](https://aerospike.com/docs/develop/tutorials/operations/expressions/operation-expressions).