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.
Prerequisites
-
An Aerospike Database 8.1.2 or later. See the database quickstart.
-
A fully compatible version of an Aerospike client library for your preferred programming language.
-
(Optional) The Aerospike Tools package for monitoring and querying your database instance.
Code Demo
All the Java code mentioned in our path expressions documentation is available in GitHub. You can clone, explore, run and modify it, or just read along.
-
Verify the server version.
Verify that the Aerospike server is running with the
asadmcommand:Terminal window # connect to your Aerospike Database server on the port it is listening onasadm -p 3000Expected 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> infoThe Build should be version >= 8.1.2.0
Exit
asadmwith the commandexit. -
Download the demo code.
Use the following command to clone the app from GitHub:
Terminal window git clone https://github.com/aerospike-examples/path-expressions-java-preview.gitcd path-expressions-java-previewThere are four folders:
- bookstore/ has the code used in the overview page
- catalog/ has the code used in this quickstart page and advanced usage
- booking/ has the code used in the performance page
- misc/ has miscellaneous code examples for path expression use, such as chaining
mapKeysIn/andFilterpairs
-
Build the application with Maven.
Back at the shell command prompt, go into the folder you want to use and run the following command:
Terminal window mvn compile exec:java
Catalog example
This example uses a simplified ecommerce product catalog stored as a
single record in Aerospike. Products are modeled as a map and organized
under a single catalog map bin, keyed by product IDs
(e.g. 10000001, 50000009).
{ "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 } ] } }}Filtering dimensions
catalog (bin) └── inventory (map of 4 maps) └── <product ID> ├── category ├── featured <-- product-level filter ├── name ├── description └── variants ├── { "2001": {...}, "2002": {...} } <-- map-backed variants └── [ {sku:3007,...}, {sku:3008,...} ] <-- list-backed variants ^ variant-level filter- Inventory level: select
inventory(map key navigation) - Product level: keyed by the product ID, filter on the
featuredfield inside each product map - Variant level: filter by the
quantityfield inside either:- a map-backed variant (keyed by SKU), or
- a list-backed variant (list of map structs)
Featured products with in-stock inventory
Query the catalog example above, using two filter expressions and combine them with traversal contexts to select featured products with in-stock variants:
// Product-level filter: featured == trueExp filterOnFeatured = Exp.eq( MapExp.getByKey( MapReturnType.VALUE, Type.BOOL, Exp.val("featured"), Exp.mapLoopVar(LoopVarPart.VALUE) // during path resolution the value of the current element is assigned to a map-typed loop variable ), Exp.val(true));
// Variant-level filter: quantity > 0Exp filterOnVariantInventory = Exp.gt( MapExp.getByKey( MapReturnType.VALUE, Type.INT, Exp.val("quantity"), Exp.mapLoopVar(LoopVarPart.VALUE) // during path resolution the value of the current element is assigned to a map-typed loop variable ), Exp.val(0));
// Path expression: featured products with in-stock variantsRecord record = client.operate(null, key, CdtOperation.selectByPath("catalog", SelectFlags.MATCHING_TREE, CTX.allChildren(), // dive into all products CTX.allChildrenWithFilter(filterOnFeatured), // only featured products CTX.mapKey(Value.get("variants")), // descend into variants CTX.allChildrenWithFilter(filterOnVariantInventory)) // only in-stock);import aerospikefrom 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()
# Path expression: featured products with in-stock variantsctx = [ cdt_ctx.cdt_ctx_all_children(), # dive into all products cdt_ctx.cdt_ctx_all_children_with_filter(filter_on_featured), # only featured products cdt_ctx.cdt_ctx_map_key('variants'), # descend into variants cdt_ctx.cdt_ctx_all_children_with_filter(filter_on_variant_inventory) # only in-stock]
ops = [ operations.select_by_path( bin_name="catalog", ctx=ctx, flags=aerospike.EXP_PATH_SELECT_MATCHING_TREE )]
config = { "hosts": [ ("127.0.0.1", 3000) ]}
client = aerospike.client(config)key = ("test", "products", "catalog")_, _, bins = client.operate(key, ops)client.close()// Product-level filter: featured == truefilterOnFeatured := aero.ExpEq( aero.ExpMapGetByKey( aero.MapReturnType.VALUE, aero.ExpTypeBOOL, aero.ExpStringVal("featured"), aero.ExpMapLoopVar(aero.VALUE), ), aero.ExpBoolVal(true),)
// Variant-level filter: quantity > 0filterOnVariantInventory := aero.ExpGreater( aero.ExpMapGetByKey( aero.MapReturnType.VALUE, aero.ExpTypeINT, aero.ExpStringVal("quantity"), aero.ExpMapLoopVar(aero.VALUE), ), aero.ExpIntVal(0),)
// Path expression: featured products with in-stock variantsrecord, err := client.Operate(nil, key, aero.SelectByPath("catalog", aero.EXP_PATH_SELECT_MATCHING_TREE, aero.CtxAllChildren(), // dive into all products aero.CtxAllChildrenWithFilter(filterOnFeatured), // only featured products aero.CtxMapKey(aero.NewValue("variants")), // descend into variants aero.CtxAllChildrenWithFilter(filterOnVariantInventory), // only in-stock ),)// Product-level filter: featured == true
as_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 > 0
as_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;}
// Path expression: featured products with in-stock variants
as_cdt_ctx ctx;as_cdt_ctx_inita(&ctx, 4);as_cdt_ctx_add_all_children(&ctx); // dive into all productsas_cdt_ctx_add_all_children_with_filter(&ctx, filter_on_featured); // only featured productsas_cdt_ctx_add_map_key(&ctx, (as_val*)as_string_new("variants", false)); // descend into variantsas_cdt_ctx_add_all_children_with_filter(&ctx, filter_on_variant_inventory); // only in-stock
as_operations ops;as_operations_inita(&ops, 1);status = as_operations_select_by_path(&err, &ops, "catalog", &ctx, AS_EXP_PATH_SELECT_MATCHING_TREE);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;}// Product-level: featured == trueExp filterOnFeatured = Exp.EQ( MapExp.GetByKey( MapReturnType.VALUE, Exp.Type.BOOL, Exp.Val("featured"), Exp.MapLoopVar(LoopVarPart.VALUE) // during path resolution the value of the current element is assigned to a map-typed loop variable ), Exp.Val(true));
// Variant-level: quantity > 0Exp filterOnVariantInventory = Exp.GT( MapExp.GetByKey( MapReturnType.VALUE, Exp.Type.INT, Exp.Val("quantity"), Exp.MapLoopVar(LoopVarPart.VALUE) // during path resolution the value of the current element is assigned to a map-typed loop variable ), Exp.Val(0));
// Path expression: featured products with in-stock variantsRecord record = client.Operate(null, key, CDTOperation.SelectByPath("catalog", SelectFlag.MATCHING_TREE, CTX.AllChildren(), // dive into all products CTX.AllChildrenWithFilter(filterOnFeatured), // only featured products CTX.MapKey(Value.Get("variants")), // descend into variants CTX.AllChildrenWithFilter(filterOnVariantInventory) // only in-stock variants ));// Product-level: featured == trueconst filterOnFeatured = exp.eq( exp.maps.getByKey( exp.loopVarMap(exp.loopVarPart.VALUE), // during path resolution the value of the current element is assigned to a map-typed loop variable exp.str('featured'), exp.type.BOOL, maps.returnType.VALUE ), exp.bool(true))
// Variant-level: quantity > 0const filterOnVariantInventory = exp.gt( exp.maps.getByKey( exp.loopVarMap(exp.loopVarPart.VALUE), // during path resolution the value of the current element is assigned to a map-typed loop variable exp.str('quantity'), exp.type.INT, maps.returnType.VALUE ), exp.int(0))
// Path expression: featured products with in-stock variantsconst context = new Context()
context.addAllChildren() // dive into all productscontext.addAllChildrenWithFilter(filterOnFeatured) // only featured productscontext.addMapKey('variants') // descend into variantscontext.addAllChildrenWithFilter(filterOnVariantInventory) // only in-stock
const ops = [ op.selectByPath("catalog", exp.pathSelectFlags.MATCHING_TREE, context)]
const result = await client.operate(key, ops)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:
-
traverse all elements of a nested map or list collection data type, and use filter expressions to match and select elements 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.