Filter expressions
For the complete documentation index see: llms.txt
All documentation pages available in markdown.
This page is for developers using Aerospike client libraries. Complete 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.
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 callsquery_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 callspolicy := 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 callsPolicy 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 callsconst 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 callsuse 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 callsWith the Developer SDK, the same filter is AEL text passed to .where():
DataSet records = DataSet.of(NAMESPACE, SET_NAME);RecordStream stream = session.query(records) .where("$.a == 11") .execute();records = DataSet.of(NAMESPACE, SET_NAME)stream = await session.query(records) \ .where("$.a == 11") \ .execute()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.
- 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.
- Sleep for 2 seconds,
- Touch the even numbered records.
- Run the query with the filter.
The results should only contain even valued bin1 and bin2 with value range > 1000.
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 statetruncateTestData();
// 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 1000stmt = new Statement();stmt.setNamespace(NAMESPACE);stmt.setSetName(SET_NAME);
// expression filter is specifed in the operation policyQueryPolicy 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:
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".
import randomimport timefrom aerospike_helpers import expressions as expfrom aerospike_helpers.expressions.list import ListGetByRankfrom aerospike_helpers.operations import operations
# start with a clean statetruncate_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 = 2000write_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 1000query_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))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".
import ( "fmt" "log" "math/rand" "time"
as "github.com/aerospike/aerospike-client-go/v6")
// start with a clean statetruncateTestData()
// 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 = 2000wpolicy := as.NewWritePolicy(0, 0)wpolicy.SendKey = truefor 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 1000queryStmt := as.NewStatement(NAMESPACE, SET_NAME)
// expression filter is specified in the query policyqueryPolicy := 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)}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".
using Aerospike.Client;
// start with a clean stateTruncateTestData();
// 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 1000Statement queryStmt = new Statement();queryStmt.SetNamespace(NAMESPACE);queryStmt.SetSetName(SET_NAME);
// expression filter is specified in the query policyQueryPolicy 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();}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'.
const Aerospike = require('aerospike');const exp = Aerospike.exp;const op = Aerospike.operations;const lists = Aerospike.lists;
// start with a clean stateawait 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 1000const 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;});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).
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 statetruncate_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 1000let 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);}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 text instead of
building an Exp tree. The touch filter (even-valued
bin1) is:
$.bin1 % 2 == 0The 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:
$.timeSinceLastUpdate() < 2000 and ($.bin2.[#-1] - $.bin2.[#0]) > 1000For example, the query filter with .where():
DataSet records = DataSet.of(NAMESPACE, SET_NAME);RecordStream stream = session.query(records) .where("$.timeSinceLastUpdate() < 2000 and ($.bin2.[#-1] - $.bin2.[#0]) > 1000") .execute();records = DataSet.of(NAMESPACE, SET_NAME)stream = await ( session.query(records) .where("$.timeSinceLastUpdate() < 2000 and ($.bin2.[#-1] - $.bin2.[#0]) > 1000") .execute())Next
Continue to Operation expressions.