Quickstart: path expressions
This page contains a quickstart guide for path expressions.
Path expressions enable granular indexing and querying from a nested map or list collection data type (CDT) using expressions.
Goals of the quickstart
The goal of the quickstart is to show you how to use path expressions to select elements from a document modeled in Aerospike as nested map and list structures. By the end, you’ll know how to filter products and their variants server-side and return only the relevant parts of the record, without denormalizing data or writing client-side processing in your application.
Data structure
For the purposes of this quickstart guide, we’ll use a simplified
ecommerce product catalog stored as a single record in Aerospike.
Products are modeled as a map and organized under a single inventory bin, keyed by product IDs
(e.g. 10000001, 50000009).
Each product record includes:
-
category: product category (e.g. clothing, electronics) -
featured: a boolean flag for merchandising purposes. -
nameanddescription: product metadata. -
variants: either a map of SKUs to attributes (size/spec, price, quantity) or a list of variant objects.
This structure is similar to real-world product catalogs, in which a product may have multiple variants (sizes, colors, configurations), each with its own availability.
For this example app, the goal is to identify products to promote on the home page of an ecommerce website. We only want to select products that are featured and are in stock (have an quantity greater than 0).
Prerequisites
-
An Aerospike client library which supports path expressions, compatible with Aerospike Database 8.1.1 or later. Check your preferred client library for compatibility.
-
The Docker containerization service.
-
(Optional) The Aerospike Tools package for monitoring and querying your database instance.
Project procedure
-
Download the demo app.
Use the following command to clone the app from GitHub:
Terminal window git clone https://github.com/aerospike-examples/path-expressions-java-preview.git -
Start the Aerospike server in Docker.
Terminal window cd path-expressions-java-previewdocker compose -f container/docker-compose.yaml up -dVerify that the Aerospike server is running with the
asadmcommand:Terminal window asadmExpected output similar to:
Seed: [('127.0.0.1', 3000, None)]Config_file: /Users/dbuser/.aerospike/astools.conf, /etc/aerospike/astools.confAerospike Interactive Shell, version 2.12.0Found 1 nodesOnline: 127.0.0.1:3000Admin>Exit
asadmwith the commandexit. -
Build the application with Maven.
Back at the shell command prompt, run the following command:
Terminal window mvn clean install exec:java -Dexec.mainClass="com.aerospike.pathexpressions.PathExpressionsDemo"To use the Aerospike Java Client preview release, add the Aerospike Preview Repository to your build tool configuration and include the desired dependency.
-
(Optional) Check your data with AQL.
AQL is part of the Aerospike Tools package. It is a command-line tool for browsing data in an Aerospike database.
To check your current data set with AQL, run the
aqlcommand and enter the following command at the AQL prompt:select inventory from test.products where PK='catalog' -
Explore the data set.
The sample data set is named
inventory_sample.jsonand is located in thedatadirectory. It contains the following items:{"inventory": {"10000001": {"category": "clothing","featured": true,"name": "Classic T-Shirt","description": "A lightweight cotton T-shirt perfect for everyday wear.","variants": {"2001": { "size": "S", "price": 25, "quantity": 100 },"2002": { "size": "M", "price": 25, "quantity": 0 },"2003": { "size": "L", "price": 27, "quantity": 50 }}},"10000002": {"category": "clothing","featured": false,"name": "Casual Polo Shirt","description": "A soft polo shirt suitable for work or leisure.","variants": {"2004": { "size": "M", "price": 30, "quantity": 20 },"2005": { "size": "XL", "price": 32, "quantity": 10 }}},"50000006": {"category": "electronics","featured": true,"name": "Laptop Pro 14","description": "High-performance laptop designed for professionals.","variants": {"3001": { "spec": "8GB RAM", "price": 599, "quantity": 0 }}},"50000009": {"category": "electronics","featured": true,"name": "Smart TV","description": "Ultra HD smart television with built-in streaming apps.","variants": [{ "sku": 3007, "spec": "1080p", "price": 199, "quantity": 60 },{ "sku": 3008, "spec": "4K", "price": 399, "quantity": 30 }]}}} -
Define filters as expressions.
The sample app filters at two levels of the data:
-
Product level: only include products where
featured = true. -
Variant level: only include variants where
quantity > 0.
Context stack: how filters apply
When selecting elements with path expressions, the server evaluates filters at different depths. This example uses the following stack:
inventory (bin)└── product (map entry, keyed by productId)├── category├── featured <-- product-level filter├── name├── description└── variants├── { "2001": {...}, "2002": {...} } <-- map-backed variants└── [ {sku:3007,...}, {sku:3008,...} ] <-- list-backed variants^ variant-level filterAt the product level, we filter by the featured field inside each product map.
At the variant level, we filter by the quantity field inside either:
-
a map-backed variant (keyed by SKU), or
-
a list-backed variant (array of objects).
// Product-level filter: featured == trueExp filterOnFeatured = Exp.eq(MapExp.getByKey(MapReturnType.VALUE, Type.BOOL,Exp.val("featured"),Exp.mapLoopVar(LoopVarPart.VALUE) // the value of the element in this iteration is extracted into a loop variable whose data type is a map),Exp.val(true));// Variant-level filter: quantity > 0Exp filterOnVariantInventory = Exp.gt(MapExp.getByKey(MapReturnType.VALUE, Type.INT,Exp.val("quantity"),Exp.mapLoopVar(LoopVarPart.VALUE) // the value of the element in this iteration is extracted into a loop variable whose data type is a map),Exp.val(0));# setupfrom aerospike import MAP_RETURN_VALUEfrom aerospike_helpers import expressions as exp, cdt_ctxfrom aerospike_helpers.operations import expression_operations, map_operations, operations# Product-level filter: featured == truefilter_on_featured = exp.Eq(exp.MapGetByKey(ctx=None,return_type=MAP_RETURN_VALUE,value_type=exp.ResultType.BOOLEAN,key=exp.Val("featured"),bin=exp.LoopVarMap(aerospike.EXP_LOOPVAR_VALUE)),exp.Val(True)).compile()# Variant-level filter: quantity > 0filter_on_variant_inventory = exp.GT(exp.MapGetByKey(ctx=None,return_type=MAP_RETURN_VALUE,value_type=exp.ResultType.INTEGER,key=exp.Val("quantity"),bin=exp.LoopVarMap(aerospike.EXP_LOOPVAR_VALUE)),exp.Val(0)).compile()// Product-level: featured == truefilterOnFeatured := aero.ExpMapGetByKey(aero.MapReturnType.VALUE, aero.ExpTypeINT, // Note: Type mapping may need adjustmentaero.ExpStringVal("featured"),aero.ExpMapLoopVar(aero.ExpLoopVarPart.VALUE),)// Variant-level: inventory > 0filterOnVariantInventory := aero.ExpGreater(aero.ExpMapGetByKey(aero.MapReturnType.VALUE, aero.ExpTypeINT,aero.ExpStringVal("quantity"),aero.ExpMapLoopVar(aero.ExpLoopVarPart.VALUE),),aero.ExpIntVal(0),)// Product-level filter: featured == trueas_exp_build(filter_on_featured,as_exp_cmp_eq(as_exp_map_get_by_key(NULL, AS_MAP_RETURN_VALUE, AS_EXP_TYPE_BOOL,as_exp_str("featured"),as_exp_loopvar_map(AS_EXP_LOOPVAR_VALUE) // loop variable points to each product map),as_exp_bool(true)));if (!filter_on_featured) {goto fail_filter_on_featured;}// Variant-level filter: quantity > 0as_exp_build(filter_on_variant_inventory,as_exp_cmp_gt(as_exp_map_get_by_key(NULL, AS_MAP_RETURN_VALUE, AS_EXP_TYPE_INT,as_exp_str("quantity"),as_exp_loopvar_map(AS_EXP_LOOPVAR_VALUE) // loop variable points to each variant object),as_exp_int(0)));if (!filter_on_variant_inventory) {goto fail_filter_on_variant_inventory;}// Product-level: featured == trueExp filterOnFeatured = Exp.EQ(MapExp.GetByKey(MapReturnType.VALUE, Exp.Type.BOOL,Exp.Val("featured"),Exp.MapLoopVar(LoopVarPart.VALUE) // the value of the element in this iteration is extracted into a loop variable whose data type is a map),Exp.Val(true));// Variant-level: quantity > 0Exp filterOnVariantInventory = Exp.GT(MapExp.GetByKey(MapReturnType.VALUE, Exp.Type.INT,Exp.Val("quantity"),Exp.MapLoopVar(LoopVarPart.VALUE) // the value of the element in this iteration is extracted into a loop variable whose data type is a map),Exp.Val(0)); -
-
Run the path expression.
Combine the filters with traversal contexts:
// OperationRecord record = client.operate(null, key,CdtOperation.selectByPath("inventory", Exp.SELECT_MATCHING_TREE,CTX.allChildren(), // dive into variantsCTX.allChildrenWithFilter(filterOnFeatured), // only featured productsCTX.mapKey(Value.get("variants")), // only 'variants' instancesCTX.allChildrenWithFilter(filterOnVariantInventory)) // only in-stock);# Operationops = [expression_operations.path_expression("inventory", aerospike.EXP_PATH_SELECT_MATCHING_TREE,cdt_ctx.all_children_with_filter(filter_on_featured)), # only featured productscdt_ctx.all_children(), # dive into variantscdt_ctx.all_children_with_filter(filter_on_variant_inventory)] # only in-stock variants(key, meta, bins) = client.operate(key, ops)// Operation with path expressionrecord, err := cli.Operate(nil, key,aero.PathExpressionOp("inventory", aero.ExpSelectFlagsMATCHING_TREE,aero.CtxAllChildrenWithFilter(filterOnFeatured), // only featured productsaero.CtxAllChildren(), // dive into variantsaero.CtxAllChildrenWithFilter(filterOnVariantInventory), // only variants with stock),)// Operation// Path: inventory map -> filter featured products -> variants map -> filter quantity > 0as_cdt_ctx ctx;as_cdt_ctx_inita(&ctx, 4);as_cdt_ctx_add_map_key(&ctx, (as_val*)as_string_new((char*)"inventory", false));as_cdt_ctx_add_all_children_with_filter(&ctx, filter_on_featured);as_cdt_ctx_add_map_key(&ctx, (as_val*)as_string_new((char*)"variants", false));as_cdt_ctx_add_all_children_with_filter(&ctx, filter_on_variant_inventory);as_operations ops;as_operations_inita(&ops, 1);status = as_operations_select_by_path(&err, &ops, "testbin", &ctx,AS_EXP_PATH_SELECT_MATCHING_TREE | AS_EXP_PATH_SELECT_NO_FAIL);if (status != AEROSPIKE_OK) {goto fail_select_by_path;}as_record* rec = NULL;status = aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec);if (status != AEROSPIKE_OK) {goto fail_key_operate;}Record record = client.Operate(null, key,CDTOperation.SelectByPath(inventoryBinName, SelectFlag.MATCHING_TREE,CTX.AllChildren(), // dive into all productsCTX.AllChildrenWithFilter(filterOnFeatured), // only featured productsCTX.MapKey(Value.Get("variants")), // dive into variantsCTX.AllChildrenWithFilter(filterOnVariantInventory) // only in-stock variants)); -
Observe the result.
Print the returned dataset and notice that the server only returns products with
featured = trueand at least one variant in stock:System.out.println(record.getMap("inventory"));print(bins["inventory"])fmt.Println(record.Bins["inventory"])// Print in JSON formatas_map* map = as_record_get_map(rec, "testbin");if (map) {char* map_str = as_val_tostring((as_val*)map);log("%s\n", map_str);free(map_str);} else {log("No data returned\n");}Console.WriteLine(System.Text.Json.JsonSerializer.Serialize((Dictionary<object, object>)record.GetMap("inventory"), new System.Text.Json.JsonSerializerOptions { WriteIndented = true }));Expected output:
{"inventory" : {"10000001" : {"variants" : {"2001" : {"size" : "S","price" : 25,"quantity" : 100},"2003" : {"size" : "L","price" : 27,"quantity" : 50}}},"50000009" : {"variants" : [ {"quantity" : 60,"sku" : 3007,"price" : 199,"spec" : "1080p"}, {"quantity" : 30,"sku" : 3008,"price" : 399,"spec" : "4K"} ]},"50000006" : {"variants" : { }}}}✅ Item
50000009keeps both variants.✅ Item
10000001, Classic T-Shirt, keeps variant items2001and2003(both havequantity > 0).❌ Item
10000002, Casual Polo Shirt, excluded (featured = false).❌ Variant
2002excluded from item10000001(quantity = 0).❌ Item
50000006excluded, variant3001(quantity = 0).
Feature summary
With path expressions you can:
-
iterate over all the elements of a nested map or list collection data type, and use expressions at each iteration to filter out mismatches at each layer.
-
Retrieve only relevant subtrees (products + in-stock variants).
-
Avoid denormalization and client-side filtering.
-
Build faster, cleaner APIs for real-world use cases like product catalogs.
Next steps
Explore additional code examples and use cases on the Advanced usage page.