Operation 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 use operation expressions to read and write computed bin values server-side without a read-modify-write round trip.
Operation expressions as the name suggests are used in an operation -
either to read from bins or write to a bin. Specifically they are used
in read and write methods of ExpOperation.
The basic computational model of operate, where operation expressions
are used, remains the same: A series of read or write commands are
performed in a given sequence on a single record. What is new is that a
read command can be an expression involving zero or more bins. Also, a
write command can get the value from an expression (enabling, for
example, use of cross-bin data with conditional logic) instead of a
simple constant to update a bin.
A read with operation expression can also use an arbitrary name for the
“computed bin” similar to the “as” keyword in the SQL statement
SELECT expr AS bin.
The pattern for coding an Operation Expression is:
- Define Expression to read or write the bins.
- Use
Expressionobject inExpOperation.reador.writemethod that returns anOperation. - Use “expression operations” in any API call that takes an operation list.
This is illustrated below.
// operate expression with write// 1. Define Expression to write the bin with.// if (age > 65 && comorbidities) {risk = "high";}// else {risk = "normal";}Expression writeExp = Exp.build( Exp.cond( Exp.and( Exp.gt( Exp.intBin("age"), Exp.val(65)), Exp.boolBin("comorbidities")), Exp.val("high"), Exp.val("normal")));// 2. Use Expression object in ExpOperation.write method.Operation writeExpOp = ExpOperation.write("risk", writeExp, // evaluates bin value to update ExpWriteFlags.DEFAULT);
// operate expression with read// 1. Define Expression to read bins.// read "yes" if (risk == "high" or worktype == "frontline") else "no"// as a computed bin "eligible"Expression readExp = Exp.build( Exp.cond( Exp.or( Exp.eq( Exp.stringBin("risk"), Exp.val("high")), Exp.eq( Exp.stringBin("worktype"), Exp.val("frontline"))), Exp.val("yes"), Exp.val("no")));// 2. Use Expression object in ExpOperation.read method.Operation readExpOp = ExpOperation.read("eligible", // named "computed bin" readExp, // evaluates value to return ExpReadFlags.DEFAULT);
// 3. Use "expression operations" in any API call that takes an operation list.Record record = Client.operate(WritePolicy policy, Key key, Operation writeExpOp, Operation readExpOp);# operate expression with write# 1. Define Expression to write the bin with.# if age > 65 and comorbidities: risk = "high"# else: risk = "normal"write_exp = exp.Cond( exp.And( exp.GT( exp.IntBin("age"), 65), exp.BoolBin("comorbidities")), "high", "normal").compile()# 2. Use the compiled expression in an expression_write operation.write_exp_op = expression_operations.expression_write( "risk", write_exp, # evaluates bin value to update aerospike.EXP_WRITE_DEFAULT)
# operate expression with read# 1. Define Expression to read bins.# read "yes" if (risk == "high" or worktype == "frontline") else "no"# as a computed bin "eligible"read_exp = exp.Cond( exp.Or( exp.Eq( exp.StringBin("risk"), "high"), exp.Eq( exp.StringBin("worktype"), "frontline")), "yes", "no").compile()# 2. Use the compiled expression in an expression_read operation.read_exp_op = expression_operations.expression_read( "eligible", # named "computed bin" read_exp, # evaluates value to return aerospike.EXP_READ_DEFAULT)
# 3. Use "expression operations" in any API call that takes an operation list._, _, record = client.operate(key, [write_exp_op, read_exp_op], policy=policy)// operate expression with write// 1. Define Expression to write the bin with.// if age > 65 && comorbidities { risk = "high" }// else { risk = "normal" }writeExp := as.ExpCond( as.ExpAnd( as.ExpGreater( as.ExpIntBin("age"), as.ExpIntVal(65)), as.ExpBoolBin("comorbidities")), as.ExpStringVal("high"), as.ExpStringVal("normal"))// 2. Use the expression in an ExpWriteOp.writeExpOp := as.ExpWriteOp("risk", writeExp, // evaluates bin value to update as.ExpWriteFlagDefault)
// operate expression with read// 1. Define Expression to read bins.// read "yes" if (risk == "high" or worktype == "frontline") else "no"// as a computed bin "eligible"readExp := as.ExpCond( as.ExpOr( as.ExpEq( as.ExpStringBin("risk"), as.ExpStringVal("high")), as.ExpEq( as.ExpStringBin("worktype"), as.ExpStringVal("frontline"))), as.ExpStringVal("yes"), as.ExpStringVal("no"))// 2. Use the expression in an ExpReadOp.readExpOp := as.ExpReadOp("eligible", // named "computed bin" readExp, // evaluates value to return as.ExpReadFlagDefault)
// 3. Use "expression operations" in any API call that takes an operation list.record, err := client.Operate(policy, key, writeExpOp, readExpOp)if err != nil { log.Fatal(err)}// operate expression with write// 1. Define Expression to write the bin with.// if (age > 65 && comorbidities) {risk = "high";}// else {risk = "normal";}Expression writeExp = Exp.Build( Exp.Cond( Exp.And( Exp.GT( Exp.IntBin("age"), Exp.Val(65)), Exp.BoolBin("comorbidities")), Exp.Val("high"), Exp.Val("normal")));// 2. Use the Expression object in the ExpOperation.Write method.Operation writeExpOp = ExpOperation.Write("risk", writeExp, // evaluates bin value to update ExpWriteFlags.DEFAULT);
// operate expression with read// 1. Define Expression to read bins.// read "yes" if (risk == "high" or worktype == "frontline") else "no"// as a computed bin "eligible"Expression readExp = Exp.Build( Exp.Cond( Exp.Or( Exp.EQ( Exp.StringBin("risk"), Exp.Val("high")), Exp.EQ( Exp.StringBin("worktype"), Exp.Val("frontline"))), Exp.Val("yes"), Exp.Val("no")));// 2. Use the Expression object in the ExpOperation.Read method.Operation readExpOp = ExpOperation.Read("eligible", // named "computed bin" readExp, // evaluates value to return ExpReadFlags.DEFAULT);
// 3. Use "expression operations" in any API call that takes an operation list.Record record = client.Operate(policy, key, writeExpOp, readExpOp);// operate expression with write// 1. Define Expression to write the bin with.// if (age > 65 && comorbidities) { risk = 'high' }// else { risk = 'normal' }const writeExpr = exp.cond( exp.and( exp.gt( exp.binInt('age'), exp.int(65)), exp.binBool('comorbidities')), exp.str('high'), exp.str('normal'));// 2. Use the expression in an exp.operations.write op.const writeExpOp = exp.operations.write('risk', writeExpr, // evaluates bin value to update exp.expWriteFlags.DEFAULT);
// operate expression with read// 1. Define Expression to read bins.// read 'yes' if (risk == 'high' or worktype == 'frontline') else 'no'// as a computed bin 'eligible'const readExpr = exp.cond( exp.or( exp.eq( exp.binStr('risk'), exp.str('high')), exp.eq( exp.binStr('worktype'), exp.str('frontline'))), exp.str('yes'), exp.str('no'));// 2. Use the expression in an exp.operations.read op.const readExpOp = exp.operations.read('eligible', // named "computed bin" readExpr, // evaluates value to return exp.expReadFlags.DEFAULT);
// 3. Use "expression operations" in any API call that takes an operation list.const record = await client.operate(key, [writeExpOp, readExpOp], {}, policy);use aerospike::expressions::{and, bool_bin, cond, eq, gt, int_bin, int_val, or, string_bin, string_val};use aerospike::operations::exp::{read_exp, write_exp, ExpReadFlags, ExpWriteFlags};
// operate expression with write// 1. Define Expression to write the bin with.// if age > 65 && comorbidities { risk = "high" }// else { risk = "normal" }let write_expr = cond(vec![ and(vec![ gt(int_bin("age".to_string()), int_val(65)), bool_bin("comorbidities".to_string())]), string_val("high".to_string()), string_val("normal".to_string())]);// 2. Use the expression in a write_exp operation.let write_exp_op = write_exp("risk", write_expr, // evaluates bin value to update ExpWriteFlags::Default);
// operate expression with read// 1. Define Expression to read bins.// read "yes" if (risk == "high" or worktype == "frontline") else "no"// as a computed bin "eligible"let read_expr = cond(vec![ or(vec![ eq(string_bin("risk".to_string()), string_val("high".to_string())), eq(string_bin("worktype".to_string()), string_val("frontline".to_string()))]), string_val("yes".to_string()), string_val("no".to_string())]);// 2. Use the expression in a read_exp operation.let read_exp_op = read_exp("eligible", // named "computed bin" read_expr, // evaluates value to return ExpReadFlags::Default);
// 3. Use "expression operations" in any API call that takes an operation list.let record = client.operate(&policy, &key, &[write_exp_op, read_exp_op]).await?;Note that the order of operations is important. The readExp relies on the risk bin which gets written in the writeExp, so the writeExp must come first.
With the Developer SDK, the write and read operation expressions above
are AEL text passed to
.upsertFrom() and .selectFrom():
DataSet people = DataSet.of(NAMESPACE, SET_NAME);
Record record = session.upsert(people.id(userId)) // write: same condition as writeExp above .bin("risk").upsertFrom( "when ($.age > 65 and $.comorbidities == true => 'high', default => 'normal')") // read: same condition as readExp above, as a computed "eligible" bin .bin("eligible").selectFrom( "when ($.risk == 'high' or $.worktype == 'frontline' => 'yes', default => 'no')") .execute() .getFirstRecord();people = DataSet.of(NAMESPACE, SET_NAME)
stream = await ( session.upsert(people.id(user_id)) # write: same condition as write_exp above .bin("risk").upsert_from( "when ($.age > 65 and $.comorbidities == true => 'high', default => 'normal')") # read: same condition as read_exp above, as a computed "eligible" bin .bin("eligible").select_from( "when ($.risk == 'high' or $.worktype == 'frontline' => 'yes', default => 'no')") .execute())record = await stream.first_or_raise()Using expression operations vs R-M-W or UDFs
Aerospike developers have multiple ways to perform a record oriented read-write logic.
- Read record data to the client, modify, and write back (“R-M-W”).
- Create a UDF for the logic and invoke it on the record.
- Use expression operations in a multi-op request.
For read-write transactions, fetching the data to the client and writing back is expensive and requires special care to ensure read-write isolation. Lua UDFs can be difficult to implement, less flexible to change, and can be slower. So it is generally beneficial to use expression operations when possible.
Here is a suggested decision process:
- Use expression operations. However if expression operations cannot be used because the task, for example, requires unsupported features such as iterators and loops, then:
- Use client-side Read-Modify-Write (R-M-W) with version check if amount of data transfer as well as possibility of conflict due to concurrency is limited. Otherwise:
- Use UDFs if Lua server side programming model and performance meet the needs. Otherwise must use 2.
Note, Aerospike provides many ways to implement a given data task on one or multiple records. To determine the optimal way for a given task, one should consider and evaluate the options available including the various execution modes (synchronous, asynchronous, background, etc).
Next
Continue to the full operation expressions code example.