JSONiq API
SirixDB uses JSONiq — the JSON query language — as its primary API for storing, querying, and updating JSON data. JSONiq extends XQuery with native JSON support, making it ideal for working with SirixDB’s temporal document store.
For a complete list of all available functions, see the Function Reference.
Setup
Add the SirixDB query module to your project.
Maven:
<dependency>
<groupId>io.sirix</groupId>
<artifactId>sirix-query</artifactId>
<version>0.10.1-SNAPSHOT</version>
</dependency>
Gradle:
dependencies {
implementation 'io.sirix:sirix-query:0.10.1-SNAPSHOT'
}
For snapshot versions, add the Sonatype snapshot repository:
<repository>
<id>central-snapshots</id>
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
<snapshots><enabled>true</enabled></snapshots>
</repository>
Requires Java 25 and the provided Gradle wrapper.
Running Queries from Java
All JSONiq queries run through the Query class with a JSON-specific store and context:
try (final var store = BasicJsonDBStore.newBuilder().build();
final var ctx = SirixQueryContext.createWithJsonStore(store);
final var chain = SirixCompileChain.createWithJsonStore(store)) {
// Store JSON data
new Query(chain, "jn:store('mydb', 'products', '[{\"name\":\"Laptop\",\"price\":999}]')")
.evaluate(ctx);
// Query and print results
final var query = new Query(chain, "jn:doc('mydb', 'products')[0].name");
query.serialize(ctx, System.out);
// => "Laptop"
}
Storing Data
Use jn:store to create a database and store JSON data. The database is created automatically if it doesn’t exist.
(: Store a JSON array as a named resource :)
jn:store('shop', 'products', '[{"name":"Laptop","price":999},{"name":"Phone","price":699}]')
(: Store from a file :)
jn:load('shop', 'inventory', '/path/to/data.json')
(: Add another resource to an existing database :)
jn:store('shop', 'customers', '[{"name":"Alice"}]', false())
Opening and Querying Data
Open a resource with jn:doc and navigate using JSONiq’s dot notation and array indexing:
(: Open the most recent revision :)
let $doc := jn:doc('shop', 'products')
return $doc[0].name
(: => "Laptop" :)
(: Open a specific revision :)
jn:doc('shop', 'products', 1)
(: Open at a point in time :)
jn:open('shop', 'products', xs:dateTime('2024-06-15T10:00:00Z'))
(: Query all resources in a database :)
for $doc in jn:collection('shop')
return $doc
Array elements are accessed with [] using zero-based indexing. Object fields are accessed with the . (deref) operator:
let $doc := jn:doc('shop', 'products')
return {
"first-product": $doc[0].name,
"second-price": $doc[1].price,
"count": jn:size($doc)
}
Updating Data
SirixDB supports all JSONiq update expressions. Each update automatically creates a new revision.
(: Replace a value :)
let $doc := jn:doc('shop', 'products')
return replace json value of $doc[0].price with 899
(: Append to an array :)
let $doc := jn:doc('shop', 'products')
return append json {"name":"Tablet","price":449} into $doc
(: Insert a field into an object :)
let $doc := jn:doc('shop', 'products')
return insert json {"stock": 50} into $doc[0]
(: Delete an array element :)
let $doc := jn:doc('shop', 'products')
return delete json $doc[1]
(: Rename a field :)
let $doc := jn:doc('shop', 'products')
return rename json $doc[0].price as "cost"
(: Insert at a specific array position :)
let $arr := jn:doc('shop', 'products')
return insert json {"name":"Monitor","price":349} into $arr at position 1
You can also commit with a message or rollback changes:
let $doc := jn:doc('shop', 'products')
return (
replace json value of $doc[0].price with 799,
sdb:commit($doc, "Summer sale pricing")
)
Temporal Queries
SirixDB’s key feature is temporal querying — every update creates an immutable revision, and you can navigate the full history.
Navigating Revisions
(: See a document across all revisions :)
for $v in jn:all-times(jn:doc('shop', 'products'))
return {"rev": sdb:revision($v), "data": $v}
(: Step forward and backward :)
let $doc := jn:doc('shop', 'products')
return {
"current-rev": sdb:revision($doc),
"previous-rev": sdb:revision(jn:previous($doc)),
"first-rev": sdb:revision(jn:first($doc))
}
(: Get all future versions from revision 2 :)
for $v in jn:future(jn:doc('shop', 'products', 2), true())
return sdb:revision($v)
Tracking Individual Nodes
Every node in SirixDB has a stable, unique key. You can track how individual values change across revisions:
(: Find a node's key :)
let $doc := jn:doc('shop', 'products')
return sdb:nodekey($doc[0].price)
(: Track a specific node's history :)
let $item := sdb:select-item(jn:doc('shop', 'products'), 6)
for $v in sdb:item-history($item)
return {"rev": sdb:revision($v), "value": $v}
Comparing Revisions
Use jn:diff to compute structural differences between any two revisions:
jn:diff('shop', 'products', 1, 4)
(: Returns a JSON object describing inserts, deletes, updates, and replacements :)
Bitemporal Queries
For resources configured with valid-time support, query along two time axes — system time (when recorded) and valid time (when true in the real world):
(: What did we know on March 5 about data valid on February 1? :)
jn:open-bitemporal('customers', 'addresses',
xs:dateTime('2024-02-01T00:00:00Z'),
xs:dateTime('2024-03-05T00:00:00Z'))
Node Metadata
Inspect metadata about any node using sdb: functions:
let $doc := jn:doc('shop', 'products')
return {
"revision": sdb:revision($doc),
"timestamp": sdb:timestamp($doc),
"children": sdb:child-count($doc),
"descendants": sdb:descendant-count($doc),
"nodekey": sdb:nodekey($doc),
"hash": sdb:hash($doc),
"path": sdb:path($doc[0].name)
}
Indexes
SirixDB supports three index types for faster lookups: name (field names), path (document paths), and CAS (content-and-structure, for typed value queries).
(: Create a CAS index on the "price" path for numeric lookups :)
let $doc := jn:doc('shop', 'products')
return jn:create-cas-index($doc, 'xs:decimal', '/[]/price')
(: Query the index for prices between 0 and 500 :)
let $doc := jn:doc('shop', 'products')
let $idx := jn:find-cas-index($doc, 'xs:decimal', '/[]/price')
return jn:scan-cas-index-range($doc, $idx, 0, 500, true(), true(), 'xs:decimal')
(: Create a name index on specific fields :)
let $doc := jn:doc('shop', 'products')
return jn:create-name-index($doc, ('name', 'price'))
(: Create a path index on all paths :)
let $doc := jn:doc('shop', 'products')
return jn:create-path-index($doc)
See the Function Reference for the complete index API.
JSONiq Language Basics
JSONiq extends XQuery with native JSON support. Here are the key language features.
Arrays
(: Create arrays :)
let $a := [1, 2.0, "three", true, false, jn:null()]
return $a[0]
(: => 1 :)
(: Distribute sequence items into array positions with '=' :)
[=(1 to 5)]
(: => [1, 2, 3, 4, 5] :)
(: Without '=', the sequence stays as one element :)
[(1 to 5)]
(: => [(1,2,3,4,5)] :)
(: Get array length :)
bit:len([1, 2, 3])
(: => 3 :)
Objects
(: Create objects :)
let $obj := {"name": "Alice", "age": 30, "scores": [95, 87, 92]}
return $obj.name
(: => "Alice" :)
(: Merge objects :)
let $base := {"x": 1, "y": 2}
return {$base, "z": 3}
(: => {"x": 1, "y": 2, "z": 3} :)
(: Project specific fields :)
{"x": 1, "y": 2, "z": 3}{x, z}
(: => {"x": 1, "z": 3} :)
(: Inspect object structure :)
let $obj := {"a": 1, "b": 2}
return bit:fields($obj)
(: => ["a", "b"] :)
Parsing JSON
let $s := io:read('/data/sample.json')
return jn:parse($s)