Operation expressions code example
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 working through this example, you can compute grades and a report server-side with operation expressions.
In the following example, these new capabilities that were not possible earlier are illustrated:
- expressions involving zero or more bins to write a bin
- named “computed bins” that return the value of a specified expression involving zero or more bins
- conditional evaluation of expression
- use of variables in an expression
The code has the following steps:
- The test data is populated with three randomly generated test scores ranging from 50 to 99 for student ids 1-20.
- The data is updated by writing two additional bins: “class” which represents the teacher’s input (0-9) based on class participation, and “grade” which is computed by adding “classwork” to average of test scores, and using this formula to compute the grade: scores below 70 receive C; scores from 70 through 85 receive B; scores above 85 receive A.
- A report is then generated for the id, grade, total score, and min/max/average of test scores.
The following snippets assume the client setup from Setup.
import com.aerospike.client.exp.Expression;import com.aerospike.client.exp.ExpOperation;import com.aerospike.client.exp.ExpReadFlags;import com.aerospike.client.exp.ExpWriteFlags;
// start with a clean statetruncateTestData();
// 1. The test data is populated with three randomly generated test scores ranging from 50 to 100// for student ids 1-20.
Random rand = new Random(1);final int SCORE_RANGE = 50;WritePolicy wpolicy = copyWritePolicyDefault();wpolicy.sendKey = true;for (int i = 1; i <= 20; i++) { Key key = new Key(NAMESPACE, SET_NAME, "id-"+i); Bin id = new Bin("id", i); List<Integer> testScores = List.of( 50 + rand.nextInt(SCORE_RANGE), 50 + rand.nextInt(SCORE_RANGE), 50 + rand.nextInt(SCORE_RANGE)); Bin tests = new Bin("tests", testScores); client.put(wpolicy, key, id, tests);}System.out.println("Test data populated.");
// 2. The data is updated by writing two additional bins: "class" which represents the teacher's input (0-10)// based on class participation, and "grade" which is computed by adding "classwork" to average of test scores,// and using this formula: 50-70 -> C, 65==70-85 -> B, 85+ -> A.
// define the expressions for average test score, total score, grade, min test score, and max test scoreExp avgScoreExp = Exp.let( Exp.def("testsBin", Exp.listBin("tests")), Exp.div( Exp.add( ListExp.getByIndex(ListReturnType.VALUE, Exp.Type.INT, Exp.val(0), Exp.var("testsBin")), ListExp.getByIndex(ListReturnType.VALUE, Exp.Type.INT, Exp.val(1), Exp.var("testsBin")), ListExp.getByIndex(ListReturnType.VALUE, Exp.Type.INT, Exp.val(2), Exp.var("testsBin"))), Exp.val(3)));Expression avgScoreExpression = Exp.build(avgScoreExp);
Exp totalScoreExp = Exp.add(avgScoreExp, Exp.intBin("class"));Expression totalScoreExpression = Exp.build(totalScoreExp);
Exp gradeExp = Exp.let( Exp.def("total", totalScoreExp), Exp.cond( Exp.lt(Exp.var("total"), Exp.val(70)), Exp.val("C"), Exp.le(Exp.var("total"), Exp.val(85)), Exp.val("B"), Exp.gt(Exp.var("total"), Exp.val(85)), Exp.val("A"), Exp.unknown()));Expression gradeExpression = Exp.build(gradeExp);
Exp minExp = ListExp.getByRank(ListReturnType.VALUE, Exp.Type.INT, Exp.val(0), Exp.listBin("tests")); // minExpression minExpression = Exp.build(minExp);
Exp maxExp = ListExp.getByRank(ListReturnType.VALUE, Exp.Type.INT, Exp.val(-1), Exp.listBin("tests")); // maxExpression maxExpression = Exp.build(maxExp);
// update class bin with a random 0-9, and the grade bin using gradeExpOpfor (int i = 1; i <= 20; i++) { Key key = new Key(NAMESPACE, SET_NAME, "id-"+i); int classwork = rand.nextInt(SCORE_RANGE)/5; Bin classBin = new Bin(new String("class"), classwork);
// write gradeExp to the bin "grade" Operation gradeExpOp = ExpOperation.write("grade", gradeExpression, ExpWriteFlags.DEFAULT);
client.operate(wpolicy, key, Operation.put(classBin), gradeExpOp);}System.out.println("Updated class participation and computed grades.");;
// run a report for all students with id, grade, total, min, max, avg// using total, min, max, and avg expressions defined above
Operation ops[] = { Operation.get("id"), Operation.get("grade"), ExpOperation.read("total", totalScoreExpression, ExpReadFlags.DEFAULT), ExpOperation.read("testMin", minExpression, ExpReadFlags.DEFAULT), ExpOperation.read("testMax", maxExpression, ExpReadFlags.DEFAULT), ExpOperation.read("testAvg", avgScoreExpression, ExpReadFlags.DEFAULT)};
for (int i = 1; i <= 20; i++) { Key key = new Key(NAMESPACE, SET_NAME, "id-"+i); Record record = client.operate(null, key, ops); System.out.format("key=%s bins=%s\n", key.userKey, record.bins);}Output:
Test data populated.Updated class participation and computed grades.key=id-1 bins={id=1, grade=A, total=90, testMin=85, testMax=97, testAvg=90}key=id-2 bins={id=2, grade=C, total=57, testMin=54, testMax=63, testAvg=57}key=id-3 bins={id=3, grade=B, total=79, testMin=56, testMax=84, testAvg=72}key=id-4 bins={id=4, grade=A, total=86, testMin=69, testMax=98, testAvg=80}key=id-5 bins={id=5, grade=B, total=73, testMin=62, testMax=67, testAvg=64}key=id-6 bins={id=6, grade=A, total=88, testMin=62, testMax=92, testAvg=79}key=id-7 bins={id=7, grade=A, total=88, testMin=76, testMax=96, testAvg=87}key=id-8 bins={id=8, grade=B, total=82, testMin=60, testMax=99, testAvg=80}key=id-9 bins={id=9, grade=B, total=80, testMin=59, testMax=98, testAvg=77}key=id-10 bins={id=10, grade=C, total=69, testMin=52, testMax=87, testAvg=64}key=id-11 bins={id=11, grade=C, total=54, testMin=50, testMax=55, testAvg=53}key=id-12 bins={id=12, grade=C, total=61, testMin=55, testMax=63, testAvg=58}key=id-13 bins={id=13, grade=B, total=81, testMin=70, testMax=89, testAvg=78}key=id-14 bins={id=14, grade=B, total=70, testMin=60, testMax=84, testAvg=69}key=id-15 bins={id=15, grade=B, total=84, testMin=77, testMax=87, testAvg=80}key=id-16 bins={id=16, grade=A, total=90, testMin=70, testMax=92, testAvg=83}key=id-17 bins={id=17, grade=C, total=61, testMin=50, testMax=57, testAvg=53}key=id-18 bins={id=18, grade=B, total=77, testMin=55, testMax=83, testAvg=71}key=id-19 bins={id=19, grade=B, total=79, testMin=58, testMax=95, testAvg=79}key=id-20 bins={id=20, grade=B, total=81, testMin=54, testMax=84, testAvg=72}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 randomfrom aerospike_helpers import expressions as expfrom aerospike_helpers.expressions.list import ListGetByIndex, ListGetByRankfrom aerospike_helpers.operations import expression_operations, operations
# start with a clean statetruncate_test_data()
# 1. The test data is populated with three randomly generated test scores ranging from 50 to 100# for student ids 1-20.
random.seed(1)SCORE_RANGE = 50write_policy = {"key": aerospike.POLICY_KEY_SEND}for i in range(1, 21): key = (NAMESPACE, SET_NAME, "id-" + str(i)) test_scores = [50 + random.randint(0, SCORE_RANGE - 1) for _ in range(3)] client.put(key, {"id": i, "tests": test_scores}, policy=write_policy)print("Test data populated.")
# 2. The data is updated by writing two additional bins: "class" which represents the teacher's input (0-10)# based on class participation, and "grade" which is computed by adding "classwork" to average of test scores,# and using this formula: 50-70 -> C, 65==70-85 -> B, 85+ -> A.
# define the expressions for average test score, total score, grade, min test score, and max test scoreavg_score_exp = exp.Let( exp.Def("testsBin", exp.ListBin("tests")), exp.Div( exp.Add( ListGetByIndex(None, aerospike.LIST_RETURN_VALUE, exp.ResultType.INTEGER, 0, exp.Var("testsBin")), ListGetByIndex(None, aerospike.LIST_RETURN_VALUE, exp.ResultType.INTEGER, 1, exp.Var("testsBin")), ListGetByIndex(None, aerospike.LIST_RETURN_VALUE, exp.ResultType.INTEGER, 2, exp.Var("testsBin")), ), 3, ),)avg_score_expression = avg_score_exp.compile()
total_score_exp = exp.Let( exp.Def("classBin", exp.IntBin("class")), exp.Add(avg_score_exp, exp.Var("classBin")),)total_score_expression = total_score_exp.compile()
grade_exp = exp.Let( exp.Def("total", total_score_exp), exp.Cond( exp.LT(exp.Var("total"), 70), "C", exp.LE(exp.Var("total"), 85), "B", exp.GT(exp.Var("total"), 85), "A", exp.Unknown(), ),)grade_expression = grade_exp.compile()
min_exp = ListGetByRank(None, aerospike.LIST_RETURN_VALUE, exp.ResultType.INTEGER, 0, exp.ListBin("tests")) # minmin_expression = min_exp.compile()
max_exp = ListGetByRank(None, aerospike.LIST_RETURN_VALUE, exp.ResultType.INTEGER, -1, exp.ListBin("tests")) # maxmax_expression = max_exp.compile()
# update class bin with a random 1-10, and the grade bin using gradeExpressionfor i in range(1, 21): key = (NAMESPACE, SET_NAME, "id-" + str(i)) classwork = random.randint(0, SCORE_RANGE - 1) // 5
# write gradeExpression to the bin "grade" ops = [ operations.write("class", classwork), expression_operations.expression_write("grade", grade_expression, aerospike.EXP_WRITE_DEFAULT), ] client.operate(key, ops)print("Updated class participation and computed grades.")
# run a report for all students with id, grade, total, min, max, avg# using total, min, max, and avg expressions defined above
ops = [ operations.read("id"), operations.read("grade"), expression_operations.expression_read("total", total_score_expression, aerospike.EXP_READ_DEFAULT), expression_operations.expression_read("testMin", min_expression, aerospike.EXP_READ_DEFAULT), expression_operations.expression_read("testMax", max_expression, aerospike.EXP_READ_DEFAULT), expression_operations.expression_read("testAvg", avg_score_expression, aerospike.EXP_READ_DEFAULT),]
for i in range(1, 21): key = (NAMESPACE, SET_NAME, "id-" + str(i)) _, _, bins = client.operate(key, ops) 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"
as "github.com/aerospike/aerospike-client-go/v6")
// start with a clean statetruncateTestData()
// 1. The test data is populated with three randomly generated test scores ranging from 50 to 100// for student ids 1-20.
r := rand.New(rand.NewSource(1))const ScoreRange = 50wpolicy := as.NewWritePolicy(0, 0)wpolicy.SendKey = truefor i := 1; i <= 20; i++ { key, _ := as.NewKey(NAMESPACE, SET_NAME, fmt.Sprintf("id-%d", i)) tests := []interface{}{ 50 + r.Intn(ScoreRange), 50 + r.Intn(ScoreRange), 50 + r.Intn(ScoreRange), } if err := client.Put(wpolicy, key, as.NewBin("id", i), as.NewBin("tests", tests)); err != nil { log.Fatal(err) }}fmt.Println("Test data populated.")
// 2. The data is updated by writing two additional bins: "class" which represents the teacher's input (0-10)// based on class participation, and "grade" which is computed by adding "classwork" to average of test scores,// and using this formula: 50-70 -> C, 65==70-85 -> B, 85+ -> A.
// define the expressions for average test score, total score, grade, min test score, and max test scoreavgScoreExp := as.ExpLet( as.ExpDef("testsBin", as.ExpListBin("tests")), as.ExpNumDiv( as.ExpNumAdd( as.ExpListGetByIndex(as.ListReturnTypeValue, as.ExpTypeINT, as.ExpIntVal(0), as.ExpVar("testsBin")), as.ExpListGetByIndex(as.ListReturnTypeValue, as.ExpTypeINT, as.ExpIntVal(1), as.ExpVar("testsBin")), as.ExpListGetByIndex(as.ListReturnTypeValue, as.ExpTypeINT, as.ExpIntVal(2), as.ExpVar("testsBin"))), as.ExpIntVal(3)))
totalScoreExp := as.ExpLet( as.ExpDef("classBin", as.ExpIntBin("class")), as.ExpNumAdd(avgScoreExp, as.ExpVar("classBin")))
gradeExp := as.ExpLet( as.ExpDef("total", totalScoreExp), as.ExpCond( as.ExpLess(as.ExpVar("total"), as.ExpIntVal(70)), as.ExpStringVal("C"), as.ExpLessEq(as.ExpVar("total"), as.ExpIntVal(85)), as.ExpStringVal("B"), as.ExpGreater(as.ExpVar("total"), as.ExpIntVal(85)), as.ExpStringVal("A"), as.ExpUnknown()))
minExp := as.ExpListGetByRank(as.ListReturnTypeValue, as.ExpTypeINT, as.ExpIntVal(0), as.ExpListBin("tests")) // minmaxExp := as.ExpListGetByRank(as.ListReturnTypeValue, as.ExpTypeINT, as.ExpIntVal(-1), as.ExpListBin("tests")) // max
// update class bin with a random 1-10, and the grade bin using gradeExpfor i := 1; i <= 20; i++ { key, _ := as.NewKey(NAMESPACE, SET_NAME, fmt.Sprintf("id-%d", i)) classwork := r.Intn(ScoreRange) / 5
// write gradeExp to the bin "grade" gradeExpOp := as.ExpWriteOp("grade", gradeExp, as.ExpWriteFlagDefault)
if _, err := client.Operate(wpolicy, key, as.PutOp(as.NewBin("class", classwork)), gradeExpOp); err != nil { log.Fatal(err) }}fmt.Println("Updated class participation and computed grades.")
// run a report for all students with id, grade, total, min, max, avg// using total, min, max, and avg expressions defined above
ops := []*as.Operation{ as.GetBinOp("id"), as.GetBinOp("grade"), as.ExpReadOp("total", totalScoreExp, as.ExpReadFlagDefault), as.ExpReadOp("testMin", minExp, as.ExpReadFlagDefault), as.ExpReadOp("testMax", maxExp, as.ExpReadFlagDefault), as.ExpReadOp("testAvg", avgScoreExp, as.ExpReadFlagDefault),}
for i := 1; i <= 20; i++ { key, _ := as.NewKey(NAMESPACE, SET_NAME, fmt.Sprintf("id-%d", i)) record, err := client.Operate(nil, key, ops...) if err != nil { log.Fatal(err) } fmt.Printf("key=%v bins=%v\n", record.Key.Value(), 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. The test data is populated with three randomly generated test scores ranging from 50 to 100// for student ids 1-20.
Random rand = new Random(1);const int ScoreRange = 50;WritePolicy wpolicy = new WritePolicy();wpolicy.sendKey = true;for (int i = 1; i <= 20; i++){ Key key = new Key(NAMESPACE, SET_NAME, "id-" + i); Bin id = new Bin("id", i); List<int> testScores = new List<int> { 50 + rand.Next(ScoreRange), 50 + rand.Next(ScoreRange), 50 + rand.Next(ScoreRange) }; Bin tests = new Bin("tests", testScores); client.Put(wpolicy, key, id, tests);}Console.WriteLine("Test data populated.");
// 2. The data is updated by writing two additional bins: "class" which represents the teacher's input (0-10)// based on class participation, and "grade" which is computed by adding "classwork" to average of test scores,// and using this formula: 50-70 -> C, 65==70-85 -> B, 85+ -> A.
// define the expressions for average test score, total score, grade, min test score, and max test scoreExp avgScoreExp = Exp.Let( Exp.Def("testsBin", Exp.ListBin("tests")), Exp.Div( Exp.Add( ListExp.GetByIndex(ListReturnType.VALUE, Exp.Type.INT, Exp.Val(0), Exp.Var("testsBin")), ListExp.GetByIndex(ListReturnType.VALUE, Exp.Type.INT, Exp.Val(1), Exp.Var("testsBin")), ListExp.GetByIndex(ListReturnType.VALUE, Exp.Type.INT, Exp.Val(2), Exp.Var("testsBin"))), Exp.Val(3)));Expression avgScoreExpression = Exp.Build(avgScoreExp);
Exp totalScoreExp = Exp.Let( Exp.Def("classBin", Exp.IntBin("class")), Exp.Add(avgScoreExp, Exp.Var("classBin")));Expression totalScoreExpression = Exp.Build(totalScoreExp);
Exp gradeExp = Exp.Let( Exp.Def("total", totalScoreExp), Exp.Cond( Exp.LT(Exp.Var("total"), Exp.Val(70)), Exp.Val("C"), Exp.LE(Exp.Var("total"), Exp.Val(85)), Exp.Val("B"), Exp.GT(Exp.Var("total"), Exp.Val(85)), Exp.Val("A"), Exp.Unknown()));Expression gradeExpression = Exp.Build(gradeExp);
Exp minExp = ListExp.GetByRank(ListReturnType.VALUE, Exp.Type.INT, Exp.Val(0), Exp.ListBin("tests")); // minExpression minExpression = Exp.Build(minExp);
Exp maxExp = ListExp.GetByRank(ListReturnType.VALUE, Exp.Type.INT, Exp.Val(-1), Exp.ListBin("tests")); // maxExpression maxExpression = Exp.Build(maxExp);
// update class bin with a random 1-10, and the grade bin using gradeExpressionfor (int i = 1; i <= 20; i++){ Key key = new Key(NAMESPACE, SET_NAME, "id-" + i); int classwork = rand.Next(ScoreRange) / 5; Bin classBin = new Bin("class", classwork);
// write gradeExpression to the bin "grade" Operation gradeExpOp = ExpOperation.Write("grade", gradeExpression, ExpWriteFlags.DEFAULT);
client.Operate(wpolicy, key, Operation.Put(classBin), gradeExpOp);}Console.WriteLine("Updated class participation and computed grades.");
// run a report for all students with id, grade, total, min, max, avg// using total, min, max, and avg expressions defined above
Operation[] ops = { Operation.Get("id"), Operation.Get("grade"), ExpOperation.Read("total", totalScoreExpression, ExpReadFlags.DEFAULT), ExpOperation.Read("testMin", minExpression, ExpReadFlags.DEFAULT), ExpOperation.Read("testMax", maxExpression, ExpReadFlags.DEFAULT), ExpOperation.Read("testAvg", avgScoreExpression, ExpReadFlags.DEFAULT)};
for (int i = 1; i <= 20; i++){ Key key = new Key(NAMESPACE, SET_NAME, "id-" + i); Record record = client.Operate(null, key, ops); Console.WriteLine("key={0} bins={1}", key.userKey, record.ToString().Split("bins:")[1]);}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 lists = Aerospike.lists;const op = Aerospike.operations;
async function main() {
// start with a clean stateawait truncateTestData();
// 1. The test data is populated with three randomly generated test scores ranging from 50 to 100// for student ids 1-20.
const ScoreRange = 50;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 tests = [ 50 + Math.floor(Math.random() * ScoreRange), 50 + Math.floor(Math.random() * ScoreRange), 50 + Math.floor(Math.random() * ScoreRange), ]; await client.put(key, { id: i, tests }, {}, writePolicy);}console.log('Test data populated.');
// 2. The data is updated by writing two additional bins: "class" which represents the teacher's input (0-10)// based on class participation, and "grade" which is computed by adding "classwork" to average of test scores,// and using this formula: 50-70 -> C, 65==70-85 -> B, 85+ -> A.
// define the expressions for average test score, total score, grade, min test score, and max test scoreconst avgScoreExpr = exp.let( ...exp.def('testsBin', exp.binList('tests')), exp.div( exp.add( exp.lists.getByIndex(exp.var('testsBin'), exp.int(0), exp.type.INT, lists.returnType.VALUE), exp.lists.getByIndex(exp.var('testsBin'), exp.int(1), exp.type.INT, lists.returnType.VALUE), exp.lists.getByIndex(exp.var('testsBin'), exp.int(2), exp.type.INT, lists.returnType.VALUE)), exp.int(3)));
const totalScoreExpr = exp.let( ...exp.def('classBin', exp.binInt('class')), exp.add(avgScoreExpr, exp.var('classBin')));
const gradeExpr = exp.let( ...exp.def('total', totalScoreExpr), exp.cond( exp.lt(exp.var('total'), exp.int(70)), exp.str('C'), exp.le(exp.var('total'), exp.int(85)), exp.str('B'), exp.gt(exp.var('total'), exp.int(85)), exp.str('A'), exp.unknown()));
const minExpr = exp.lists.getByRank(exp.binList('tests'), exp.int(0), exp.type.INT, lists.returnType.VALUE); // minconst maxExpr = exp.lists.getByRank(exp.binList('tests'), exp.int(-1), exp.type.INT, lists.returnType.VALUE); // max
// update class bin with a random 1-10, and the grade bin using gradeExprfor (let i = 1; i <= 20; i++) { const key = new Aerospike.Key(NAMESPACE, SET_NAME, `id-${i}`); const classwork = Math.floor((Math.random() * ScoreRange) / 5);
// write gradeExpr to the bin "grade" const gradeExpOp = exp.operations.write('grade', gradeExpr, exp.expWriteFlags.DEFAULT);
await client.operate(key, [op.write('class', classwork), gradeExpOp], {}, writePolicy);}console.log('Updated class participation and computed grades.');
// run a report for all students with id, grade, total, min, max, avg// using total, min, max, and avg expressions defined above
const ops = [ op.read('id'), op.read('grade'), exp.operations.read('total', totalScoreExpr, exp.expReadFlags.DEFAULT), exp.operations.read('testMin', minExpr, exp.expReadFlags.DEFAULT), exp.operations.read('testMax', maxExpr, exp.expReadFlags.DEFAULT), exp.operations.read('testAvg', avgScoreExpr, exp.expReadFlags.DEFAULT),];
for (let i = 1; i <= 20; i++) { const key = new Aerospike.Key(NAMESPACE, SET_NAME, `id-${i}`); const record = await client.operate(key, ops); console.log(`key=${record.key.key} bins=${JSON.stringify(record.bins)}`);}
}main().catch(console.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_index, get_by_rank};use aerospike::expressions::{cond, def, exp_let, gt, int_bin, int_val, le, list_bin, lt, num_add, num_div, string_val, unknown, var, ExpType};use aerospike::operations::exp::{read_exp, write_exp, ExpReadFlags, ExpWriteFlags};use aerospike::{as_bin, as_key, as_list, operations, ListReturnType, WritePolicy};use rand::{rngs::StdRng, Rng, SeedableRng};
// start with a clean statetruncate_test_data(&client).await;
// 1. The test data is populated with three randomly generated test scores ranging from 50 to 100// for student ids 1-20.
let mut rng = StdRng::seed_from_u64(1);const SCORE_RANGE: i64 = 50;let mut wpolicy = WritePolicy::default();wpolicy.send_key = true;for i in 1..=20 { let key = as_key!(NAMESPACE, SET, format!("id-{i}")); let (t0, t1, t2) = ( 50 + rng.gen_range(0..SCORE_RANGE), 50 + rng.gen_range(0..SCORE_RANGE), 50 + rng.gen_range(0..SCORE_RANGE), ); let bins = vec![as_bin!("id", i), as_bin!("tests", as_list!(t0, t1, t2))]; client.put(&wpolicy, &key, &bins).await?;}println!("Test data populated.");
// 2. The data is updated by writing two additional bins: "class" which represents the teacher's input (0-10)// based on class participation, and "grade" which is computed by adding "classwork" to average of test scores,// and using this formula: 50-70 -> C, 65==70-85 -> B, 85+ -> A.
// define the expressions for average test score, total score, grade, min test score, and max test scorelet avg_score_exp = exp_let(vec![ def("testsBin".to_string(), list_bin("tests".to_string())), num_div(vec![ num_add(vec![ get_by_index(ListReturnType::Values, ExpType::INT, int_val(0), var("testsBin".to_string()), &[]), get_by_index(ListReturnType::Values, ExpType::INT, int_val(1), var("testsBin".to_string()), &[]), get_by_index(ListReturnType::Values, ExpType::INT, int_val(2), var("testsBin".to_string()), &[]), ]), int_val(3), ]),]);
let total_score_exp = exp_let(vec![ def("classBin".to_string(), int_bin("class".to_string())), num_add(vec![avg_score_exp.clone(), var("classBin".to_string())]),]);
let grade_exp = exp_let(vec![ def("total".to_string(), total_score_exp.clone()), cond(vec![ lt(var("total".to_string()), int_val(70)), string_val("C".to_string()), le(var("total".to_string()), int_val(85)), string_val("B".to_string()), gt(var("total".to_string()), int_val(85)), string_val("A".to_string()), unknown(), ]),]);
let min_exp = get_by_rank(ListReturnType::Values, ExpType::INT, int_val(0), list_bin("tests".to_string()), &[]); // minlet max_exp = get_by_rank(ListReturnType::Values, ExpType::INT, int_val(-1), list_bin("tests".to_string()), &[]); // max
// update class bin with a random 1-10, and the grade bin using grade_expfor i in 1..=20 { let key = as_key!(NAMESPACE, SET, format!("id-{i}")); let classwork = rng.gen_range(0..SCORE_RANGE) / 5; let class_bin = as_bin!("class", classwork);
// write grade_exp to the bin "grade" let ops = [ operations::scalar::put(&class_bin), write_exp("grade", grade_exp.clone(), ExpWriteFlags::Default), ]; client.operate(&wpolicy, &key, &ops).await?;}println!("Updated class participation and computed grades.");
// run a report for all students with id, grade, total, min, max, avg// using total, min, max, and avg expressions defined above
let ops = [ operations::scalar::get_bin("id"), operations::scalar::get_bin("grade"), read_exp("total", total_score_exp.clone(), ExpReadFlags::Default), read_exp("testMin", min_exp.clone(), ExpReadFlags::Default), read_exp("testMax", max_exp.clone(), ExpReadFlags::Default), read_exp("testAvg", avg_score_exp.clone(), ExpReadFlags::Default),];
for i in 1..=20 { let key = as_key!(NAMESPACE, SET, format!("id-{i}")); let record = client.operate(&WritePolicy::default(), &key, &ops).await?; println!("key={:?} bins={:?}", key.user_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 expresses the same computation as
AEL text, using
let ... then to bind the three test scores, and when in place of
cond for the grade lookup. The average score (testAvg) is:
let (t0 = $.tests.[0], t1 = $.tests.[1], t2 = $.tests.[2])then ((${t0} + ${t1} + ${t2}) / 3)The total score (testAvg plus class) extends that same binding:
let (t0 = $.tests.[0], t1 = $.tests.[1], t2 = $.tests.[2], avg = ((${t0} + ${t1} + ${t2}) / 3), classBin = $.class)then (${avg} + ${classBin})The “grade” write expression reuses total inside a when:
let (t0 = $.tests.[0], t1 = $.tests.[1], t2 = $.tests.[2], avg = ((${t0} + ${t1} + ${t2}) / 3), classBin = $.class, total = ${avg} + ${classBin})then ( when ( ${total} < 70 => 'C', ${total} <= 85 => 'B', ${total} > 85 => 'A', default => unknown))The min and max test scores use the same rank selectors as
ListExp.getByRank above: $.tests.[#0]:INT (smallest) and
$.tests.[#-1]:INT (largest). The :INT pin is required on the tail of
a selectFrom target because a read has to resolve its own result type,
and an unpinned rank selector gives the compiler nothing to infer from.
Writing the grade and reading the report with the Developer SDK:
DataSet students = DataSet.of(NAMESPACE, SET_NAME);final String TESTS = "t0 = $.tests.[0], t1 = $.tests.[1], t2 = $.tests.[2]";final String SUM = "(${t0} + ${t1} + ${t2})";final String AVG = "(" + SUM + " / 3)";
// Individual expressions (each is a complete AEL string)final String avg = "let (" + TESTS + ") then " + AVG;final String total = "let (" + TESTS + ", avg = " + AVG + ", classBin = $.class) " + "then (${avg} + ${classBin})";final String grade = "let (" + TESTS + ", avg = " + AVG + ", classBin = $.class, total = ${avg} + ${classBin}) " + "then (when (${total} < 70 => 'C', ${total} <= 85 => 'B', ${total} > 85 => 'A', default => unknown))";
session.upsert(students.id(String.valueOf(i))) .bin("grade").upsertFrom(grade) .execute();
Record record = session.query(students.id(String.valueOf(i))) .bin("total").selectFrom(total) .bin("testMin").selectFrom("$.tests.[#0]:INT") .bin("testMax").selectFrom("$.tests.[#-1]:INT") .bin("testAvg").selectFrom(avg) .execute() .getFirstRecord();students = DataSet.of(NAMESPACE, SET_NAME)avg = ("let (t0 = $.tests.[0], t1 = $.tests.[1], t2 = $.tests.[2]) " "then (${t0} + ${t1} + ${t2}) / 3")total = ("let (t0 = $.tests.[0], t1 = $.tests.[1], t2 = $.tests.[2], " "avg = (${t0} + ${t1} + ${t2}) / 3, classBin = $.class) " "then (${avg} + ${classBin})")grade = ("let (t0 = $.tests.[0], t1 = $.tests.[1], t2 = $.tests.[2], " "avg = (${t0} + ${t1} + ${t2}) / 3, classBin = $.class, " "total = ${avg} + ${classBin}) " "then (when (${total} < 70 => 'C', ${total} <= 85 => 'B', " "${total} > 85 => 'A', default => unknown))")
await ( session.upsert(students.id(str(i))) .bin("grade").upsert_from(grade) .execute())
stream = await ( session.query(students.id(str(i))) .bin("total").select_from(total) .bin("testMin").select_from("$.tests.[#0]:INT") .bin("testMax").select_from("$.tests.[#-1]:INT") .bin("testAvg").select_from(avg) .execute())record = await stream.first_or_raise()Next
Continue to Usage notes and next steps.