@jarenjs/json 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +175 -0
- package/LICENSE +21 -0
- package/README.md +471 -0
- package/dist/types/basic.d.ts +32 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/jslt/dispatch.d.ts +11 -0
- package/dist/types/jslt/errors.d.ts +18 -0
- package/dist/types/jslt/index.d.ts +53 -0
- package/dist/types/jslt/stylesheet.d.ts +8 -0
- package/dist/types/jtlt/desugar.d.ts +19 -0
- package/dist/types/jtlt/errors.d.ts +18 -0
- package/dist/types/jtlt/index.d.ts +57 -0
- package/dist/types/jtlt/template.d.ts +8 -0
- package/dist/types/jtlt/writer.d.ts +6 -0
- package/dist/types/path.d.ts +235 -0
- package/dist/types/pointer.d.ts +114 -0
- package/dist/types/query/compile.d.ts +21 -0
- package/dist/types/query/errors.d.ts +18 -0
- package/dist/types/query/index.d.ts +70 -0
- package/dist/types/query/normalize.d.ts +68 -0
- package/dist/types/query/operators.d.ts +424 -0
- package/dist/types/query/runtime.d.ts +93 -0
- package/dist/types/segments.d.ts +62 -0
- package/dist/types/xquery/index.d.ts +19 -0
- package/dist/types/xquery/parse.d.ts +20 -0
- package/docs/JSLT-FORMAT.md +861 -0
- package/docs/JSLT-PRELUDE.md +159 -0
- package/docs/JTLT-FORMAT.md +659 -0
- package/docs/QUERY-FORMAT.md +1221 -0
- package/docs/XQUERY-FRONTEND.md +321 -0
- package/package.json +81 -0
- package/schemas/jaren-jslt.draft-07.schema.json +776 -0
- package/schemas/jaren-jslt.schema.json +776 -0
- package/schemas/jaren-query.draft-07.schema.json +613 -0
- package/schemas/jaren-query.schema.json +375 -0
- package/src/basic.js +300 -0
- package/src/index.js +4 -0
- package/src/jslt/dispatch.js +934 -0
- package/src/jslt/errors.js +34 -0
- package/src/jslt/index.js +121 -0
- package/src/jslt/stylesheet.js +234 -0
- package/src/jtlt/desugar.js +231 -0
- package/src/jtlt/errors.js +34 -0
- package/src/jtlt/index.js +155 -0
- package/src/jtlt/template.js +130 -0
- package/src/jtlt/writer.js +110 -0
- package/src/path.js +977 -0
- package/src/pointer.js +453 -0
- package/src/query/compile.js +817 -0
- package/src/query/errors.js +33 -0
- package/src/query/index.js +150 -0
- package/src/query/normalize.js +1047 -0
- package/src/query/operators.js +1253 -0
- package/src/query/runtime.js +233 -0
- package/src/segments.js +627 -0
- package/src/xquery/index.js +35 -0
- package/src/xquery/parse.js +1647 -0
|
@@ -0,0 +1,1221 @@
|
|
|
1
|
+
# The Jaren JSON Query Format
|
|
2
|
+
|
|
3
|
+
**Version 0.1 — Specification**
|
|
4
|
+
|
|
5
|
+
Module: `@jarenjs/json/query`. This document is the language contract; the
|
|
6
|
+
package [README](../README.md) carries the guided tour and the engine's
|
|
7
|
+
internals are described in [ARCHITECTURE](../ARCHITECTURE.md).
|
|
8
|
+
|
|
9
|
+
> **Naming note (non-normative).** "Jaren JSON Query format" is a working name
|
|
10
|
+
> and is used consistently throughout this document. The obvious short name
|
|
11
|
+
> "JSON Query" collides with the independent jsonquery.org project; final
|
|
12
|
+
> naming/branding is an open question and is deliberately not settled here.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 1. Introduction
|
|
17
|
+
|
|
18
|
+
### 1.1 What this language is
|
|
19
|
+
|
|
20
|
+
The Jaren JSON Query format is a declarative query-and-transformation language
|
|
21
|
+
for JSON documents. Its semantics are those of **XQuery 3.1** — sequences,
|
|
22
|
+
FLWOR expressions, effective boolean value, existential comparisons — but its
|
|
23
|
+
surface syntax is **JSON itself**: a query document is a JSON value, the way an
|
|
24
|
+
XSLT stylesheet is an XML document. Navigation leaves are **RFC 9535 JSONPath**
|
|
25
|
+
strings, and the degenerate query is a bare JSONPath string.
|
|
26
|
+
|
|
27
|
+
Semantics follow XQuery 3.1 except where a numbered deviation (D1–D7, §11)
|
|
28
|
+
says otherwise.
|
|
29
|
+
|
|
30
|
+
A non-normative XQuery *text* front-end (`parseXQuery(text)` producing a query
|
|
31
|
+
document) is a planned compatibility layer; only the JSON format is specified
|
|
32
|
+
here. The JSON query document is the canonical language.
|
|
33
|
+
|
|
34
|
+
### 1.2 Conformance and normative language
|
|
35
|
+
|
|
36
|
+
The key words **MUST**, **MUST NOT**, **REQUIRED**, **SHALL**, **SHALL NOT**,
|
|
37
|
+
**SHOULD**, **SHOULD NOT**, **RECOMMENDED**, **MAY**, and **OPTIONAL** in this
|
|
38
|
+
document are to be interpreted as described in RFC 2119.
|
|
39
|
+
|
|
40
|
+
Two conformance roles exist:
|
|
41
|
+
|
|
42
|
+
- A **producer** emits query documents. A producer MUST emit documents that
|
|
43
|
+
are structurally valid per §12's JSON Schema and semantically valid per this
|
|
44
|
+
specification.
|
|
45
|
+
- A **consumer** (compiler + evaluator) MUST accept every valid query document
|
|
46
|
+
and MUST reject invalid documents with the compile errors of §10; it MUST
|
|
47
|
+
raise the runtime errors of §10 under the conditions specified there.
|
|
48
|
+
|
|
49
|
+
### 1.3 Terminology
|
|
50
|
+
|
|
51
|
+
- **Query document** — the top-level JSON value handed to the compiler (§4).
|
|
52
|
+
- **Expression** — any JSON value in expression position, interpreted per the
|
|
53
|
+
encoding rules of §3.
|
|
54
|
+
- **Phrase** — an object whose keys are all `$`-prefixed and which matches one
|
|
55
|
+
of the closed vocabulary shapes of §§4–8 (operator phrases, the FLWOR
|
|
56
|
+
phrase, quantifier phrases, the version envelope).
|
|
57
|
+
- **Path** — a string expression that is an RFC 9535 JSONPath query, absolute
|
|
58
|
+
or variable-rooted (§5).
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 2. Data model
|
|
63
|
+
|
|
64
|
+
### 2.1 Items and sequences
|
|
65
|
+
|
|
66
|
+
1. An **item** is any JSON value: `null`, a boolean, a number, a string, an
|
|
67
|
+
array, or an object. There is no node identity beyond JSON value structure;
|
|
68
|
+
arrays and objects are items like any other.
|
|
69
|
+
2. Numbers are IEEE 754 double-precision floats (**D1**). There is no separate
|
|
70
|
+
integer, decimal, or float type. Integer-valued positions and lengths are
|
|
71
|
+
doubles with integral values.
|
|
72
|
+
3. Every expression evaluates to a **sequence** of zero or more items.
|
|
73
|
+
Sequences are **flat** and **ordered**: a sequence never contains another
|
|
74
|
+
sequence. Combining sequences concatenates them.
|
|
75
|
+
4. The **empty sequence** `()` contains no items. The empty sequence is *not*
|
|
76
|
+
the same thing as `null`: `null` is an item, and a sequence containing
|
|
77
|
+
`null` has length 1.
|
|
78
|
+
5. A sequence of exactly one item is identified with that item ("singleton ≡
|
|
79
|
+
item"). A literal `42` and a one-item sequence containing `42` are
|
|
80
|
+
indistinguishable.
|
|
81
|
+
|
|
82
|
+
### 2.2 Effective boolean value (EBV)
|
|
83
|
+
|
|
84
|
+
Several constructs (`$where`, `$if`, `$and`, `$or`, `$not`, `$satisfies`)
|
|
85
|
+
reduce a sequence to a boolean, its **effective boolean value**. The EBV of a
|
|
86
|
+
sequence MUST be computed as follows, in order:
|
|
87
|
+
|
|
88
|
+
| Sequence | EBV |
|
|
89
|
+
|---|---|
|
|
90
|
+
| empty sequence | `false` |
|
|
91
|
+
| singleton `false` / `true` | the boolean itself |
|
|
92
|
+
| singleton number `n` | `false` if `n` is `0`, `-0`, or `NaN`; otherwise `true` |
|
|
93
|
+
| singleton string `s` | `false` if `s` is empty; otherwise `true` |
|
|
94
|
+
| singleton `null` | `false` |
|
|
95
|
+
| singleton array or object | `true` (**D3** — XQuery raises FORG0006 here) |
|
|
96
|
+
| any sequence of two or more items | runtime error `JQ2003` |
|
|
97
|
+
|
|
98
|
+
Note that D3 makes the EBV total over all singletons; only multi-item
|
|
99
|
+
sequences are erroneous.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 3. Encoding rules
|
|
104
|
+
|
|
105
|
+
Three rules assign a meaning to every JSON value in expression position. They
|
|
106
|
+
are exhaustive: every JSON value is either an expression or a compile error.
|
|
107
|
+
|
|
108
|
+
### 3.1 Rule 1 — objects
|
|
109
|
+
|
|
110
|
+
Partition an object's keys by whether they start with `$` (U+0024):
|
|
111
|
+
|
|
112
|
+
1. **All keys `$`-prefixed → operator phrase.** The object MUST match one
|
|
113
|
+
shape from the closed phrase vocabulary (§§4, 6, 7, 8). An unknown `$`-key
|
|
114
|
+
is compile error `JQ0002`; a known key with the wrong value shape, arity,
|
|
115
|
+
or key combination is compile error `JQ0003`.
|
|
116
|
+
2. **No key `$`-prefixed → map constructor.** Each key is a literal member
|
|
117
|
+
name; each value is evaluated as an expression. The result is a single
|
|
118
|
+
object item. Member value expressions follow the content rules of §3.4.
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{ "title": "$b.title", "inStock": true, "tags": ["new", "sale"] }
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
constructs an object with members `title` (value of the path `$b.title`),
|
|
125
|
+
`inStock` (literal `true`), and `tags` (a constructed array, Rule 3).
|
|
126
|
+
3. **Mixed → compile error `JQ0001`.** An object with both `$`-prefixed and
|
|
127
|
+
plain keys is never valid.
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{ "title": "$b.title", "$where": true }
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
is a compile error (`JQ0001`).
|
|
134
|
+
|
|
135
|
+
To construct an object whose member names start with `$` or are computed at
|
|
136
|
+
runtime, use the escape hatches of §3.5 (`$const`, `$map`).
|
|
137
|
+
|
|
138
|
+
An empty object `{}` has no `$`-prefixed key and is therefore a map
|
|
139
|
+
constructor: it constructs the empty object.
|
|
140
|
+
|
|
141
|
+
### 3.2 Rule 2 — strings
|
|
142
|
+
|
|
143
|
+
A string starting with `$` (U+0024) is a **query expression**; any other
|
|
144
|
+
string is a **literal string**. The forms, checked in order:
|
|
145
|
+
|
|
146
|
+
1. **Absolute path.** `$` alone, or a string starting with `$.`, `$[`, or
|
|
147
|
+
`$..`, MUST be a complete RFC 9535 JSONPath query. Its root `$` is the
|
|
148
|
+
input document. (RFC 9535 queries begin with `$` by grammar, so the sigil
|
|
149
|
+
costs nothing.) Examples: `"$"`, `"$.store.book[*]"`,
|
|
150
|
+
`"$..price"`, `"$['odd key']"`.
|
|
151
|
+
2. **Variable-rooted path.** `$name`, where *name* matches
|
|
152
|
+
`[A-Za-z_][A-Za-z0-9_]*`, optionally followed by RFC 9535 *segments*:
|
|
153
|
+
`"$b"`, `"$b.price"`, `"$b['odd key'][0]"`, `"$b[?@.x > 1]"`. The grammar
|
|
154
|
+
is RFC 9535 with the root identifier `$` replaced by the variable
|
|
155
|
+
reference. The path is evaluated with the variable's value as its root.
|
|
156
|
+
3. **Literal string escape.** A string starting with `$$` denotes the literal
|
|
157
|
+
string obtained by dropping exactly one leading `$`: `"$$price"` is the
|
|
158
|
+
string `"$price"`, `"$$$x"` is the string `"$$x"`. The escape applies only
|
|
159
|
+
to the two leading characters; no other unescaping occurs.
|
|
160
|
+
4. **Literal string.** Any string not starting with `$` is itself:
|
|
161
|
+
`"hello"` is the string `"hello"`.
|
|
162
|
+
|
|
163
|
+
A string starting with `$` that matches none of forms 1–3 (e.g. `"$9foo"`,
|
|
164
|
+
`"$ x"`), or that matches a head form but is not a grammatically valid path
|
|
165
|
+
(e.g. `"$.store["`), is compile error `JQ0004`.
|
|
166
|
+
|
|
167
|
+
Inside the bracketed filter expressions embedded in a path (either form),
|
|
168
|
+
`@` and `$` keep their RFC 9535 meanings — the current node and the *input
|
|
169
|
+
document* root respectively. **Query variables are not visible inside path
|
|
170
|
+
filters**; a filter that needs another variable is a cross-variable predicate
|
|
171
|
+
and belongs in `$where` (§6.4). See §5.2 for the two filter dialects.
|
|
172
|
+
|
|
173
|
+
### 3.3 Rule 3 — scalars are literals
|
|
174
|
+
|
|
175
|
+
Numbers, booleans, and `null` denote themselves: `42`, `true`, `null` are
|
|
176
|
+
literal items (singleton sequences).
|
|
177
|
+
|
|
178
|
+
### 3.4 Rule 3, continued — arrays are array constructors
|
|
179
|
+
|
|
180
|
+
An array in expression position is an **array constructor**. Each element is
|
|
181
|
+
evaluated in order, and the resulting sequences are concatenated per XQuery
|
|
182
|
+
sequence flattening into the members of one new array item: an element
|
|
183
|
+
evaluating to a sequence of N items contributes N members; an element
|
|
184
|
+
evaluating to the empty sequence contributes none.
|
|
185
|
+
|
|
186
|
+
```json
|
|
187
|
+
[1, "$.store.book[*].price", { "$seq": [2, 3] }]
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Against a store with four book prices, this constructs a single 7-element
|
|
191
|
+
array: `1`, the four prices, `2`, `3`.
|
|
192
|
+
|
|
193
|
+
The same flattening applies to the member value expressions of a map
|
|
194
|
+
constructor and to every other expression position; it is a property of the
|
|
195
|
+
sequence data model, not of the array constructor alone. To embed an array
|
|
196
|
+
*as a value* without evaluation, use `$const` (§3.5.1); to bind one without
|
|
197
|
+
iteration, use `$let` (§6.3).
|
|
198
|
+
|
|
199
|
+
### 3.5 Escape hatches
|
|
200
|
+
|
|
201
|
+
#### 3.5.1 `$const` — quote
|
|
202
|
+
|
|
203
|
+
```json
|
|
204
|
+
{ "$const": v }
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
evaluates to `v` verbatim, as a single item. Nothing inside `v` is evaluated:
|
|
208
|
+
strings starting with `$` stay as written, objects with `$`-keys are plain
|
|
209
|
+
data, arrays are not flattened. `v` MAY be any JSON value.
|
|
210
|
+
|
|
211
|
+
```json
|
|
212
|
+
{ "template": { "$const": { "$for": "kept verbatim", "price": null } }, "label": "$$price" }
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
constructs `{ "template": { "$for": "kept verbatim", "price": null }, "label": "$price" }`.
|
|
216
|
+
|
|
217
|
+
#### 3.5.2 `$map` — general map constructor
|
|
218
|
+
|
|
219
|
+
```json
|
|
220
|
+
{ "$map": [[keyExpr, valueExpr], ...] }
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
constructs one object from a list of key/value expression pairs. Use it for
|
|
224
|
+
computed member names and for names that start with `$`. For each pair, in
|
|
225
|
+
order:
|
|
226
|
+
|
|
227
|
+
- *keyExpr* MUST evaluate to a single string; any other result (empty
|
|
228
|
+
sequence, non-string, multi-item sequence) is runtime error `JQ2004`.
|
|
229
|
+
- *valueExpr* is evaluated as an ordinary expression (its result flows per
|
|
230
|
+
§3.4).
|
|
231
|
+
- Later pairs win on duplicate keys.
|
|
232
|
+
|
|
233
|
+
Each pair MUST be an array of exactly two expressions (`JQ0003` otherwise).
|
|
234
|
+
An empty pair list constructs the empty object.
|
|
235
|
+
|
|
236
|
+
```json
|
|
237
|
+
{ "$let": { "prefix": "col_", "book": "$.store.book[0]" },
|
|
238
|
+
"$return": { "$map": [
|
|
239
|
+
[{ "$concat": ["$prefix", "title"] }, "$book.title"],
|
|
240
|
+
[{ "$concat": ["$prefix", "price"] }, "$book.price"]
|
|
241
|
+
] } }
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
constructs `{ "col_title": ..., "col_price": ... }`.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## 4. Query documents
|
|
249
|
+
|
|
250
|
+
### 4.1 Top level
|
|
251
|
+
|
|
252
|
+
A query document is either:
|
|
253
|
+
|
|
254
|
+
1. a **bare expression** — any expression per §3. In particular a bare
|
|
255
|
+
JSONPath string is a complete query (the degenerate case), and so is a
|
|
256
|
+
bare scalar; or
|
|
257
|
+
2. the **version envelope** phrase:
|
|
258
|
+
|
|
259
|
+
```json
|
|
260
|
+
{ "$query": "0.1", "$expr": <expression> }
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Both keys are REQUIRED and no other key is permitted (`JQ0003`).
|
|
264
|
+
|
|
265
|
+
### 4.2 Versioning
|
|
266
|
+
|
|
267
|
+
The version identifier for this specification is the string `"0.1"`. A
|
|
268
|
+
missing envelope implies the consumer's current version. A `$query` value
|
|
269
|
+
that is not a version string the consumer implements — including non-string
|
|
270
|
+
values and unknown future versions — is compile error `JQ0006`.
|
|
271
|
+
|
|
272
|
+
The envelope is only recognized at the top level of a query document; an
|
|
273
|
+
object with `$query`/`$expr` keys in any other expression position is an
|
|
274
|
+
unknown operator phrase (`JQ0002`/`JQ0003`).
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## 5. Paths
|
|
279
|
+
|
|
280
|
+
### 5.1 Path semantics
|
|
281
|
+
|
|
282
|
+
A path (absolute or variable-rooted, §3.2) evaluates to the sequence of the
|
|
283
|
+
nodes' values selected by the RFC 9535 query, **in document order** as defined
|
|
284
|
+
by RFC 9535 (children of an object in member order, of an array in index
|
|
285
|
+
order; descendant traversal per RFC 9535 §2.5.2). That node sequence then
|
|
286
|
+
flows through the query as an ordinary XQuery sequence.
|
|
287
|
+
|
|
288
|
+
A variable-rooted path evaluates its segments against each item of the
|
|
289
|
+
variable's bound sequence, in order, concatenating results. For the common
|
|
290
|
+
case of a `$for` variable the binding is a single item.
|
|
291
|
+
|
|
292
|
+
**Singular queries** (RFC 9535 singular query: name and index selectors only)
|
|
293
|
+
are statically known to evaluate to zero or one item; consumers MAY exploit
|
|
294
|
+
this, and later work uses it for static cardinality checks.
|
|
295
|
+
|
|
296
|
+
An absolute path in any expression position reads from the one input
|
|
297
|
+
document; there is no context-item drift — `$` is always the document root.
|
|
298
|
+
|
|
299
|
+
### 5.2 The two filter dialects
|
|
300
|
+
|
|
301
|
+
Two boolean mini-languages coexist in this format, and both keep the exact
|
|
302
|
+
semantics of their own standard:
|
|
303
|
+
|
|
304
|
+
1. **Inside a path's `[?...]` filter**, RFC 9535 semantics apply unchanged —
|
|
305
|
+
including the special absent-value `Nothing`, where `Nothing == Nothing`
|
|
306
|
+
is **true** for two empty singular-query results.
|
|
307
|
+
2. **At query level** (`$where`, `$eq`, ...), XQuery semantics apply —
|
|
308
|
+
comparisons are existential over sequences, so a comparison in which
|
|
309
|
+
either side is the empty sequence is **false** (§8.4).
|
|
310
|
+
|
|
311
|
+
Worked example. Input:
|
|
312
|
+
|
|
313
|
+
```json
|
|
314
|
+
{ "a": [ { "n": 1 } ] }
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
The item in `a` has neither member `b` nor member `c`.
|
|
318
|
+
|
|
319
|
+
- Path dialect: `"$.a[?@.b == @.c]"` **selects the item** — both sides are
|
|
320
|
+
empty nodelists of singular queries, i.e. `Nothing == Nothing`, which
|
|
321
|
+
RFC 9535 defines as true.
|
|
322
|
+
- Query dialect:
|
|
323
|
+
|
|
324
|
+
```json
|
|
325
|
+
{ "$for": { "x": "$.a[*]" },
|
|
326
|
+
"$where": { "$eq": ["$x.b", "$x.c"] },
|
|
327
|
+
"$return": "$x" }
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
returns the **empty sequence** — `$x.b` and `$x.c` are both empty, an
|
|
331
|
+
existential comparison over empty sequences finds no witnessing pair, so
|
|
332
|
+
`$eq` is false.
|
|
333
|
+
|
|
334
|
+
Neither dialect is wrong; they answer different questions. Producers SHOULD
|
|
335
|
+
choose the dialect deliberately when absent members are possible.
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## 6. The FLWOR phrase
|
|
340
|
+
|
|
341
|
+
### 6.1 Shape and clause order
|
|
342
|
+
|
|
343
|
+
A FLWOR phrase is an operator phrase containing `$for` and/or `$let`, plus
|
|
344
|
+
optional clauses, plus the REQUIRED `$return`:
|
|
345
|
+
|
|
346
|
+
| Key | Value | Presence |
|
|
347
|
+
|---|---|---|
|
|
348
|
+
| `$for` | binding object (§6.2) | at least one of `$for`, `$let` |
|
|
349
|
+
| `$let` | binding object (§6.3) | at least one of `$for`, `$let` |
|
|
350
|
+
| `$as` | schema assertion object (§6.8) | OPTIONAL |
|
|
351
|
+
| `$where` | expression | OPTIONAL |
|
|
352
|
+
| `$groupby` | binding object (§6.5) | OPTIONAL |
|
|
353
|
+
| `$orderby` | key spec or array of key specs (§6.6) | OPTIONAL |
|
|
354
|
+
| `$count` | variable name string (§6.7) | OPTIONAL |
|
|
355
|
+
| `$return` | expression | REQUIRED |
|
|
356
|
+
|
|
357
|
+
The clauses apply in **fixed semantic order regardless of their order in the
|
|
358
|
+
JSON document** (**D7**):
|
|
359
|
+
|
|
360
|
+
```
|
|
361
|
+
$for → $let → $as → $where → $groupby → $orderby → $count → $return
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
JSON key order is not interoperable — several ecosystems (e.g. Go maps)
|
|
365
|
+
neither preserve nor guarantee it — so this format MUST NOT give key order
|
|
366
|
+
semantic weight anywhere, and does not.
|
|
367
|
+
|
|
368
|
+
Exotic clause interleavings (a `$let` between two `$for`s, a `$where` before
|
|
369
|
+
a `$for`, multiple `$where`s) are expressed by **nesting** FLWOR phrases,
|
|
370
|
+
which is standard XQuery practice anyway:
|
|
371
|
+
|
|
372
|
+
```json
|
|
373
|
+
{ "$for": { "a": "$.store.book[*]" },
|
|
374
|
+
"$return": { "$let": { "p": "$a.price" },
|
|
375
|
+
"$where": { "$gt": ["$p", 10] },
|
|
376
|
+
"$return": "$a.title" } }
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
The FLWOR phrase evaluates to the concatenation of the `$return` results over
|
|
380
|
+
the surviving tuple stream, in tuple order.
|
|
381
|
+
|
|
382
|
+
### 6.2 `$for` — iteration bindings
|
|
383
|
+
|
|
384
|
+
```json
|
|
385
|
+
"$for": { name: source, ... }
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
Each *name* MUST match `[A-Za-z_][A-Za-z0-9_]*` (`JQ0003` otherwise). Each
|
|
389
|
+
*source* is either an expression or the extended binding form below. A `$for`
|
|
390
|
+
binding evaluates its source and iterates the resulting item sequence,
|
|
391
|
+
binding *name* to one item per tuple.
|
|
392
|
+
|
|
393
|
+
**Auto-iteration of arrays (D4).** When an item produced by the source is an
|
|
394
|
+
array, `$for` unpacks it into its members — one level only — and iterates
|
|
395
|
+
those. This is the ergonomic default for the JSON data model, where `[*]`
|
|
396
|
+
already produced values, not nodes. To bind an array *as a value*, use `$let`
|
|
397
|
+
or `{"$const": [...]}`. Objects are NOT auto-iterated; iterate an object's
|
|
398
|
+
values with a `[*]` path segment (a future `$entries` operator is the planned
|
|
399
|
+
counterpart for member pairs).
|
|
400
|
+
|
|
401
|
+
**Multiple bindings** nest left-to-right in document key order, exactly like
|
|
402
|
+
consecutive XQuery `for` clauses, and **may be correlated**: a later source
|
|
403
|
+
may reference variables bound earlier in the same `$for` object.
|
|
404
|
+
|
|
405
|
+
> **Interop caveat.** Correlation is the one place where the *relative order*
|
|
406
|
+
> of keys inside a single binding object matters. Producers on
|
|
407
|
+
> key-order-hostile stacks SHOULD emit one binding per phrase, nesting
|
|
408
|
+
> phrases, instead of relying on multi-key binding objects.
|
|
409
|
+
|
|
410
|
+
**Extended binding form** — a source written as
|
|
411
|
+
|
|
412
|
+
```json
|
|
413
|
+
{ "$in": expr, "$at": "posName" }
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
iterates `expr` like a plain source and additionally binds *posName* to the
|
|
417
|
+
**0-based** (D6) position of the current item within the iterated sequence.
|
|
418
|
+
Both keys are REQUIRED in this form (a positionless binding is simply the
|
|
419
|
+
plain form); *posName* MUST be a valid variable name.
|
|
420
|
+
|
|
421
|
+
```json
|
|
422
|
+
{ "$for": { "b": { "$in": "$.store.book[*]", "$at": "i" } },
|
|
423
|
+
"$count": "n",
|
|
424
|
+
"$return": { "index": "$i", "row": "$n", "title": "$b.title" } }
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### 6.3 `$let` — sequence bindings
|
|
428
|
+
|
|
429
|
+
```json
|
|
430
|
+
"$let": { name: expr, ... }
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Binds each *name* to the **full sequence** its expression evaluates to — no
|
|
434
|
+
iteration, no array unpacking. Name rules, document-key-order evaluation, and
|
|
435
|
+
correlation rules are identical to `$for` (later `$let` sources see earlier
|
|
436
|
+
`$let` variables of the same object). The extended `$in`/`$at` form is not
|
|
437
|
+
available in `$let` (`JQ0003`).
|
|
438
|
+
|
|
439
|
+
Binding the same variable name twice within one FLWOR phrase — in one binding
|
|
440
|
+
object (where JSON parsers permit duplicate keys), across the phrase's `$for`
|
|
441
|
+
and `$let` objects, as an `$at` name, or in `$groupby` (as a new key name) —
|
|
442
|
+
is compile error `JQ0007`. Rebinding a name from an *enclosing* phrase is
|
|
443
|
+
ordinary shadowing and is allowed.
|
|
444
|
+
|
|
445
|
+
### 6.4 `$where` — tuple filter
|
|
446
|
+
|
|
447
|
+
```json
|
|
448
|
+
"$where": expr
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Evaluates *expr* once per tuple and keeps the tuples whose **EBV** (§2.2) is
|
|
452
|
+
true. Cross-variable predicates (joins) belong here, not in path filters
|
|
453
|
+
(§3.2, §5.2).
|
|
454
|
+
|
|
455
|
+
### 6.5 `$groupby` — grouping
|
|
456
|
+
|
|
457
|
+
```json
|
|
458
|
+
"$groupby": { name: keyExpr, ... }
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
XQuery 3.1 group-by semantics:
|
|
462
|
+
|
|
463
|
+
- Each *keyExpr* is evaluated per tuple; each *name* becomes a
|
|
464
|
+
**grouping-key variable**, bound in every subsequent clause to that group's
|
|
465
|
+
key value (a singleton per group).
|
|
466
|
+
- Tuples with equal key combinations form one group. Key equality is **deep
|
|
467
|
+
structural JSON equality** (`equalsJson`-grade, the same relation as `$eq`
|
|
468
|
+
item equality, §8.4), with numbers compared mathematically.
|
|
469
|
+
- Every other variable bound in the phrase is **rebound to the sequence** of
|
|
470
|
+
its values across the group's tuples, in tuple order.
|
|
471
|
+
- The tuple stream after `$groupby` has one tuple per group, in order of
|
|
472
|
+
**first appearance** of each group's key combination in the incoming
|
|
473
|
+
tuple stream.
|
|
474
|
+
|
|
475
|
+
*name* MUST be a valid variable name, distinct from every other variable
|
|
476
|
+
bound in the phrase (`JQ0007`).
|
|
477
|
+
|
|
478
|
+
```json
|
|
479
|
+
{ "$for": { "b": "$.store.book[*]" },
|
|
480
|
+
"$groupby": { "genre": "$b.category" },
|
|
481
|
+
"$return": { "genre": "$genre",
|
|
482
|
+
"count": { "$count": "$b" },
|
|
483
|
+
"avg": { "$avg": "$b.price" } } }
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
Here, inside `$return`, `$genre` is one key value and `$b` is the sequence of
|
|
487
|
+
that genre's books.
|
|
488
|
+
|
|
489
|
+
### 6.6 `$orderby` — ordering
|
|
490
|
+
|
|
491
|
+
```json
|
|
492
|
+
"$orderby": keySpec
|
|
493
|
+
"$orderby": [keySpec, ...]
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
A *keySpec* is either an expression (shorthand for ascending, empty-least) or
|
|
497
|
+
the explicit form
|
|
498
|
+
|
|
499
|
+
```json
|
|
500
|
+
{ "$key": expr, "$dir": "asc" | "desc", "$empty": "least" | "greatest" }
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
with `$key` REQUIRED and `$dir` (default `"asc"`) and `$empty` (default
|
|
504
|
+
`"least"`) OPTIONAL.
|
|
505
|
+
|
|
506
|
+
- An array value of `$orderby` is **always a list of key specs**, ordered
|
|
507
|
+
major to minor, and MUST NOT be empty. Consequently a *single* key spec is
|
|
508
|
+
never written as a bare array: an array-constructor sort key (a runtime
|
|
509
|
+
error under the key-type rules anyway) MUST be wrapped as
|
|
510
|
+
`{"$key": [...]}`. The schema enforces this reading.
|
|
511
|
+
- The sort is **stable**.
|
|
512
|
+
- Each key expression is evaluated per tuple. A key value MUST be the empty
|
|
513
|
+
sequence or a single number or string; within one comparison pair the two
|
|
514
|
+
key values MUST be both numbers or both strings — numbers compare
|
|
515
|
+
mathematically, strings by **Unicode scalar values** (code point order).
|
|
516
|
+
Comparing any other combination (number with string, or a key that is a
|
|
517
|
+
boolean, `null`, array, object, or multi-item sequence) is runtime error
|
|
518
|
+
`JQ2005`.
|
|
519
|
+
- Empty key sequences sort per `$empty`: `"least"` (default) places them
|
|
520
|
+
first ascending / last descending; `"greatest"` the reverse.
|
|
521
|
+
|
|
522
|
+
```json
|
|
523
|
+
{ "$for": { "b": "$.store.book[*]" },
|
|
524
|
+
"$orderby": [ { "$key": "$b.price", "$dir": "desc", "$empty": "greatest" }, "$b.title" ],
|
|
525
|
+
"$return": "$b.title" }
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### 6.7 `$count` — tuple numbering
|
|
529
|
+
|
|
530
|
+
```json
|
|
531
|
+
"$count": "name"
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
Binds *name* (a valid variable name string) to the **0-based** (D6) index of
|
|
535
|
+
the tuple in the stream *after* `$where`, `$groupby`, and `$orderby` have
|
|
536
|
+
applied. See §6.2 for an example.
|
|
537
|
+
|
|
538
|
+
Collision note: as a FLWOR clause, `$count`'s value is a **name string**; as
|
|
539
|
+
an operator (§8.7), `$count` is a single-key object whose value is an
|
|
540
|
+
expression. The two are structurally unambiguous — the clause occurs only
|
|
541
|
+
among other FLWOR keys with `$return` present; the operator phrase is exactly
|
|
542
|
+
one key.
|
|
543
|
+
|
|
544
|
+
### 6.8 `$as` — schema assertions on bindings
|
|
545
|
+
|
|
546
|
+
```json
|
|
547
|
+
"$as": { name: schema, ... }
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
Numbered after `$count` for historical reasons; in the fixed clause order of
|
|
551
|
+
§6.1, `$as` occupies the slot **between `$let` and `$where`**.
|
|
552
|
+
|
|
553
|
+
Each member names a variable bound by **this phrase's** `$for` (including
|
|
554
|
+
`$at` position names) or `$let`, and pairs it with a **JSON Schema literal**
|
|
555
|
+
(§8.11): a JSON value taken **verbatim** — never interpreted as a query
|
|
556
|
+
expression, so JSON Schema's `$`-prefixed keywords (`$ref`, `$defs`, ...) do
|
|
557
|
+
not collide with Rule 1. A name not bound by the phrase's `$for`/`$let` is
|
|
558
|
+
compile error `JQ0005`. Each schema is compiled once, at query compile time,
|
|
559
|
+
by the consumer's type-test compiler (`JQ0008` when none is installed,
|
|
560
|
+
`JQ0009` when it rejects the schema — see §8.11).
|
|
561
|
+
|
|
562
|
+
Per tuple, after the `$for`/`$let` bindings are established and before
|
|
563
|
+
`$where` applies:
|
|
564
|
+
|
|
565
|
+
- a `$for` or `$at` variable is validated as its one bound **item**;
|
|
566
|
+
- a `$let` variable is validated **per item of its bound sequence** (the
|
|
567
|
+
empty sequence passes vacuously — the schema sees items, never the
|
|
568
|
+
sequence itself, §8.11).
|
|
569
|
+
|
|
570
|
+
The first failing item is runtime error `JQ2008`, naming the variable. `$as`
|
|
571
|
+
never drops tuples — it asserts; use `$valid` in `$where` (§8.11) to filter
|
|
572
|
+
instead.
|
|
573
|
+
|
|
574
|
+
```json
|
|
575
|
+
{ "$for": { "b": "$.store.book[*]" },
|
|
576
|
+
"$as": { "b": { "type": "object", "required": ["title", "price"] } },
|
|
577
|
+
"$return": "$b.title" }
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
---
|
|
581
|
+
|
|
582
|
+
## 7. Quantifier phrases
|
|
583
|
+
|
|
584
|
+
```json
|
|
585
|
+
{ "$some": { name: expr, ... }, "$satisfies": expr }
|
|
586
|
+
{ "$every": { name: expr, ... }, "$satisfies": expr }
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
XQuery `some/every ... satisfies`. The binding object follows `$for` rules
|
|
590
|
+
(names, document-key-order nesting, correlation, D4 array unpacking) except
|
|
591
|
+
that the extended `$in`/`$at` form is not available (`JQ0003`). Both keys are
|
|
592
|
+
REQUIRED.
|
|
593
|
+
|
|
594
|
+
For each binding tuple, the EBV (§2.2) of `$satisfies` is computed:
|
|
595
|
+
|
|
596
|
+
- `$some` is `true` iff at least one tuple satisfies; `false` over an empty
|
|
597
|
+
tuple stream.
|
|
598
|
+
- `$every` is `true` iff every tuple satisfies; `true` over an empty tuple
|
|
599
|
+
stream.
|
|
600
|
+
|
|
601
|
+
Evaluation MUST short-circuit ("early exit"): `$some` MAY stop at the first
|
|
602
|
+
satisfying tuple, `$every` at the first failing one; a runtime error in a
|
|
603
|
+
tuple after the deciding tuple is then not raised.
|
|
604
|
+
|
|
605
|
+
```json
|
|
606
|
+
{ "$every": { "b": "$.store.book[*]" }, "$satisfies": { "$exists": "$b.title" } }
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
```json
|
|
610
|
+
{ "$let": { "books": "$.store.book[*]" },
|
|
611
|
+
"$return": { "$some": { "b": "$books" },
|
|
612
|
+
"$satisfies": { "$gt": ["$b.price", 20] } } }
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
---
|
|
616
|
+
|
|
617
|
+
## 8. Operators
|
|
618
|
+
|
|
619
|
+
### 8.1 Vocabulary and conventions
|
|
620
|
+
|
|
621
|
+
Every operator is a **single-`$`-key object** (the phrases of §§4, 6, 7 and
|
|
622
|
+
the two-key quantifier shapes are the only exceptions). The vocabulary is
|
|
623
|
+
**closed**: an object whose single `$`-key is not listed in this section is
|
|
624
|
+
compile error `JQ0002`. Wrong argument shapes and arities are `JQ0003`.
|
|
625
|
+
|
|
626
|
+
Signature notation: `expr` is any expression; `[a, b]` is a JSON array of
|
|
627
|
+
exactly two expressions; `[e, ...]` is variadic; `?` marks an optional
|
|
628
|
+
trailing argument. "Cardinality" describes the *runtime* sequence each
|
|
629
|
+
argument position accepts; argument positions are structurally always
|
|
630
|
+
expressions (per-position runtime rules are enforced by the evaluator with
|
|
631
|
+
`JQ2xxx` errors, not by the schema).
|
|
632
|
+
|
|
633
|
+
Unless a definition below says otherwise, an operator's result is exactly
|
|
634
|
+
one item. Operators whose semantics come from XQuery 3.1 Functions and
|
|
635
|
+
Operators keep that reference in their definition; F&O behavior is adapted
|
|
636
|
+
to the JSON data model per the deviations of §11 (notably D1 numbers and
|
|
637
|
+
D6 0-based positions).
|
|
638
|
+
|
|
639
|
+
The JSON-Schema-as-type-system layer defines `$valid` and `$assert` (§8.11)
|
|
640
|
+
and the FLWOR clause `$as` (§6.8); no keys remain reserved in this version.
|
|
641
|
+
|
|
642
|
+
### 8.2 Sequences
|
|
643
|
+
|
|
644
|
+
| Operator | Signature | Result |
|
|
645
|
+
|---|---|---|
|
|
646
|
+
| `$seq` | `{"$seq": [e, ...]}` | XQuery comma: evaluate each element, concatenate all results into one flat sequence. `{"$seq": []}` is the empty sequence. |
|
|
647
|
+
| `$exists` | `{"$exists": e}` | `true` iff `e`'s result is non-empty (XQuery `fn:exists`). |
|
|
648
|
+
| `$empty` | `{"$empty": e}` | `true` iff `e`'s result is empty (XQuery `fn:empty`). |
|
|
649
|
+
|
|
650
|
+
```json
|
|
651
|
+
{ "$if": [ { "$exists": "$.store.bicycle" },
|
|
652
|
+
{ "$seq": ["$.store.bicycle.color", "$.store.bicycle.price"] } ] }
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
### 8.3 Conditional
|
|
656
|
+
|
|
657
|
+
| Operator | Signature | Result |
|
|
658
|
+
|---|---|---|
|
|
659
|
+
| `$if` | `{"$if": [cond, then, else?]}` | EBV of *cond*; on `true` evaluates *then*, on `false` evaluates *else*. A missing *else* means the empty sequence. Only the taken branch is evaluated. |
|
|
660
|
+
|
|
661
|
+
Arity 2–3 (`JQ0003` otherwise). Example above.
|
|
662
|
+
|
|
663
|
+
### 8.4 Comparisons — `$eq $ne $lt $le $gt $ge`
|
|
664
|
+
|
|
665
|
+
Signature: `{"$eq": [left, right]}` etc., arity exactly 2.
|
|
666
|
+
|
|
667
|
+
These are **general comparisons** in the XQuery sense (the `=` family):
|
|
668
|
+
existentially lifted over sequences. `{"$op": [L, R]}` is `true` iff **some**
|
|
669
|
+
item `l` of `L`'s result and **some** item `r` of `R`'s result satisfy the
|
|
670
|
+
item comparison; otherwise `false`. Consequences:
|
|
671
|
+
|
|
672
|
+
- Over empty sequences the result is `false` — there is no witnessing pair.
|
|
673
|
+
Contrast this with `Nothing == Nothing` inside path filters, which is true;
|
|
674
|
+
see §5.2, "The two filter dialects".
|
|
675
|
+
- `$ne` is existential too: `{"$ne": [L, R]}` is true iff some pair differs —
|
|
676
|
+
it is NOT the negation of `$eq` on multi-item sequences. Use
|
|
677
|
+
`{"$not": {"$eq": [L, R]}}` for the negation.
|
|
678
|
+
|
|
679
|
+
**Item comparison rules:**
|
|
680
|
+
|
|
681
|
+
- `$eq` / `$ne` use **deep structural JSON equality** (**D2**): two items are
|
|
682
|
+
equal iff they are structurally identical JSON values; numbers compare
|
|
683
|
+
mathematically (`1` equals `1.0`; `NaN` equals nothing, `-0` equals `0`);
|
|
684
|
+
arrays memberwise in order; objects by key set and per-key values,
|
|
685
|
+
key order irrelevant.
|
|
686
|
+
- `$lt $le $gt $ge` compare numbers with numbers (mathematically) and strings
|
|
687
|
+
with strings (Unicode scalar value order). For **any other item pair**
|
|
688
|
+
(number vs string, booleans, `null`, arrays, objects) the item comparison
|
|
689
|
+
is simply **false** for that pair — not an error — and contributes no
|
|
690
|
+
witness.
|
|
691
|
+
|
|
692
|
+
```json
|
|
693
|
+
{ "$and": [
|
|
694
|
+
{ "$eq": ["$.store.bicycle.color", "red"] },
|
|
695
|
+
{ "$or": [ { "$lt": ["$.store.bicycle.price", 400] }, { "$not": false } ] },
|
|
696
|
+
{ "$ne": [1, 2] }, { "$le": [1, 1] }, { "$gt": [2, 1] }, { "$ge": [2, 2] }
|
|
697
|
+
] }
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
### 8.5 Arithmetic — `$add $sub $mul $div $idiv $mod`, `$neg`
|
|
701
|
+
|
|
702
|
+
Signatures: `{"$add": [a, b]}` etc., arity exactly 2; `{"$neg": a}` unary
|
|
703
|
+
(the value is the operand expression itself, not a one-element array).
|
|
704
|
+
|
|
705
|
+
Operand rules (each operand, after evaluation):
|
|
706
|
+
|
|
707
|
+
- empty sequence → the operator's result is the **empty sequence** (XQuery);
|
|
708
|
+
- a singleton number → used as is;
|
|
709
|
+
- a singleton non-number, or a multi-item sequence → runtime type error
|
|
710
|
+
`JQ2001` (XPTY0004-shaped).
|
|
711
|
+
|
|
712
|
+
All arithmetic is IEEE double arithmetic (**D1**):
|
|
713
|
+
|
|
714
|
+
- `$div` by zero follows IEEE 754: `Infinity`, `-Infinity`, or `NaN` — the
|
|
715
|
+
XQuery *double* division semantics, not the decimal FOAR0001 error.
|
|
716
|
+
- `$idiv` is **truncating division** (quotient rounded toward zero to an
|
|
717
|
+
integral double); `$idiv` or `$mod` with a zero divisor is runtime error
|
|
718
|
+
`JQ2002`.
|
|
719
|
+
- `$mod` takes the sign of the dividend (XQuery `mod` semantics).
|
|
720
|
+
- `$neg` is unary minus.
|
|
721
|
+
|
|
722
|
+
```json
|
|
723
|
+
{ "$seq": [ { "$add": [1, 2] }, { "$sub": [3, 1] }, { "$mul": [2, 4] },
|
|
724
|
+
{ "$div": [1, 0] }, { "$idiv": [7, 2] }, { "$mod": [7, 2] },
|
|
725
|
+
{ "$neg": "$.store.bicycle.price" } ] }
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
### 8.6 Logic — `$and $or $not`
|
|
729
|
+
|
|
730
|
+
| Operator | Signature | Result |
|
|
731
|
+
|---|---|---|
|
|
732
|
+
| `$and` | `{"$and": [e, ...]}` (≥ 1) | `true` iff every operand's EBV is true. |
|
|
733
|
+
| `$or` | `{"$or": [e, ...]}` (≥ 1) | `true` iff some operand's EBV is true. |
|
|
734
|
+
| `$not` | `{"$not": e}` | negated EBV of `e` (XQuery `fn:not`). |
|
|
735
|
+
|
|
736
|
+
Operands are reduced by EBV (§2.2) left to right with **short-circuit**
|
|
737
|
+
evaluation: `$and` stops at the first false, `$or` at the first true;
|
|
738
|
+
operands after the deciding one are not evaluated and cannot raise errors.
|
|
739
|
+
Example in §8.4.
|
|
740
|
+
|
|
741
|
+
### 8.7 Strings
|
|
742
|
+
|
|
743
|
+
**String parameters.** Every argument of the operators in this section
|
|
744
|
+
(except where a definition says otherwise) MUST evaluate to the empty
|
|
745
|
+
sequence — read as `""`, the F&O `xs:string?` convention — or to a single
|
|
746
|
+
string; a singleton of any other type, or a sequence of two or more items,
|
|
747
|
+
is runtime error `JQ2001`. `$concat` and `$string-join` additionally cast
|
|
748
|
+
their *item* operands per the `$string` table (§8.10). All results are
|
|
749
|
+
single items.
|
|
750
|
+
|
|
751
|
+
| Operator | Signature | Definition |
|
|
752
|
+
|---|---|---|
|
|
753
|
+
| `$concat` | `{"$concat": [e, ...]}` (≥ 0) | Variadic concatenation. Each operand is cast to string per `$string` (§8.10); an empty-sequence operand contributes `''`. `{"$concat": []}` is `""`. |
|
|
754
|
+
| `$string-join` | `[seq, sep?]` (1–2) | Concatenates the items of *seq*, each cast to string per `$string`, separated by *sep* (a string parameter, default `""`). An empty *seq* yields `""` (F&O `fn:string-join`). |
|
|
755
|
+
| `$substring` | `[str, start, len?]` (2–3) | The code points of *str* at 0-based (**D6** — deviation from F&O's 1-based positions) positions `p` with `round(start) ≤ p` and, when *len* is given, `p < round(start) + round(len)` (F&O `fn:substring` bounds; `round` rounds half toward +∞). *start*/*len* MUST each be a single number (`JQ2001`); a `NaN` bound selects nothing. |
|
|
756
|
+
| `$contains` | `[str, sub]` | `true` iff *str* contains *sub* (`fn:contains`; every string contains `""`). |
|
|
757
|
+
| `$starts-with` | `[str, prefix]` | `true` iff *str* starts with *prefix* (`fn:starts-with`). |
|
|
758
|
+
| `$ends-with` | `[str, suffix]` | `true` iff *str* ends with *suffix* (`fn:ends-with`). |
|
|
759
|
+
| `$upper` | `{"$upper": e}` | Unicode default uppercase (`fn:upper-case`). |
|
|
760
|
+
| `$lower` | `{"$lower": e}` | Unicode default lowercase (`fn:lower-case`). |
|
|
761
|
+
| `$string-length` | `{"$string-length": e}` | Length in Unicode scalar values (code points), `fn:string-length`. Empty → `0`. |
|
|
762
|
+
| `$normalize-space` | `{"$normalize-space": e}` | Strips leading and trailing whitespace (space, tab, CR, LF) and collapses every internal whitespace run to one space (`fn:normalize-space`). |
|
|
763
|
+
| `$match` | `[input, pattern]` | `true` iff *pattern* matches **all** of *input* (anchored, the RFC 9535 `match()` behavior). |
|
|
764
|
+
| `$search` | `[input, pattern]` | `true` iff *pattern* matches a substring of *input* (RFC 9535 `search()`, XQuery `fn:matches`). |
|
|
765
|
+
| `$replace` | `[input, pattern, replacement]` (exactly 3) | Replaces every non-overlapping match of *pattern* in *input* with *replacement* (`fn:replace`). *replacement* is inserted **literally** — there are no capture-group references (I-Regexp guarantees no capture semantics). |
|
|
766
|
+
|
|
767
|
+
Regular expression operators use **I-Regexp (RFC 9485)** syntax — the same
|
|
768
|
+
interoperable regex dialect RFC 9535 uses — not XSD regular expressions
|
|
769
|
+
(**D5**). I-Regexp has no flags argument, hence `$replace`'s fixed arity 3.
|
|
770
|
+
A *pattern* that is not a syntactically valid I-Regexp makes `$match` and
|
|
771
|
+
`$search` evaluate to `false` (the RFC 9535 rule for nonconforming
|
|
772
|
+
patterns), but is runtime error `JQ2001` in `$replace` (the F&O
|
|
773
|
+
`err:FORX0002` condition — a replacement cannot silently do nothing). A
|
|
774
|
+
`$replace` *pattern* that matches the zero-length string (e.g. `"a*"`) is
|
|
775
|
+
also `JQ2001` (F&O `err:FORX0003`). Pattern *type* errors remain `JQ2001`
|
|
776
|
+
in all three operators per the string-parameter rule above.
|
|
777
|
+
|
|
778
|
+
```json
|
|
779
|
+
{ "$seq": [ { "$concat": ["a", "b"] },
|
|
780
|
+
{ "$string-join": ["$.store.book[*].title", ", "] },
|
|
781
|
+
{ "$substring": ["hello", 1, 3] },
|
|
782
|
+
{ "$contains": ["hello", "ell"] },
|
|
783
|
+
{ "$starts-with": ["hello", "he"] },
|
|
784
|
+
{ "$ends-with": ["hello", "lo"] },
|
|
785
|
+
{ "$upper": "abc" }, { "$lower": "ABC" },
|
|
786
|
+
{ "$string-length": "abc" }, { "$normalize-space": " a b " },
|
|
787
|
+
{ "$match": ["abc", "a.c"] }, { "$search": ["abc", "b"] },
|
|
788
|
+
{ "$replace": ["abc", "b", "x"] } ] }
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
### 8.8 Aggregates — `$count $sum $avg $min $max`
|
|
792
|
+
|
|
793
|
+
Each is unary: `{"$count": e}` etc. Aggregates consume their operand
|
|
794
|
+
sequence **as-is**: an item that is an array counts as *one* item — D4
|
|
795
|
+
unpacking is a `$for`/quantifier binding rule, not a sequence rule. (So
|
|
796
|
+
`{"$count": {"$const": [1, 2, 3]}}` is `1`, while iterating the same value
|
|
797
|
+
with `$for` yields three tuples.) See §6.7 for the `$count`
|
|
798
|
+
clause/operator collision note.
|
|
799
|
+
|
|
800
|
+
| Operator | Empty sequence | Definition |
|
|
801
|
+
|---|---|---|
|
|
802
|
+
| `$count` | `0` | The number of items in the operand's result (`fn:count`). |
|
|
803
|
+
| `$sum` | `0` | The sum of the items (`fn:sum`). Every item MUST be a number (`JQ2001`). |
|
|
804
|
+
| `$avg` | empty | The arithmetic mean of the items (`fn:avg`). Every item MUST be a number (`JQ2001`). |
|
|
805
|
+
| `$min` | empty | The least item (`fn:min`). Items MUST be all numbers or all strings (`JQ2001` otherwise, including mixed); numbers compare mathematically, strings by Unicode scalar values. A `NaN` item makes the result `NaN` (F&O). |
|
|
806
|
+
| `$max` | empty | The greatest item (`fn:max`), same rules as `$min`. |
|
|
807
|
+
|
|
808
|
+
### 8.9 Sequence operators — `$distinct $reverse $sort $head $tail $subsequence $index-of $range $get`
|
|
809
|
+
|
|
810
|
+
| Operator | Signature | Definition |
|
|
811
|
+
|---|---|---|
|
|
812
|
+
| `$distinct` | `{"$distinct": e}` | The distinct items of the operand, in first-occurrence order (`fn:distinct-values` adapted to D2 deep equality). Equality is the **grouping key relation** of §6.5: deep structural equality with `NaN` equal to itself and `-0` equal to `0` — `$distinct` and `$groupby` always agree. |
|
|
813
|
+
| `$reverse` | `{"$reverse": e}` | The operand's items in reverse order (`fn:reverse`). |
|
|
814
|
+
| `$sort` | `{"$sort": e}` | The operand's items sorted ascending by value (`fn:sort`, natural order only — key-based sorting is `$orderby`'s job). Items MUST be all numbers or all strings; anything else, or a mix, is runtime error `JQ2005` (the `$orderby` key-type rules). `NaN` orders per §6.6: equal to itself, less than every other number. The sort is stable. |
|
|
815
|
+
| `$head` | `{"$head": e}` | The first item, or empty (`fn:head`). |
|
|
816
|
+
| `$tail` | `{"$tail": e}` | Every item but the first; empty for operands of one or zero items (`fn:tail`). |
|
|
817
|
+
| `$subsequence` | `[seq, start, len?]` (2–3) | The items of *seq* at the 0-based (D6) positions selected by the `$substring` bound rules (§8.7, F&O `fn:subsequence`): `round(start) ≤ p`, and `p < round(start) + round(len)` when *len* is given. *start*/*len* MUST each be a single number (`JQ2001`). |
|
|
818
|
+
| `$index-of` | `[seq, item]` | The 0-based (D6) positions in *seq* of the items deep-equal to *item*, as a sequence, in order (`fn:index-of`). Equality is the `$eq` item relation (D2) — `NaN` matches nothing. *item* MUST be exactly one item (`JQ2001`). |
|
|
819
|
+
| `$range` | `[start, end]` | The integers from *start* to *end* **inclusive** (the XQuery `to` operator). Either operand empty → empty; *start* > *end* → empty. A non-integral or non-number operand is `JQ2001`. A result of more than 2³² items is runtime error `JQ2007` (resource guard). |
|
|
820
|
+
| `$get` | `[target, key]` | Dynamic lookup, the runtime counterpart of a path leaf: an object *target* with a string *key* yields the member value or empty; an array *target* with an integer *key* yields the element at that 0-based (D6) index — a negative index counts from the end, like the RFC 9535 index selector — or empty. **Every other combination** (wrong type pairing, non-integral index, empty or multi-item operands) is simply the empty sequence, never an error. |
|
|
821
|
+
|
|
822
|
+
Combined example (also exercises the aggregates of §8.8):
|
|
823
|
+
|
|
824
|
+
```json
|
|
825
|
+
{ "$seq": [ { "$count": "$.store.book[*]" }, { "$sum": "$.store.book[*].price" },
|
|
826
|
+
{ "$avg": "$.store.book[*].price" }, { "$min": "$.store.book[*].price" },
|
|
827
|
+
{ "$max": "$.store.book[*].price" }, { "$distinct": "$.store.book[*].category" },
|
|
828
|
+
{ "$reverse": "$.store.book[*].title" }, { "$sort": "$.store.book[*].price" },
|
|
829
|
+
{ "$head": "$.store.book[*]" }, { "$tail": "$.store.book[*]" },
|
|
830
|
+
{ "$subsequence": ["$.store.book[*]", 1, 2] },
|
|
831
|
+
{ "$index-of": ["$.store.book[*].category", "fiction"] },
|
|
832
|
+
{ "$range": [1, 5] }, { "$get": ["$.store.bicycle", "color"] } ] }
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
### 8.10 Types and casts
|
|
836
|
+
|
|
837
|
+
**Type predicates.** Unary `{"$is-string": e}`, `$is-number`,
|
|
838
|
+
`$is-boolean`, `$is-null`, `$is-array`, `$is-object` — `true` iff `e`'s
|
|
839
|
+
result is a **singleton** of that type. The empty sequence and sequences
|
|
840
|
+
of two or more items yield `false`, never an error — these are cheap
|
|
841
|
+
tests, not assertions.
|
|
842
|
+
|
|
843
|
+
**Casts.** Unary `{"$string": e}`, `{"$number": e}`, `{"$boolean": e}`.
|
|
844
|
+
`$string` and `$number` propagate the empty sequence (empty → empty); a
|
|
845
|
+
multi-item operand, or a singleton the cast table rejects, is runtime
|
|
846
|
+
error `JQ2001`.
|
|
847
|
+
|
|
848
|
+
| Cast | Definition |
|
|
849
|
+
|---|---|
|
|
850
|
+
| `$string` | string → itself; number → its shortest JavaScript serialization (`String(n)`; `NaN`/`Infinity` serialize by name); `true`/`false` → `"true"`/`"false"`; `null` → `"null"`; array or object → `JQ2001`; empty → empty. (`$concat`/`$string-join` cast items by this table, with an explicit `''` for empty operands — §8.7.) |
|
|
851
|
+
| `$number` | number → itself; string → the number it spells **iff** it is a syntactically valid JSON number (RFC 8259 grammar — no leading `+`, no bare or trailing `.`, no whitespace, no `Infinity`/`NaN`), else `JQ2001`; `true`/`false` → `1`/`0`; `null`, array, object → `JQ2001`; empty → empty. |
|
|
852
|
+
| `$boolean` | The **EBV** (§2.2) as an operator: empty → `false`, singletons per the EBV table (D3 included), a sequence of two or more items → `JQ2003`. |
|
|
853
|
+
|
|
854
|
+
| Operator | Signature | Definition |
|
|
855
|
+
|---|---|---|
|
|
856
|
+
| `$coalesce` | `{"$coalesce": [e, ...]}` (≥ 1) | The result of the first operand whose result is non-empty, else empty. Evaluation is lazy: operands after the deciding one are **not evaluated** and cannot raise errors. |
|
|
857
|
+
| `$default` | `[e, fallback]` | `e`'s result if non-empty, else *fallback*'s — sugar for `$coalesce` of exactly two. |
|
|
858
|
+
|
|
859
|
+
```json
|
|
860
|
+
{ "$seq": [ { "$is-string": "abc" }, { "$is-number": 1 }, { "$is-boolean": true },
|
|
861
|
+
{ "$is-null": null }, { "$is-array": { "$const": [1] } },
|
|
862
|
+
{ "$is-object": { "$const": {} } },
|
|
863
|
+
{ "$string": 12 }, { "$number": "12" }, { "$boolean": 1 },
|
|
864
|
+
{ "$coalesce": ["$.missing", "fallback"] },
|
|
865
|
+
{ "$default": ["$.missing", 0] } ] }
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
### 8.11 Schema operators — `$valid $assert`
|
|
869
|
+
|
|
870
|
+
**JSON Schema is this language's type system.** Where XQuery bolted XML
|
|
871
|
+
Schema onto its type lattice, this format embeds JSON Schema documents
|
|
872
|
+
directly inside query documents, as type tests and assertions. The same
|
|
873
|
+
vocabulary that validates the data validates it *inside* queries.
|
|
874
|
+
|
|
875
|
+
**Schema literals.** The second argument of `$valid`/`$assert` — like each
|
|
876
|
+
member value of the `$as` clause (§6.8) — is a **JSON Schema literal**: a
|
|
877
|
+
JSON value taken **verbatim**. It is never normalized or evaluated as a
|
|
878
|
+
query expression; JSON Schema's `$`-prefixed keywords (`$ref`, `$defs`, ...)
|
|
879
|
+
do not collide with Rule 1, `"$name"` strings inside it stay literal strings,
|
|
880
|
+
and its arrays are not array constructors. Consumers MUST deep-copy and
|
|
881
|
+
freeze the literal (like `$const`) and MUST compile it exactly once, at
|
|
882
|
+
query compile time. The format's own JSON Schema (§12) admits any JSON value
|
|
883
|
+
in schema-literal position and does **not** meta-validate it; validity of
|
|
884
|
+
the embedded schema is the type-test compiler's judgment (`JQ0009`).
|
|
885
|
+
|
|
886
|
+
**Per-item validation.** Both operators (and `$as`) validate the **items**
|
|
887
|
+
of a sequence, one at a time — the schema sees each item, never the sequence
|
|
888
|
+
itself. A sequence of three numbers validates against
|
|
889
|
+
`{"type": "number"}`, not against an array schema.
|
|
890
|
+
|
|
891
|
+
| Operator | Signature | Definition |
|
|
892
|
+
|---|---|---|
|
|
893
|
+
| `$valid` | `[expr, schema]` (exactly 2) | `true` iff **every** item of *expr*'s result satisfies *schema*; `true` over the empty sequence (vacuously, like `$every`). Never an error — the cheap test. |
|
|
894
|
+
| `$assert` | `[expr, schema]` (exactly 2) | Identity on success: returns *expr*'s result unchanged when every item satisfies *schema*. The first failing item is runtime error `JQ2008` (at the operator's `docPath`). |
|
|
895
|
+
|
|
896
|
+
```json
|
|
897
|
+
{ "$for": { "b": "$.store.book[*]" },
|
|
898
|
+
"$where": { "$valid": ["$b", { "type": "object", "required": ["isbn"] }] },
|
|
899
|
+
"$return": { "$assert": ["$b.price", { "type": "number", "minimum": 0 }] } }
|
|
900
|
+
```
|
|
901
|
+
|
|
902
|
+
**The type-test compiler hook (non-normative implementation note).** The
|
|
903
|
+
reference engine (`@jarenjs/json`) has **no dependency** on any JSON Schema
|
|
904
|
+
validator. It defines an extension point instead:
|
|
905
|
+
|
|
906
|
+
```
|
|
907
|
+
compileJsonQuery(doc, { compileTypeTest: (schemaJson, docPath) => (value => boolean) })
|
|
908
|
+
```
|
|
909
|
+
|
|
910
|
+
The hook is invoked once per schema literal at **query compile time**, with
|
|
911
|
+
the frozen literal and its RFC 6901 pointer; it returns the hot-path item
|
|
912
|
+
predicate the compiled query closes over. `@jarenjs/validate/query` exports
|
|
913
|
+
`createTypeTestCompiler(validatorOrFactory?)`, which compiles literals with
|
|
914
|
+
a `JarenValidator` (boolean mode, errors off) — supplying an instance with
|
|
915
|
+
registered schemas lets `$ref`s in query schema literals resolve against
|
|
916
|
+
them. The dependency direction is validate → json; any conforming validator
|
|
917
|
+
can implement the hook. A query using `$valid`/`$assert`/`$as` compiled
|
|
918
|
+
**without** a hook is compile error `JQ0008`; a hook that rejects a schema
|
|
919
|
+
literal (throws) is compile error `JQ0009` at the operator's `docPath`.
|
|
920
|
+
|
|
921
|
+
The same package closes the loop in the other direction: its `$query`
|
|
922
|
+
schema keyword embeds a query document inside a JSON Schema and asserts the
|
|
923
|
+
query's EBV (§2.2) against each validated instance — schemas inside queries
|
|
924
|
+
here, queries inside schemas there. Compiled queries expose `query.ebv`
|
|
925
|
+
beside `first`/`exists` for exactly this. See the `@jarenjs/validate`
|
|
926
|
+
README's "`$query` — cross-field assertions" section.
|
|
927
|
+
|
|
928
|
+
---
|
|
929
|
+
|
|
930
|
+
## 9. Variables, scoping, and external parameters
|
|
931
|
+
|
|
932
|
+
1. Variables are introduced by `$for`, `$let`, `$at`, `$count`, `$groupby`
|
|
933
|
+
key names, and quantifier bindings. A variable is in scope in every
|
|
934
|
+
*later* clause of its phrase (per the semantic order of §6.1), in later
|
|
935
|
+
bindings of the same binding object (§6.2), and in all nested expressions
|
|
936
|
+
there — but **not** inside embedded path filters (§3.2).
|
|
937
|
+
2. Inner bindings shadow outer bindings of the same name. Duplicate bindings
|
|
938
|
+
within one phrase are `JQ0007` (§6.3).
|
|
939
|
+
3. **Externals.** A variable referenced but not bound by any enclosing phrase
|
|
940
|
+
is an **external parameter**. The set of external names is collected at
|
|
941
|
+
compile time and exposed by the compiled query; callers bind externals at
|
|
942
|
+
call time. Evaluating a reference to an external that the caller did not
|
|
943
|
+
bind is runtime error `JQ2006`. (There is no declaration syntax; use is
|
|
944
|
+
the declaration. Consumers MAY offer static "all externals bound" checks.)
|
|
945
|
+
|
|
946
|
+
```json
|
|
947
|
+
{ "$query": "0.1",
|
|
948
|
+
"$expr": { "$for": { "b": "$.store.book[*]" },
|
|
949
|
+
"$where": { "$ge": ["$b.price", "$minPrice"] },
|
|
950
|
+
"$return": "$b.title" } }
|
|
951
|
+
```
|
|
952
|
+
|
|
953
|
+
`$minPrice` is free — an external the caller binds at call time.
|
|
954
|
+
|
|
955
|
+
---
|
|
956
|
+
|
|
957
|
+
## 10. Errors
|
|
958
|
+
|
|
959
|
+
### 10.1 Error objects
|
|
960
|
+
|
|
961
|
+
Consumers MUST raise compile-time errors as `JsonQueryCompileError` and
|
|
962
|
+
runtime errors as `JsonQueryRuntimeError`. Every error carries:
|
|
963
|
+
|
|
964
|
+
- `code` — a stable identifier from the registry below;
|
|
965
|
+
- `docPath` — an **RFC 6901 JSON Pointer into the query document** locating
|
|
966
|
+
the offending construct (e.g. `/$expr/$where/$eq/1`);
|
|
967
|
+
- `message` — human-readable, non-normative.
|
|
968
|
+
|
|
969
|
+
### 10.2 Compile errors (`JQ0xxx`)
|
|
970
|
+
|
|
971
|
+
| Code | Condition | XQuery analogue (non-normative) |
|
|
972
|
+
|---|---|---|
|
|
973
|
+
| `JQ0001` | Object mixes `$`-prefixed and plain keys (§3.1) | XPST0003 |
|
|
974
|
+
| `JQ0002` | Unknown operator / `$`-key outside the vocabulary | XPST0017 |
|
|
975
|
+
| `JQ0003` | Known phrase with bad arity, value shape, or key combination | XPST0003 |
|
|
976
|
+
| `JQ0004` | String starting `$` is not a valid path or escape (§3.2) | XPST0003 |
|
|
977
|
+
| `JQ0005` | Variable reference that is neither bound nor collectible as an external (reserved for closed-world compilation modes; see §9); also an `$as` member naming a variable not bound by its phrase's `$for`/`$let` (§6.8) | XPST0008 |
|
|
978
|
+
| `JQ0006` | Version envelope with unknown or non-string `$query` (§4.2) | XQST0031 |
|
|
979
|
+
| `JQ0007` | Duplicate variable binding within one phrase (§6.3) | XQST0089 |
|
|
980
|
+
| `JQ0008` | Schema operator (`$valid`/`$assert`/`$as`) in a query compiled without a type-test compiler (§8.11) | XQST0009 |
|
|
981
|
+
| `JQ0009` | Schema literal rejected by the type-test compiler (invalid embedded schema, §8.11) | XQST0059 |
|
|
982
|
+
|
|
983
|
+
### 10.3 Runtime errors (`JQ2xxx`)
|
|
984
|
+
|
|
985
|
+
| Code | Condition | XQuery analogue (non-normative) |
|
|
986
|
+
|---|---|---|
|
|
987
|
+
| `JQ2001` | Runtime type error (non-number arithmetic operand, bad cast, ...) | XPTY0004 |
|
|
988
|
+
| `JQ2002` | `$idiv`/`$mod` by zero (§8.5) | FOAR0001 |
|
|
989
|
+
| `JQ2003` | EBV of a multi-item sequence (§2.2) | FORG0006 |
|
|
990
|
+
| `JQ2004` | `$map` key expression not a single string (§3.5.2) | XPTY0004 |
|
|
991
|
+
| `JQ2005` | Incomparable `$orderby`/`$sort` keys (§6.6, §8.9) | XPTY0004 |
|
|
992
|
+
| `JQ2006` | Reference to an unbound external parameter (§9) | XPDY0002 |
|
|
993
|
+
| `JQ2007` | Resource guard: an operator result exceeding an implementation limit (`$range` over 2³² items, §8.9) | XPDY0130 |
|
|
994
|
+
| `JQ2008` | Schema assertion failure: an item rejected by `$assert`'s schema, or a bound variable rejected by its `$as` schema (§6.8, §8.11) | XPTY0004 |
|
|
995
|
+
|
|
996
|
+
---
|
|
997
|
+
|
|
998
|
+
## 11. Deviations from XQuery 3.1
|
|
999
|
+
|
|
1000
|
+
Normative registry. Everything not listed here follows XQuery 3.1.
|
|
1001
|
+
|
|
1002
|
+
| # | Deviation | Rationale |
|
|
1003
|
+
|---|---|---|
|
|
1004
|
+
| **D1** | All numbers are IEEE 754 doubles; no integer/decimal/float tower. `$div` by 0 is IEEE (`±Infinity`/`NaN`); `$idiv`/`$mod` by 0 error. | JSON has one number type; JavaScript engines have one. A numeric tower would be a fiction the data model cannot honor. |
|
|
1005
|
+
| **D2** | `$eq`/`$ne` item equality is deep structural JSON equality (arrays/objects compare by structure), numbers mathematically. Existential lifting over sequences is retained. | XQuery's `eq` on maps/arrays is an error; for JSON, structural equality is the useful, obvious relation (and `$groupby`/`$distinct` need it anyway). |
|
|
1006
|
+
| **D3** | EBV of a singleton array or object is `true`. | XQuery raises FORG0006 for function items; for JSON data, "the object is there" is the intuitive truthiness. Keeps EBV total over singletons. |
|
|
1007
|
+
| **D4** | `$for` unpacks an item that is an array into its members (one level). Objects are not unpacked. | RFC 9535 paths return arrays as values; without unpacking, `$for` over `"$b.tags"` would iterate one array item. Escape: `$let` / `$const`. |
|
|
1008
|
+
| **D5** | Regex operators use I-Regexp (RFC 9485), not XSD regular expressions. No flags argument. | Matches RFC 9535's regex dialect (one regex language across path filters and operators) and is interoperable by design. |
|
|
1009
|
+
| **D6** | Positional values are 0-based: `$at`, `$count` (clause), `$substring`/`$subsequence` starts, `$index-of` results, `$get` indexes. | JSON and RFC 9535 array indexes are 0-based; a 1-based island inside them invites off-by-one errors. |
|
|
1010
|
+
| **D7** | FLWOR clause keys apply in the fixed semantic order of §6.1 regardless of document key order; clause interleavings are expressed by nesting. One `$where`/`$groupby`/`$orderby`/`$count` per phrase. | JSON key order is not interoperable across ecosystems; semantics must not depend on it. Nesting expresses every interleaving. See also the correlation interop caveat, §6.2. |
|
|
1011
|
+
|
|
1012
|
+
---
|
|
1013
|
+
|
|
1014
|
+
## 12. The JSON Schema for query documents
|
|
1015
|
+
|
|
1016
|
+
### 12.1 Artifacts and draft policy
|
|
1017
|
+
|
|
1018
|
+
The complete structural grammar of this format is published as JSON Schema,
|
|
1019
|
+
in two artifacts:
|
|
1020
|
+
|
|
1021
|
+
- `packages/json/schemas/jaren-query.schema.json` — **canonical**, draft
|
|
1022
|
+
2020-12, `$id` `https://jarenjs.dev/schemas/jaren-query/0.1`.
|
|
1023
|
+
- `packages/json/schemas/jaren-query.draft-07.schema.json` — the draft-07
|
|
1024
|
+
twin, `$id` `https://jarenjs.dev/schemas/jaren-query/0.1/draft-07`.
|
|
1025
|
+
|
|
1026
|
+
Nothing in this format requires post-draft-07 keywords, so the canonical
|
|
1027
|
+
schema is authored in a **draft-neutral keyword subset** and the twin is a
|
|
1028
|
+
**mechanical derivation**: swap `$schema`, rename `$defs` →
|
|
1029
|
+
`definitions`, rewrite `#/$defs/` ref targets, suffix the `$id` with
|
|
1030
|
+
`/draft-07`. The subset rules (normative for schema maintenance):
|
|
1031
|
+
|
|
1032
|
+
- no keywords beside `$ref` in any schema object that has `$ref` (draft-07
|
|
1033
|
+
ignores `$ref` siblings; wrap in `allOf` where composition is needed);
|
|
1034
|
+
- no `unevaluatedProperties` / `unevaluatedItems`;
|
|
1035
|
+
- no `$dynamicRef` / `$dynamicAnchor`;
|
|
1036
|
+
- no tuple validation (2020-12 `prefixItems` and draft-07 array-form `items`
|
|
1037
|
+
are mutually incompatible) — operator argument arrays validate with uniform
|
|
1038
|
+
`items` plus `minItems`/`maxItems`. Nothing is lost: every argument
|
|
1039
|
+
position is an expression anyway, and per-position facts ("this key must
|
|
1040
|
+
evaluate to a string") are runtime rules owned by the compiler (`JQ2xxx`),
|
|
1041
|
+
not structure;
|
|
1042
|
+
- allowed keywords: `type`, `oneOf`/`anyOf`/`allOf`, `properties`,
|
|
1043
|
+
`required`, `additionalProperties`, `patternProperties`, `propertyNames`,
|
|
1044
|
+
`pattern`, `enum`/`const`, boolean schemas, `minProperties`/
|
|
1045
|
+
`maxProperties`, `minItems`/`maxItems`, uniform `items`, `format`,
|
|
1046
|
+
recursive `$ref` — all draft-06+ with identical semantics in both drafts.
|
|
1047
|
+
|
|
1048
|
+
### 12.2 What the schema does and does not enforce
|
|
1049
|
+
|
|
1050
|
+
The schema is **structural** validation; the compiler remains authoritative.
|
|
1051
|
+
The schema enforces: the object partition rule (a map constructor admits no
|
|
1052
|
+
`$`-prefixed key, an operator phrase admits only its own keys — a mixed
|
|
1053
|
+
object matches neither and fails), the closed operator vocabulary and all
|
|
1054
|
+
arities, binding-object shapes and variable-name lexemes, the envelope, and
|
|
1055
|
+
the string forms of §3.2 (query-string head patterns; absolute paths
|
|
1056
|
+
additionally carry `format: "json-path"` — the RFC 9535 format implemented by
|
|
1057
|
+
`@jarenjs/formats`; note `format` is annotation-only by default from draft
|
|
1058
|
+
2020-12 unless the validator enables assertion).
|
|
1059
|
+
|
|
1060
|
+
The schema cannot express, and therefore leaves to the compiler (stated in
|
|
1061
|
+
`description`s in the artifacts): fixed clause ordering (semantic, not
|
|
1062
|
+
structural — every key order is valid JSON), variable scoping and duplicate
|
|
1063
|
+
detection (`JQ0005`/`JQ0007`), `$as` name binding (`JQ0005`), full grammar
|
|
1064
|
+
of variable-rooted path segments (only the head is pattern-checked), and all
|
|
1065
|
+
runtime typing rules. Schema-literal positions (§8.11) validate as `true` —
|
|
1066
|
+
draft-neutral by definition; embedded JSON Schemas are deliberately **not**
|
|
1067
|
+
meta-validated by these artifacts (the type-test compiler is authoritative,
|
|
1068
|
+
`JQ0009`). Where the schema and this text disagree, this text wins and the
|
|
1069
|
+
schema has a bug.
|
|
1070
|
+
|
|
1071
|
+
---
|
|
1072
|
+
|
|
1073
|
+
## Appendix A. Worked examples (normative fixtures)
|
|
1074
|
+
|
|
1075
|
+
Each example is committed verbatim as a fixture under
|
|
1076
|
+
`test/json/fixtures/query-format/valid/` and MUST validate against both
|
|
1077
|
+
schema artifacts. Examples run against the RFC 9535 bookstore document
|
|
1078
|
+
(plus a `ratings` array where shown).
|
|
1079
|
+
|
|
1080
|
+
### A.1 Degenerate query — a bare JSONPath string
|
|
1081
|
+
|
|
1082
|
+
```json
|
|
1083
|
+
"$.store.book[?@.price < 10].title"
|
|
1084
|
+
```
|
|
1085
|
+
|
|
1086
|
+
The complete query document. Result: the titles of the cheap books, in
|
|
1087
|
+
document order.
|
|
1088
|
+
|
|
1089
|
+
### A.2 Bookstore FLWOR
|
|
1090
|
+
|
|
1091
|
+
```json
|
|
1092
|
+
{ "$for": { "b": "$.store.book[*]" },
|
|
1093
|
+
"$where": { "$lt": ["$b.price", 10] },
|
|
1094
|
+
"$orderby": "$b.price",
|
|
1095
|
+
"$return": { "title": "$b.title", "price": "$b.price" } }
|
|
1096
|
+
```
|
|
1097
|
+
|
|
1098
|
+
One object per cheap book, cheapest first.
|
|
1099
|
+
|
|
1100
|
+
### A.3 Join
|
|
1101
|
+
|
|
1102
|
+
```json
|
|
1103
|
+
{ "$for": { "b": "$.store.book[*]", "r": "$.ratings[*]" },
|
|
1104
|
+
"$where": { "$eq": ["$b.isbn", "$r.isbn"] },
|
|
1105
|
+
"$orderby": "$b.price",
|
|
1106
|
+
"$return": { "title": "$b.title", "stars": "$r.stars" } }
|
|
1107
|
+
```
|
|
1108
|
+
|
|
1109
|
+
Nested iteration over books × ratings; the `$where` equijoin keeps matching
|
|
1110
|
+
pairs (books without `isbn` produce the empty sequence on the left — the
|
|
1111
|
+
existential `$eq` is false, so they drop out; contrast §5.2).
|
|
1112
|
+
|
|
1113
|
+
### A.4 Group + aggregate
|
|
1114
|
+
|
|
1115
|
+
```json
|
|
1116
|
+
{ "$for": { "b": "$.store.book[*]" },
|
|
1117
|
+
"$groupby": { "genre": "$b.category" },
|
|
1118
|
+
"$return": { "genre": "$genre",
|
|
1119
|
+
"count": { "$count": "$b" },
|
|
1120
|
+
"avg": { "$avg": "$b.price" } } }
|
|
1121
|
+
```
|
|
1122
|
+
|
|
1123
|
+
Books per genre: after grouping, `$b` is the group's book sequence (§6.5).
|
|
1124
|
+
|
|
1125
|
+
### A.5 `$let` + reshaping with computed keys
|
|
1126
|
+
|
|
1127
|
+
```json
|
|
1128
|
+
{ "$let": { "prefix": "col_", "book": "$.store.book[0]" },
|
|
1129
|
+
"$return": { "$map": [
|
|
1130
|
+
[{ "$concat": ["$prefix", "title"] }, "$book.title"],
|
|
1131
|
+
[{ "$concat": ["$prefix", "price"] }, "$book.price"]
|
|
1132
|
+
] } }
|
|
1133
|
+
```
|
|
1134
|
+
|
|
1135
|
+
### A.6 `$const` and `$$` escapes in one document
|
|
1136
|
+
|
|
1137
|
+
```json
|
|
1138
|
+
{ "template": { "$const": { "$for": "kept verbatim", "price": null } },
|
|
1139
|
+
"label": "$$price" }
|
|
1140
|
+
```
|
|
1141
|
+
|
|
1142
|
+
A map constructor whose `template` member is quoted data (the inner `$for`
|
|
1143
|
+
is never interpreted) and whose `label` member is the literal string
|
|
1144
|
+
`"$price"`.
|
|
1145
|
+
|
|
1146
|
+
### A.7 Quantifier over a variable-rooted path
|
|
1147
|
+
|
|
1148
|
+
```json
|
|
1149
|
+
{ "$let": { "books": "$.store.book[*]" },
|
|
1150
|
+
"$return": { "$some": { "b": "$books" },
|
|
1151
|
+
"$satisfies": { "$gt": ["$b.price", 20] } } }
|
|
1152
|
+
```
|
|
1153
|
+
|
|
1154
|
+
### A.8 External parameter
|
|
1155
|
+
|
|
1156
|
+
```json
|
|
1157
|
+
{ "$query": "0.1",
|
|
1158
|
+
"$expr": { "$for": { "b": "$.store.book[*]" },
|
|
1159
|
+
"$where": { "$ge": ["$b.price", "$minPrice"] },
|
|
1160
|
+
"$return": "$b.title" } }
|
|
1161
|
+
```
|
|
1162
|
+
|
|
1163
|
+
`$minPrice` is an external (§9), bound by the caller; also demonstrates the
|
|
1164
|
+
version envelope.
|
|
1165
|
+
|
|
1166
|
+
### A.9 Schema type tests — `$as` and `$valid`
|
|
1167
|
+
|
|
1168
|
+
```json
|
|
1169
|
+
{ "$for": { "b": "$.store.book[?@.isbn]", "r": "$.ratings[*]" },
|
|
1170
|
+
"$as": { "b": { "type": "object", "required": ["isbn", "price"] } },
|
|
1171
|
+
"$where": { "$and": [
|
|
1172
|
+
{ "$eq": ["$b.isbn", "$r.isbn"] },
|
|
1173
|
+
{ "$valid": ["$r.stars", { "type": "number", "minimum": 0, "maximum": 5 }] }
|
|
1174
|
+
] },
|
|
1175
|
+
"$orderby": "$b.price",
|
|
1176
|
+
"$return": { "title": "$b.title", "stars": "$r.stars" } }
|
|
1177
|
+
```
|
|
1178
|
+
|
|
1179
|
+
The join of A.3 with JSON Schema as the type system (§6.8, §8.11): the path
|
|
1180
|
+
filter pre-selects books that have an `isbn`, the `$as` clause *asserts*
|
|
1181
|
+
that every joined `b` is an object carrying `isbn` and `price` (a violation
|
|
1182
|
+
would be `JQ2008`, not a dropped tuple), and the `$valid` conjunct *filters*
|
|
1183
|
+
rating pairs to plausible star values. Note the schema literals are verbatim
|
|
1184
|
+
JSON Schema — their keywords are not query operators.
|
|
1185
|
+
|
|
1186
|
+
---
|
|
1187
|
+
|
|
1188
|
+
## Appendix B. LLM structured output (non-normative)
|
|
1189
|
+
|
|
1190
|
+
The entire language is one JSON Schema. That is not an implementation detail;
|
|
1191
|
+
it is a headline feature.
|
|
1192
|
+
|
|
1193
|
+
Constrained decoding — the "structured output" mode of every major LLM
|
|
1194
|
+
serving stack — takes a JSON Schema and makes it impossible for the model to
|
|
1195
|
+
emit a token that leads outside the schema. Point that machinery at
|
|
1196
|
+
`jaren-query.schema.json` and a model **cannot produce a structurally invalid
|
|
1197
|
+
query**: no unknown operators, no three-argument `$eq`, no mixed
|
|
1198
|
+
`$`/plain-key objects, no malformed version envelope. The closed vocabulary,
|
|
1199
|
+
exact arities, and anchored patterns in the schema were designed with this
|
|
1200
|
+
consumer in mind — every constraint the schema can express is one hallucination
|
|
1201
|
+
class removed at generation time, before any code runs.
|
|
1202
|
+
|
|
1203
|
+
The residue is small and cheap to check: path-string grammar beyond the
|
|
1204
|
+
anchored head patterns (fully validated where the `json-path` format is
|
|
1205
|
+
asserted, since absolute path leaves carry `format: "json-path"`), variable
|
|
1206
|
+
scoping, and the runtime typing rules — exactly the `JQ0xxx`/`JQ2xxx` errors
|
|
1207
|
+
of §10, which arrive with a `docPath` pointer that can be fed straight back
|
|
1208
|
+
to the model for repair. Compare the usual alternative — asking a model to
|
|
1209
|
+
emit a bespoke query DSL as free text and parsing it hopefully — and the
|
|
1210
|
+
trade is: grammar errors eliminated by construction, semantic errors reduced
|
|
1211
|
+
to a machine-checkable, machine-repairable residue.
|
|
1212
|
+
|
|
1213
|
+
Because query documents are plain JSON, they also travel well through the
|
|
1214
|
+
rest of an LLM toolchain: function-call arguments, retrieval filters, and
|
|
1215
|
+
audit logs all speak JSON already, and a generated query can be validated,
|
|
1216
|
+
diffed, stored, and replayed without ever touching a parser.
|
|
1217
|
+
|
|
1218
|
+
The same constrained-decoding model applies to complete recursive
|
|
1219
|
+
stylesheets; see
|
|
1220
|
+
[JSLT-FORMAT Appendix B](./JSLT-FORMAT.md#appendix-b-llm-structured-output-non-normative)
|
|
1221
|
+
and its mechanically query-derived schema twins.
|