@jarenjs/json 0.9.2 → 0.34.0
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 +86 -13
- package/README.md +248 -23
- package/dist/types/canonical.d.ts +37 -0
- package/dist/types/cow.d.ts +28 -0
- package/dist/types/errors.d.ts +45 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/jslt/errors.d.ts +15 -8
- package/dist/types/jslt/index.d.ts +22 -0
- package/dist/types/jslt/packs/finance.d.ts +119 -0
- package/dist/types/jslt/packs/index.d.ts +310 -0
- package/dist/types/jslt/packs/math.d.ts +159 -0
- package/dist/types/jslt/packs/stats.d.ts +48 -0
- package/dist/types/jslt/registry.d.ts +65 -0
- package/dist/types/jtlt/errors.d.ts +3 -6
- package/dist/types/option-variants.d.ts +29 -0
- package/dist/types/patch.d.ts +214 -0
- package/dist/types/path.d.ts +139 -9
- package/dist/types/pointer.d.ts +100 -9
- package/dist/types/query/compile.d.ts +12 -0
- package/dist/types/query/errors.d.ts +72 -8
- package/dist/types/query/index.d.ts +317 -25
- package/dist/types/query/normalize.d.ts +24 -0
- package/dist/types/query/operators.d.ts +241 -1
- package/dist/types/query/runtime.d.ts +5 -8
- package/dist/types/query/types.d.ts +34 -0
- package/dist/types/segments.d.ts +31 -0
- package/dist/types/write.d.ts +204 -0
- package/dist/types/xquery/parse.d.ts +2 -3
- package/docs/JSLT-FORMAT.md +74 -3
- package/docs/JSLT-PRELUDE.md +1 -1
- package/docs/QUERY-FORMAT.md +695 -33
- package/package.json +18 -4
- package/schemas/geojson.draft-07.schema.json +323 -0
- package/schemas/geojson.jaren.schema.json +863 -0
- package/schemas/geojson.schema.json +172 -0
- package/schemas/jaren-jslt.authoring.schema.json +142 -0
- package/schemas/jaren-jslt.draft-07.schema.json +152 -11
- package/schemas/jaren-jslt.llm-profile.schema.json +782 -0
- package/schemas/jaren-jslt.schema.json +152 -11
- package/schemas/jaren-query.draft-07.schema.json +152 -11
- package/schemas/jaren-query.llm-profile.schema.json +619 -0
- package/schemas/jaren-query.schema.json +82 -15
- package/src/basic.js +1 -1
- package/src/canonical.js +170 -0
- package/src/cow.js +106 -0
- package/src/errors.js +68 -0
- package/src/index.js +3 -0
- package/src/jslt/dispatch.js +178 -28
- package/src/jslt/errors.js +19 -14
- package/src/jslt/index.js +37 -29
- package/src/jslt/packs/finance.js +49 -0
- package/src/jslt/packs/index.js +18 -0
- package/src/jslt/packs/math.js +46 -0
- package/src/jslt/packs/stats.js +65 -0
- package/src/jslt/registry.js +200 -0
- package/src/jslt/stylesheet.js +14 -23
- package/src/jtlt/desugar.js +2 -3
- package/src/jtlt/errors.js +6 -12
- package/src/jtlt/index.js +12 -29
- package/src/jtlt/template.js +9 -18
- package/src/option-variants.js +54 -0
- package/src/patch.js +1052 -0
- package/src/path.js +319 -52
- package/src/pointer.js +225 -44
- package/src/query/compile.js +790 -75
- package/src/query/errors.js +72 -12
- package/src/query/index.js +274 -42
- package/src/query/normalize.js +489 -78
- package/src/query/operators.js +620 -23
- package/src/query/runtime.js +5 -19
- package/src/query/types.js +213 -0
- package/src/segments.js +409 -64
- package/src/write.js +660 -0
- package/src/xquery/parse.js +37 -53
package/docs/QUERY-FORMAT.md
CHANGED
|
@@ -78,6 +78,13 @@ Two conformance roles exist:
|
|
|
78
78
|
5. A sequence of exactly one item is identified with that item ("singleton ≡
|
|
79
79
|
item"). A literal `42` and a one-item sequence containing `42` are
|
|
80
80
|
indistinguishable.
|
|
81
|
+
6. The **input document** a query is applied to MUST be an item — that is, a
|
|
82
|
+
JSON value. An implementation is NOT required to verify this (a deep check
|
|
83
|
+
would cost a full walk per call), so a non-JSON value passed in may simply
|
|
84
|
+
flow through as an opaque item. It MUST, however, reject a *missing*
|
|
85
|
+
document (JavaScript `undefined`) with `JQ2011`: a host API that spends
|
|
86
|
+
the absent value on the empty sequence would otherwise answer "empty" for
|
|
87
|
+
a query whose existence test says otherwise.
|
|
81
88
|
|
|
82
89
|
### 2.2 Effective boolean value (EBV)
|
|
83
90
|
|
|
@@ -190,11 +197,15 @@ evaluating to the empty sequence contributes none.
|
|
|
190
197
|
Against a store with four book prices, this constructs a single 7-element
|
|
191
198
|
array: `1`, the four prices, `2`, `3`.
|
|
192
199
|
|
|
193
|
-
|
|
194
|
-
constructor
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
200
|
+
This flattening is a property of the sequence data model, not of the array
|
|
201
|
+
constructor alone; it applies in every expression position where a sequence can
|
|
202
|
+
flow. A **map-constructor member value** is the one position that cannot flatten
|
|
203
|
+
— a JSON member holds exactly one value — so it takes the single-value
|
|
204
|
+
cardinality rule instead: a member value that evaluates to the empty sequence
|
|
205
|
+
**omits the member**, a singleton becomes the member's value, and a value of two
|
|
206
|
+
or more items is runtime error `JQ2001`. To embed an array *as a value* without
|
|
207
|
+
evaluation, use `$const` (§3.5.1); to bind one without iteration, use `$let`
|
|
208
|
+
(§6.3).
|
|
198
209
|
|
|
199
210
|
### 3.5 Escape hatches
|
|
200
211
|
|
|
@@ -226,9 +237,12 @@ order:
|
|
|
226
237
|
|
|
227
238
|
- *keyExpr* MUST evaluate to a single string; any other result (empty
|
|
228
239
|
sequence, non-string, multi-item sequence) is runtime error `JQ2004`.
|
|
229
|
-
- *valueExpr* is evaluated
|
|
230
|
-
|
|
231
|
-
|
|
240
|
+
- *valueExpr* is evaluated and taken as the member value under §3.4's
|
|
241
|
+
member-value cardinality: an empty result **omits the pair**, a singleton is
|
|
242
|
+
the value, and two or more items are runtime error `JQ2001`.
|
|
243
|
+
- Later pairs win on duplicate keys — but because an omitted (empty-valued) pair
|
|
244
|
+
is never written, an earlier pair for the same key **survives** a later pair
|
|
245
|
+
for that key whose value is the empty sequence.
|
|
232
246
|
|
|
233
247
|
Each pair MUST be an array of exactly two expressions (`JQ0003` otherwise).
|
|
234
248
|
An empty pair list constructs the empty object.
|
|
@@ -296,6 +310,17 @@ this, and later work uses it for static cardinality checks.
|
|
|
296
310
|
An absolute path in any expression position reads from the one input
|
|
297
311
|
document; there is no context-item drift — `$` is always the document root.
|
|
298
312
|
|
|
313
|
+
> **Footgun (variable-rooted filters).** A filter selector filters the
|
|
314
|
+
> *children* of each node it is applied to (RFC 9535 §2.3.5). So a filter
|
|
315
|
+
> written directly against a `$let`/`$for` variable tests that item's members,
|
|
316
|
+
> not the item itself. Given `{"$let": {"books": "$.store.book[*]"}}`, the path
|
|
317
|
+
> `"$books[?@.price > 20]"` applies the filter to the *members* of each book
|
|
318
|
+
> object and asks each member for a `.price` child — no member has one, so every
|
|
319
|
+
> book contributes the empty sequence and the whole path is empty. To filter the
|
|
320
|
+
> books themselves, place the predicate one level up, where the books are the
|
|
321
|
+
> children being filtered — `"$.store.book[?@.price > 20]"` — or iterate with
|
|
322
|
+
> `$for` and test in `$where` (§6.4).
|
|
323
|
+
|
|
299
324
|
### 5.2 The two filter dialects
|
|
300
325
|
|
|
301
326
|
Two boolean mini-languages coexist in this format, and both keep the exact
|
|
@@ -340,13 +365,14 @@ choose the dialect deliberately when absent members are possible.
|
|
|
340
365
|
|
|
341
366
|
### 6.1 Shape and clause order
|
|
342
367
|
|
|
343
|
-
A FLWOR phrase is an operator phrase containing `$for` and/or
|
|
344
|
-
optional clauses, plus the REQUIRED `$return`:
|
|
368
|
+
A FLWOR phrase is an operator phrase containing `$fold`, `$for` and/or
|
|
369
|
+
`$let`, plus optional clauses, plus the REQUIRED `$return`:
|
|
345
370
|
|
|
346
371
|
| Key | Value | Presence |
|
|
347
372
|
|---|---|---|
|
|
348
|
-
| `$
|
|
349
|
-
| `$
|
|
373
|
+
| `$fold` | one-member accumulator binding (§6.9) | at least one of `$fold`, `$for`, `$let` |
|
|
374
|
+
| `$for` | binding object (§6.2) | at least one of `$fold`, `$for`, `$let` |
|
|
375
|
+
| `$let` | binding object (§6.3) | at least one of `$fold`, `$for`, `$let` |
|
|
350
376
|
| `$as` | schema assertion object (§6.8) | OPTIONAL |
|
|
351
377
|
| `$where` | expression | OPTIONAL |
|
|
352
378
|
| `$groupby` | binding object (§6.5) | OPTIONAL |
|
|
@@ -358,7 +384,7 @@ The clauses apply in **fixed semantic order regardless of their order in the
|
|
|
358
384
|
JSON document** (**D7**):
|
|
359
385
|
|
|
360
386
|
```
|
|
361
|
-
$for → $let → $as → $where → $groupby → $orderby → $count → $return
|
|
387
|
+
$fold → $for → $let → $as → $where → $groupby → $orderby → $count → $return
|
|
362
388
|
```
|
|
363
389
|
|
|
364
390
|
JSON key order is not interoperable — several ecosystems (e.g. Go maps)
|
|
@@ -377,7 +403,9 @@ which is standard XQuery practice anyway:
|
|
|
377
403
|
```
|
|
378
404
|
|
|
379
405
|
The FLWOR phrase evaluates to the concatenation of the `$return` results over
|
|
380
|
-
the surviving tuple stream, in tuple order
|
|
406
|
+
the surviving tuple stream, in tuple order — unless the phrase has a `$fold`
|
|
407
|
+
clause (§6.9), in which case it evaluates to the final accumulator and
|
|
408
|
+
`$return` names the accumulator's next value instead of an output item.
|
|
381
409
|
|
|
382
410
|
### 6.2 `$for` — iteration bindings
|
|
383
411
|
|
|
@@ -395,8 +423,8 @@ array, `$for` unpacks it into its members — one level only — and iterates
|
|
|
395
423
|
those. This is the ergonomic default for the JSON data model, where `[*]`
|
|
396
424
|
already produced values, not nodes. To bind an array *as a value*, use `$let`
|
|
397
425
|
or `{"$const": [...]}`. Objects are NOT auto-iterated; iterate an object's
|
|
398
|
-
values with a `[*]` path segment
|
|
399
|
-
|
|
426
|
+
values with a `[*]` path segment, or its member pairs with the `$entries`
|
|
427
|
+
operator (§8.9).
|
|
400
428
|
|
|
401
429
|
**Multiple bindings** nest left-to-right in document key order, exactly like
|
|
402
430
|
consecutive XQuery `for` clauses, and **may be correlated**: a later source
|
|
@@ -407,16 +435,18 @@ may reference variables bound earlier in the same `$for` object.
|
|
|
407
435
|
> key-order-hostile stacks SHOULD emit one binding per phrase, nesting
|
|
408
436
|
> phrases, instead of relying on multi-key binding objects.
|
|
409
437
|
|
|
410
|
-
**Extended binding form** — a source written as
|
|
438
|
+
**Extended binding form** — a source written as an object with an `$in`
|
|
439
|
+
member iterates `$in` like a plain source, with options:
|
|
411
440
|
|
|
412
441
|
```json
|
|
413
442
|
{ "$in": expr, "$at": "posName" }
|
|
414
443
|
```
|
|
415
444
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
445
|
+
binds *posName* to the **0-based** (D6) position of the current item within
|
|
446
|
+
the iterated sequence. `$in` is the only REQUIRED key (so a bare
|
|
447
|
+
`{"$in": expr}` is just the long spelling of the plain form); *posName* MUST
|
|
448
|
+
be a valid variable name. The two remaining options — `$allowing-empty` and
|
|
449
|
+
the `$window` family — are §6.10.
|
|
420
450
|
|
|
421
451
|
```json
|
|
422
452
|
{ "$for": { "b": { "$in": "$.store.book[*]", "$at": "i" } },
|
|
@@ -462,10 +492,14 @@ XQuery 3.1 group-by semantics:
|
|
|
462
492
|
|
|
463
493
|
- Each *keyExpr* is evaluated per tuple; each *name* becomes a
|
|
464
494
|
**grouping-key variable**, bound in every subsequent clause to that group's
|
|
465
|
-
key value (a singleton per group).
|
|
495
|
+
key value (a singleton per group). A *keyExpr* MUST evaluate to the empty
|
|
496
|
+
sequence or a single item; a two-or-more-item key is runtime error `JQ2001`.
|
|
497
|
+
An **empty key is allowed** and groups with the other empty keys.
|
|
466
498
|
- Tuples with equal key combinations form one group. Key equality is **deep
|
|
467
|
-
structural JSON equality** (`equalsJson`-grade,
|
|
468
|
-
item equality
|
|
499
|
+
structural JSON equality** (`equalsJson`-grade), with numbers compared
|
|
500
|
+
mathematically. Unlike `$eq` item equality (§8.4), grouping treats **`NaN` as
|
|
501
|
+
equal to itself** (the XQuery grouping rule; §8.9's `$distinct` and `$sort`
|
|
502
|
+
cite the same relation).
|
|
469
503
|
- Every other variable bound in the phrase is **rebound to the sequence** of
|
|
470
504
|
its values across the group's tuples, in tuple order.
|
|
471
505
|
- The tuple stream after `$groupby` has one tuple per group, in order of
|
|
@@ -497,11 +531,21 @@ A *keySpec* is either an expression (shorthand for ascending, empty-least) or
|
|
|
497
531
|
the explicit form
|
|
498
532
|
|
|
499
533
|
```json
|
|
500
|
-
{ "$key": expr, "$dir": "asc" | "desc", "$empty": "least" | "greatest"
|
|
534
|
+
{ "$key": expr, "$dir": "asc" | "desc", "$empty": "least" | "greatest",
|
|
535
|
+
"$collation": "name" }
|
|
501
536
|
```
|
|
502
537
|
|
|
503
|
-
with `$key` REQUIRED and `$dir` (default `"asc"`)
|
|
504
|
-
`"least"`) OPTIONAL.
|
|
538
|
+
with `$key` REQUIRED and `$dir` (default `"asc"`), `$empty` (default
|
|
539
|
+
`"least"`) and `$collation` OPTIONAL.
|
|
540
|
+
|
|
541
|
+
`$collation` names a **registered pure compare function**
|
|
542
|
+
(`options.collations`, a trusted host capability like `options.functions`,
|
|
543
|
+
§8.12) applied to this key's *string* comparisons — the natural-language
|
|
544
|
+
orders (`Intl.Collator('nl').compare`, say) that the format's default
|
|
545
|
+
code-point order (§8.9) deliberately does not build in. Number keys keep
|
|
546
|
+
numeric order; an unregistered name is compile error `JQ0010`. Registered
|
|
547
|
+
collations appear in `query.dependencies`/`query.explain()` (§8.12), so a
|
|
548
|
+
saved rule declares the collations it needs.
|
|
505
549
|
|
|
506
550
|
- An array value of `$orderby` is **always a list of key specs**, ordered
|
|
507
551
|
major to minor, and MUST NOT be empty. Consequently a *single* key spec is
|
|
@@ -515,7 +559,9 @@ with `$key` REQUIRED and `$dir` (default `"asc"`) and `$empty` (default
|
|
|
515
559
|
mathematically, strings by **Unicode scalar values** (code point order).
|
|
516
560
|
Comparing any other combination (number with string, or a key that is a
|
|
517
561
|
boolean, `null`, array, object, or multi-item sequence) is runtime error
|
|
518
|
-
`JQ2005`.
|
|
562
|
+
`JQ2005`. A `NaN` key is a number: the comparator is total over `NaN`, ordering
|
|
563
|
+
it **equal to itself and less than every other number**, then falling through
|
|
564
|
+
to the next key on ties (§8.9's `$sort` cites this same rule).
|
|
519
565
|
- Empty key sequences sort per `$empty`: `"least"` (default) places them
|
|
520
566
|
first ascending / last descending; `"greatest"` the reverse.
|
|
521
567
|
|
|
@@ -577,6 +623,124 @@ instead.
|
|
|
577
623
|
"$return": "$b.title" }
|
|
578
624
|
```
|
|
579
625
|
|
|
626
|
+
### 6.9 `$fold` — the accumulator clause
|
|
627
|
+
|
|
628
|
+
```json
|
|
629
|
+
"$fold": { name: initExpr }
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
`$fold` turns the phrase from a *map* into a **reduction**. It binds exactly
|
|
633
|
+
one accumulator variable (a second member is `JQ0003`):
|
|
634
|
+
|
|
635
|
+
- *initExpr* is evaluated **once**, in the phrase's **enclosing** scope,
|
|
636
|
+
before the tuple stream starts. It therefore cannot reference this phrase's
|
|
637
|
+
own `$for`/`$let` bindings — such a name is free, and resolves as an
|
|
638
|
+
external (§9) rather than as the binding.
|
|
639
|
+
- *name* is in scope from `$let` onward — in `$let`, `$where`, `$groupby`,
|
|
640
|
+
`$orderby` and `$return` — but **not** in `$for` sources, which are
|
|
641
|
+
iterated once and must not depend on a value that changes per tuple.
|
|
642
|
+
- For each surviving tuple, `$return` is evaluated and the accumulator is
|
|
643
|
+
**rebound to its result**. `$return` names the accumulator's next value,
|
|
644
|
+
not an item of the output.
|
|
645
|
+
- The phrase evaluates to the **final accumulator**. If no tuple survives,
|
|
646
|
+
that is the initial value.
|
|
647
|
+
|
|
648
|
+
*name* counts as a binding of this phrase for the duplicate rule (`JQ0007`).
|
|
649
|
+
Because the accumulator is a binding rather than a lambda parameter, this
|
|
650
|
+
gives the language a general fold without giving the JSON encoding a way to
|
|
651
|
+
spell a **function value** — folds, running totals and pointer walks are
|
|
652
|
+
expressible; passing a function to an operator still is not.
|
|
653
|
+
|
|
654
|
+
`$fold` composes with every other clause. With `$orderby` the reduction runs
|
|
655
|
+
over the *sorted* tuples; with `$groupby` it updates once per group; `$where`
|
|
656
|
+
selects which tuples update it at all.
|
|
657
|
+
|
|
658
|
+
```json
|
|
659
|
+
{ "$fold": { "total": 0 },
|
|
660
|
+
"$for": { "b": "$.store.book[*]" },
|
|
661
|
+
"$where": { "$lt": ["$b.price", 10] },
|
|
662
|
+
"$return": { "$add": ["$total", "$b.price"] } }
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
sums the prices of the cheap books. Because `$get` (§8.9) is a real dynamic
|
|
666
|
+
lookup, a fold over a runtime path is a pointer walk:
|
|
667
|
+
|
|
668
|
+
```json
|
|
669
|
+
{ "$fold": { "cur": "$.doc" },
|
|
670
|
+
"$for": { "seg": "$.path[*]" },
|
|
671
|
+
"$return": { "$get": ["$cur", "$seg"] } }
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
resolves `$.path` — a sequence of member names and array indexes — against
|
|
675
|
+
`$.doc`, one segment per tuple.
|
|
676
|
+
|
|
677
|
+
### 6.10 `$allowing-empty` and window clauses
|
|
678
|
+
|
|
679
|
+
Two further options of the extended `$for` binding form (§6.2).
|
|
680
|
+
|
|
681
|
+
**`$allowing-empty`** makes a binding **outer-join-style**: when the source
|
|
682
|
+
would yield no tuple at all, the clause emits exactly **one** tuple with the
|
|
683
|
+
variable bound to the **empty sequence**, so the enclosing tuple survives.
|
|
684
|
+
|
|
685
|
+
```json
|
|
686
|
+
{ "$in": expr, "$allowing-empty": true }
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
The condition is "yields no tuple", not "the sequence was empty" — an item
|
|
690
|
+
that is an empty array contributes no members under D4 unpacking (§6.2) and
|
|
691
|
+
so also triggers it. When the binding also has `$at`, that tuple's position
|
|
692
|
+
is **-1**: every real position is a non-negative 0-based one (D6), so -1 is
|
|
693
|
+
the only value free to mean "no position".
|
|
694
|
+
|
|
695
|
+
Note the source is what must be empty. Because a path filter's `$` is the
|
|
696
|
+
input document and never a query variable (§3.2), a *correlated* source is
|
|
697
|
+
written as a nested phrase:
|
|
698
|
+
|
|
699
|
+
```json
|
|
700
|
+
{ "$for": { "b": "$.store.book[*]",
|
|
701
|
+
"r": { "$in": { "$for": { "x": "$.ratings[*]" },
|
|
702
|
+
"$where": { "$eq": ["$x.isbn", "$b.isbn"] },
|
|
703
|
+
"$return": "$x" },
|
|
704
|
+
"$allowing-empty": true } },
|
|
705
|
+
"$return": { "title": "$b.title", "stars": "$r.stars" } }
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
keeps every book, rated or not; an unrated book's `stars` member is omitted
|
|
709
|
+
because an empty member value omits the member (§3.4).
|
|
710
|
+
|
|
711
|
+
**Windows** iterate consecutive *runs* of the item stream instead of single
|
|
712
|
+
items:
|
|
713
|
+
|
|
714
|
+
```json
|
|
715
|
+
{ "$in": expr, "$window": "tumbling" | "sliding", "$size": n, "$step": m, "$at": "w" }
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
`$size` is REQUIRED with `$window` and MUST be a positive **integer literal**,
|
|
719
|
+
as MUST `$step` when present; they are not expressions, because a width that
|
|
720
|
+
varied per tuple could not be compiled into a specialized loop. `$size`/`$step`
|
|
721
|
+
without `$window`, or `$window` without `$size`, is `JQ0003`. The variable
|
|
722
|
+
binds the window's items **as a sequence** (not an array item); `$at` binds
|
|
723
|
+
the 0-based window number.
|
|
724
|
+
|
|
725
|
+
A window starts at item 0 and every `$step` items thereafter; `$step`
|
|
726
|
+
defaults to `$size` for `tumbling` and to `1` for `sliding`. The two kinds
|
|
727
|
+
differ only in what happens at the end of the stream:
|
|
728
|
+
|
|
729
|
+
- **`tumbling`** *partitions* the stream — every item belongs to exactly one
|
|
730
|
+
window — so a short final window IS emitted; dropping it would silently
|
|
731
|
+
lose data.
|
|
732
|
+
- **`sliding`** is a moving view of fixed width, so a short window is not one
|
|
733
|
+
of them: only full-width windows are emitted.
|
|
734
|
+
|
|
735
|
+
```json
|
|
736
|
+
{ "$for": { "w": { "$in": "$.readings[*]", "$window": "sliding", "$size": 3 } },
|
|
737
|
+
"$return": { "$avg": "$w" } }
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
is a 3-point moving average; the same document with `"tumbling"` and no
|
|
741
|
+
`$step` averages disjoint blocks of three, including a final block of one or
|
|
742
|
+
two if the stream does not divide evenly.
|
|
743
|
+
|
|
580
744
|
---
|
|
581
745
|
|
|
582
746
|
## 7. Quantifier phrases
|
|
@@ -715,7 +879,10 @@ All arithmetic is IEEE double arithmetic (**D1**):
|
|
|
715
879
|
XQuery *double* division semantics, not the decimal FOAR0001 error.
|
|
716
880
|
- `$idiv` is **truncating division** (quotient rounded toward zero to an
|
|
717
881
|
integral double); `$idiv` or `$mod` with a zero divisor is runtime error
|
|
718
|
-
`JQ2002`.
|
|
882
|
+
`JQ2002`. Only a **zero divisor** errors: an `Infinity` or `NaN` *dividend*
|
|
883
|
+
over a finite non-zero divisor is not an error — both operators keep IEEE
|
|
884
|
+
double behavior (`Math.trunc` of the quotient, `%` for `$mod`) and yield
|
|
885
|
+
`NaN`, in keeping with D1's IEEE arithmetic.
|
|
719
886
|
- `$mod` takes the sign of the dividend (XQuery `mod` semantics).
|
|
720
887
|
- `$neg` is unary minus.
|
|
721
888
|
|
|
@@ -805,7 +972,7 @@ clause/operator collision note.
|
|
|
805
972
|
| `$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
973
|
| `$max` | empty | The greatest item (`fn:max`), same rules as `$min`. |
|
|
807
974
|
|
|
808
|
-
### 8.9 Sequence operators — `$distinct $reverse $sort $head $tail $subsequence $index-of $range $get`
|
|
975
|
+
### 8.9 Sequence operators — `$distinct $reverse $sort $head $tail $subsequence $index-of $range $get $entries $from-entries`
|
|
809
976
|
|
|
810
977
|
| Operator | Signature | Definition |
|
|
811
978
|
|---|---|---|
|
|
@@ -818,6 +985,8 @@ clause/operator collision note.
|
|
|
818
985
|
| `$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
986
|
| `$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
987
|
| `$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. |
|
|
988
|
+
| `$entries` | `{"$entries": e}` | The member-pair counterpart of a `[*]` path segment (which yields values only): each OBJECT item of the operand contributes one `{"key": name, "value": v}` pair per member, in member order; non-object items contribute nothing, and an empty operand yields the empty sequence. `$from-entries` is the inverse; the `$map` constructor (§3.5.2) is the fixed-arity form for computed keys. |
|
|
989
|
+
| `$from-entries` | `{"$from-entries": e}` | The inverse of `$entries`: assembles ONE object from the operand's `{"key": name, "value": v}` items, in sequence order — later pairs win on duplicate keys, exactly like the `$map` constructor. Items without a string `key` contribute nothing; a pair missing its `value` member reads as `null`; an empty operand constructs the empty object. |
|
|
821
990
|
|
|
822
991
|
Combined example (also exercises the aggregates of §8.8):
|
|
823
992
|
|
|
@@ -927,6 +1096,265 @@ README's "`$query` — cross-field assertions" section.
|
|
|
927
1096
|
|
|
928
1097
|
---
|
|
929
1098
|
|
|
1099
|
+
### 8.12 Registered functions, operators, collations, and execution limits
|
|
1100
|
+
|
|
1101
|
+
Three compile options make a compilation's **trusted host capabilities**
|
|
1102
|
+
explicit — none of them changes the closed format: a document using them
|
|
1103
|
+
compiles only against a host that registered them, and a host that
|
|
1104
|
+
registered nothing keeps exactly the spec vocabulary.
|
|
1105
|
+
|
|
1106
|
+
**`options.functions` and `$call`.** A registry of named pure functions;
|
|
1107
|
+
the `$call` phrase invokes one:
|
|
1108
|
+
|
|
1109
|
+
```json
|
|
1110
|
+
{ "$call": ["upper", "$b.title"] }
|
|
1111
|
+
```
|
|
1112
|
+
|
|
1113
|
+
The first item MUST be a literal string naming a registered function
|
|
1114
|
+
(`JQ0010` otherwise). Argument expressions evaluate first; each crosses
|
|
1115
|
+
the boundary as plain JSON — a sequence as an array of items, the empty
|
|
1116
|
+
sequence as `undefined`. The function's return value is one item;
|
|
1117
|
+
`undefined` is the empty sequence. A throwing function is runtime error
|
|
1118
|
+
`JQ2010`. Functions MUST be pure over JSON: they are part of the query's
|
|
1119
|
+
semantics, not an effect hatch.
|
|
1120
|
+
|
|
1121
|
+
**`options.extensions` and host `$`-operators.** A registry of named
|
|
1122
|
+
operator entries that extend the closed operator vocabulary (§8) with
|
|
1123
|
+
host-provided `$`-operators. Each entry has the same shape the built-in
|
|
1124
|
+
operators use — `{ params, result, resultType, compile, normalize? }` —
|
|
1125
|
+
so a registered `{ "$sqrt": "$b.x" }` normalizes, type-annotates (its
|
|
1126
|
+
declared `resultType` flows through `annotateTypes`, Appendix C.8) and
|
|
1127
|
+
compiles exactly like a built-in, and appears in
|
|
1128
|
+
`query.dependencies.operators`. A name that collides with the closed
|
|
1129
|
+
vocabulary is a `TypeError` at compile — a host programming error, never
|
|
1130
|
+
a `JQ` document error. The intended way to build this registry is
|
|
1131
|
+
`createJsltRegistry()` from `@jarenjs/json/jslt` (JSLT-FORMAT §13), which
|
|
1132
|
+
composes plain-data packs of pure `@jarenjs/core` functions (math,
|
|
1133
|
+
finance, statistics) into `{ extensions, functions }` and works across
|
|
1134
|
+
stylesheets, bare queries and `@jarenjs/linq` — and, through
|
|
1135
|
+
`@jarenjs/db`, in the query residual and (for the `pushable:'scalar'`
|
|
1136
|
+
subset) as SQLite deterministic UDFs (MODEL-FORMAT §8.1–8.2). A document
|
|
1137
|
+
compiled *without* a registry keeps exactly the spec vocabulary — `$sqrt`
|
|
1138
|
+
is then `JQ0002`.
|
|
1139
|
+
|
|
1140
|
+
**`options.collations`.** A registry of named pure compare functions for
|
|
1141
|
+
`$orderby`'s `$collation` member (§6.6).
|
|
1142
|
+
|
|
1143
|
+
**`options.limits`.** Deterministic limits enforced *inside* the
|
|
1144
|
+
synchronous engine. Two of them are **output caps**: they bound what a
|
|
1145
|
+
phrase or the query hands onward, NOT memory, fan-out or intermediate
|
|
1146
|
+
accumulation.
|
|
1147
|
+
|
|
1148
|
+
- `sequenceItems` — bounds every FLWOR phrase materialization (the
|
|
1149
|
+
sequence a phrase *returns*) and tightens `$range`'s resource guard
|
|
1150
|
+
below its 2³² ceiling; exceeding it is `JQ2009` (`$range` keeps its
|
|
1151
|
+
historical `JQ2007`). A `$groupby`/`$orderby` barrier may accumulate
|
|
1152
|
+
arbitrarily many items — and a collation may run arbitrarily many
|
|
1153
|
+
comparisons — behind a small final output; bare paths, `$count` and
|
|
1154
|
+
other operators can likewise materialize above the cap internally.
|
|
1155
|
+
- `resultItems` — bounds the final result at the query boundary
|
|
1156
|
+
(`JQ2009`), checked after evaluation; `first()`, `exists()` and
|
|
1157
|
+
`ebv()` deliberately bypass it.
|
|
1158
|
+
|
|
1159
|
+
The other two bound the **query** rather than its output:
|
|
1160
|
+
|
|
1161
|
+
- `steps` — bounds **expression-node evaluations** (`JQ2009`). A step is
|
|
1162
|
+
one node evaluation, not one primitive operation: a node that loops
|
|
1163
|
+
internally — materializing a `$range`, a general comparison's cross
|
|
1164
|
+
product, a sort's comparisons — counts once. It is the only limit that
|
|
1165
|
+
bounds work rather than output, and the only one that costs: setting
|
|
1166
|
+
it compiles a counter check into every node. The counter resets per
|
|
1167
|
+
evaluation, so a compiled query stays reusable.
|
|
1168
|
+
- `depth` — bounds **expression nesting**, and is checked at **compile
|
|
1169
|
+
time** (`JQ0011`). The language has no recursion — no user-defined
|
|
1170
|
+
functions, no self-reference — so the compiled closure tree's maximum
|
|
1171
|
+
evaluation depth IS the document's static nesting. Checking it once is
|
|
1172
|
+
therefore exact, and costs nothing to evaluate.
|
|
1173
|
+
|
|
1174
|
+
An unknown limit name, or a value that is not a positive integer, is a
|
|
1175
|
+
host programming error (`TypeError`), because an accepted-but-unenforced
|
|
1176
|
+
limit would be a silent false guarantee.
|
|
1177
|
+
|
|
1178
|
+
Together these make trusted, developer-authored rules diagnosable, and
|
|
1179
|
+
give user- or model-authored queries a deterministic work bound. They
|
|
1180
|
+
still are not a sandbox: `steps` bounds evaluations, not memory, and a
|
|
1181
|
+
wall-clock or CPU limit is out of scope by design — a synchronous run on
|
|
1182
|
+
the caller's thread cannot be preempted, so a host needing hard
|
|
1183
|
+
termination owns a worker or isolate.
|
|
1184
|
+
|
|
1185
|
+
**Dependencies and explanation.** The compiled query reports what it
|
|
1186
|
+
needs: `query.dependencies` is a frozen `{ externals, operators,
|
|
1187
|
+
functions, collations }`, and `query.explain()` returns that plus the
|
|
1188
|
+
enforced limits as fresh plain JSON — the vetting surface for saved or
|
|
1189
|
+
machine-authored rules.
|
|
1190
|
+
|
|
1191
|
+
### 8.13 Dates and times
|
|
1192
|
+
|
|
1193
|
+
JSON has no date type, so dates are **RFC 3339 strings** and these operators
|
|
1194
|
+
are ordinary string operators with a calendar's worth of rules. Every one of
|
|
1195
|
+
them is a **pure function of its operand**: there is deliberately no
|
|
1196
|
+
`current-dateTime`, because a compiled query must give the same answer for
|
|
1197
|
+
the same input document forever — it is cached by document identity, saved as
|
|
1198
|
+
a rule, and usable as a validation keyword.
|
|
1199
|
+
|
|
1200
|
+
**Type predicates.** Unary `{"$is-date": e}`, `$is-time`, `$is-datetime`,
|
|
1201
|
+
`$is-duration` — `true` iff `e` is a **singleton string** in that lexical
|
|
1202
|
+
form (`full-date`, `full-time`, `date-time`, and the RFC 3339 Appendix A
|
|
1203
|
+
duration grammar). Like the §8.10 `$is-*` family these never raise: the empty
|
|
1204
|
+
sequence, a multi-item sequence and a non-string are all `false`. The forms
|
|
1205
|
+
are disjoint — a `date-time` is not a `date`.
|
|
1206
|
+
|
|
1207
|
+
**Components.** Unary `$year`, `$month`, `$day`, `$hours`, `$minutes`,
|
|
1208
|
+
`$seconds`, `$offset`. Each propagates the empty sequence and returns a
|
|
1209
|
+
number. Components are read **lexically, in the value's own offset** — the
|
|
1210
|
+
`fn:year-from-dateTime` reading, and the one that makes "group by month"
|
|
1211
|
+
mean what an author expects. `$seconds` carries the fraction (`5.5`).
|
|
1212
|
+
`$offset` is minutes east of UTC, and is the one component that yields the
|
|
1213
|
+
**empty sequence** rather than an error when absent, because RFC 3339 leaves
|
|
1214
|
+
a bare `full-date` offset-less.
|
|
1215
|
+
|
|
1216
|
+
Asking a value for a component of a half it does not have — the `$hours` of a
|
|
1217
|
+
`full-date`, the `$year` of a `full-time` — is runtime error `JQ2001`, as is
|
|
1218
|
+
an operand that is not an RFC 3339 value at all.
|
|
1219
|
+
|
|
1220
|
+
**Instants.** Unary `$epoch` maps a value carrying a date to **milliseconds
|
|
1221
|
+
since 1970-01-01T00:00:00Z**, and `$datetime` maps such a number back to a
|
|
1222
|
+
canonical UTC `date-time` string. `$epoch` is the one place a value is
|
|
1223
|
+
shifted to UTC, which makes it the way to compare or subtract across
|
|
1224
|
+
offsets — as *strings*, `"…T14:00:00+02:00"` sorts after `"…T12:00:00Z"`
|
|
1225
|
+
though they are the same instant. A `full-time` has no instant to place
|
|
1226
|
+
(`JQ2001`); so does a number outside the range RFC 3339 can spell.
|
|
1227
|
+
|
|
1228
|
+
**Calendar arithmetic.** `$date-add` and `$date-sub` shift a value, either
|
|
1229
|
+
by an ISO 8601 duration (`[date, "P1M"]`) or by an amount and a unit
|
|
1230
|
+
(`[date, 3, "day"]`). `$start-of` and `$end-of` truncate to a unit, and
|
|
1231
|
+
`$date-diff` counts whole units from one value to another. The unit is
|
|
1232
|
+
**data**, not vocabulary — one of `year`, `quarter`, `month`, `week`, `day`,
|
|
1233
|
+
`hour`, `minute`, `second`, `millisecond` — so an unknown one is `JQ2001`
|
|
1234
|
+
rather than a compile error.
|
|
1235
|
+
|
|
1236
|
+
Two rules make these predictable:
|
|
1237
|
+
|
|
1238
|
+
- **The lexical form is preserved.** A `full-date` shifted by a day is still
|
|
1239
|
+
a `full-date`, and a `date-time` keeps its own offset rather than being
|
|
1240
|
+
normalized to UTC. A query that buckets dates must not silently start
|
|
1241
|
+
producing date-times. Consequently `$end-of` on a `full-date` yields that
|
|
1242
|
+
unit's last *day*, where on a `date-time` it yields the last millisecond —
|
|
1243
|
+
a `full-date` has nowhere to put one.
|
|
1244
|
+
- **Month arithmetic clamps.** `2026-01-31` plus one month is `2026-02-28`,
|
|
1245
|
+
because the alternative — overflowing into March — makes adding a month
|
|
1246
|
+
non-monotonic. `$date-diff` counts months to match, so adding its result
|
|
1247
|
+
back never overshoots: `2026-01-31` to `2026-02-28` is **one** month.
|
|
1248
|
+
|
|
1249
|
+
`$date-format` renders a value through a **Unicode LDML** pattern
|
|
1250
|
+
(`yyyy-MM-dd`, not moment's `YYYY-MM-DD`); a literal pattern compiles once
|
|
1251
|
+
with the query. Patterns are limited to the locale-independent tokens: month
|
|
1252
|
+
and weekday *names* would need locale data this format does not carry, so
|
|
1253
|
+
`MMMM`, `MMM`, `EEEE`, `EEE` and `a` are rejected — `JQ0003` for a literal
|
|
1254
|
+
pattern, `JQ2001` for one computed at runtime. Localized rendering belongs to
|
|
1255
|
+
the presentation layer, not to a query.
|
|
1256
|
+
|
|
1257
|
+
| Operator | Definition |
|
|
1258
|
+
|---|---|
|
|
1259
|
+
| `$is-date` `$is-time` `$is-datetime` `$is-duration` | singleton string in that RFC 3339 form → `true`; anything else → `false` |
|
|
1260
|
+
| `$year` `$month` `$day` | lexical date components; a value with no date is `JQ2001` |
|
|
1261
|
+
| `$hours` `$minutes` `$seconds` | lexical time components, `$seconds` including its fraction; a value with no time is `JQ2001` |
|
|
1262
|
+
| `$offset` | minutes east of UTC; a bare `full-date` → empty |
|
|
1263
|
+
| `$week` `$week-year` | ISO 8601 week number and its week-numbering year, which is not always the calendar year (2027-01-01 is week 53 of 2026) |
|
|
1264
|
+
| `$quarter` `$weekday` | calendar quarter 1-4; ISO weekday 1 (Monday) to 7 (Sunday) |
|
|
1265
|
+
| `$epoch` | date or date-time → milliseconds since the epoch (UTC); a `full-time` → `JQ2001` |
|
|
1266
|
+
| `$datetime` | epoch milliseconds → canonical UTC `date-time`; out of RFC 3339 range → `JQ2001` |
|
|
1267
|
+
| `$date-add` `$date-sub` | `[date, duration]` or `[date, amount, unit]` → a value of the same lexical form |
|
|
1268
|
+
| `$start-of` `$end-of` | `[date, unit]` → the unit's first / last instant, in the same lexical form |
|
|
1269
|
+
| `$date-diff` | `[from, to, unit]` → whole units, negative when `to` precedes `from` |
|
|
1270
|
+
| `$date-format` | `[date, pattern]` → the value rendered through an LDML pattern |
|
|
1271
|
+
|
|
1272
|
+
Every operator that *produces* a date produces it in the same canonical
|
|
1273
|
+
spelling, so one query can never emit two forms of one instant: a fractional
|
|
1274
|
+
second appears only when non-zero and without trailing zeros
|
|
1275
|
+
(`…:05.5Z`, never `…:05.500Z`).
|
|
1276
|
+
|
|
1277
|
+
Durations are recognized and applied, but never *decomposed* into a number:
|
|
1278
|
+
`P1M` is not a fixed count of milliseconds, so there is no honest length to
|
|
1279
|
+
report without a calendar anchor. Applying one to a date is where the anchor
|
|
1280
|
+
exists, which is what `$date-add` is for; fixed-width spans go through
|
|
1281
|
+
`$epoch` and ordinary `$sub`.
|
|
1282
|
+
|
|
1283
|
+
```json
|
|
1284
|
+
{ "$for": { "e": "$.events[*]" },
|
|
1285
|
+
"$groupby": { "w": { "$start-of": ["$e.on", "week"] } },
|
|
1286
|
+
"$orderby": ["$w"],
|
|
1287
|
+
"$return": { "week": "$w", "count": { "$count": "$e" } } }
|
|
1288
|
+
```
|
|
1289
|
+
|
|
1290
|
+
buckets events into ISO weeks — the shape components alone could not express,
|
|
1291
|
+
because a week boundary is arithmetic, not a field.
|
|
1292
|
+
|
|
1293
|
+
```json
|
|
1294
|
+
{ "$for": { "e": "$.events[*]" },
|
|
1295
|
+
"$where": { "$is-datetime": "$e.at" },
|
|
1296
|
+
"$groupby": { "y": { "$year": "$e.at" }, "m": { "$month": "$e.at" } },
|
|
1297
|
+
"$orderby": ["$y", "$m"],
|
|
1298
|
+
"$return": { "year": "$y", "month": "$m", "count": { "$count": "$e" } } }
|
|
1299
|
+
```
|
|
1300
|
+
|
|
1301
|
+
### 8.14 Spatial
|
|
1302
|
+
|
|
1303
|
+
Geography enters the language the way dates did: through the format the data
|
|
1304
|
+
already has. Operands are **GeoJSON** ([RFC 7946](https://datatracker.ietf.org/doc/html/rfc7946))
|
|
1305
|
+
— a bare position `[longitude, latitude]`, a geometry, a `Feature`, or a
|
|
1306
|
+
`FeatureCollection` — because that is what a JSON document holds. There is no
|
|
1307
|
+
geometry type to construct, and a wrapper is unwrapped for you: passing a
|
|
1308
|
+
`Feature` where a geometry is wanted is not an error, it is the common case.
|
|
1309
|
+
|
|
1310
|
+
Coordinates are longitude then latitude, in WGS 84 decimal degrees. RFC 7946
|
|
1311
|
+
removed alternative coordinate reference systems, so there is nothing to
|
|
1312
|
+
configure and no `crs` to honour.
|
|
1313
|
+
|
|
1314
|
+
**Measurements are geodesic, never planar.** A degree of longitude spans about
|
|
1315
|
+
111 km at the equator and 68 km at 52°N, so a Euclidean answer over raw degrees
|
|
1316
|
+
is wrong by two thirds over a kilometre at Dutch latitudes. `$distance`,
|
|
1317
|
+
`$area` and `$length` answer in metres and square metres on the WGS 84 sphere,
|
|
1318
|
+
accurate to under half a percent. Convert with ordinary arithmetic —
|
|
1319
|
+
`{"$idiv": [{"$distance": [a, b]}, 1000]}` for kilometres.
|
|
1320
|
+
|
|
1321
|
+
`$distance` and `$within` measure a value by its **representative position**: a
|
|
1322
|
+
bare position or `Point` is itself, anything else is its centroid. That is
|
|
1323
|
+
stated rather than inferred because the alternative — the minimum distance
|
|
1324
|
+
between two shapes — needs point-to-segment geodesics and is a much larger
|
|
1325
|
+
piece of work this format does not yet do.
|
|
1326
|
+
|
|
1327
|
+
`$bbox-intersects` is named for exactly what it tests. An operator called
|
|
1328
|
+
`$intersects` that compared only bounding boxes would be a lie the first time
|
|
1329
|
+
two L-shaped regions shared a box and nothing else; real geometry-to-geometry
|
|
1330
|
+
intersection is overlay work and is deliberately absent.
|
|
1331
|
+
|
|
1332
|
+
| Operator | Definition |
|
|
1333
|
+
|---|---|
|
|
1334
|
+
| `$bbox` | any value → `[west, south, east, north]`; a value with no positions → empty |
|
|
1335
|
+
| `$area` | square metres of the value's polygons, exterior rings less holes; anything without a surface → `0` |
|
|
1336
|
+
| `$length` | metres of the value's lines and ring perimeters; a point → `0` |
|
|
1337
|
+
| `$centroid` | the mean of the value's positions, as a position. **Not** the area-weighted centre of mass: for a concave shape it can fall outside the polygon |
|
|
1338
|
+
| `$distance` | `[a, b]` → metres between the two representative positions |
|
|
1339
|
+
| `$within` | `[a, b]` → is `a`'s representative position inside `b`'s surface? Only a polygon has an inside, so a line or point as `b` is `false` |
|
|
1340
|
+
| `$bbox-intersects` | `[a, b]` → do the two bounding boxes overlap? Touching edges count |
|
|
1341
|
+
| `$geohash` | `[value]` or `[value, precision]` → the base-32 cell string; precision is 1-12, default 9 |
|
|
1342
|
+
|
|
1343
|
+
Note what needs **no** operator. A geohash is a string, so proximity is
|
|
1344
|
+
`$starts-with` on a prefix and spatial bucketing is `$groupby` over
|
|
1345
|
+
`$substring` — the existing vocabulary already indexes, groups and orders them.
|
|
1346
|
+
|
|
1347
|
+
```json
|
|
1348
|
+
{ "$for": { "c": "$.cities[*]" },
|
|
1349
|
+
"$where": { "$within": ["$c.at", "$.region"] },
|
|
1350
|
+
"$orderby": [{ "$key": { "$distance": ["$c.at", "$.centre"] } }],
|
|
1351
|
+
"$return": { "name": "$c.name",
|
|
1352
|
+
"km": { "$idiv": [{ "$distance": ["$c.at", "$.centre"] }, 1000] } } }
|
|
1353
|
+
```
|
|
1354
|
+
|
|
1355
|
+
selects the cities inside a region and orders them by how far they are from a
|
|
1356
|
+
point — a spatial filter and a spatial sort, in the language's own clauses.
|
|
1357
|
+
|
|
930
1358
|
## 9. Variables, scoping, and external parameters
|
|
931
1359
|
|
|
932
1360
|
1. Variables are introduced by `$for`, `$let`, `$at`, `$count`, `$groupby`
|
|
@@ -952,6 +1380,33 @@ README's "`$query` — cross-field assertions" section.
|
|
|
952
1380
|
|
|
953
1381
|
`$minPrice` is free — an external the caller binds at call time.
|
|
954
1382
|
|
|
1383
|
+
**Closed-world compilation.** Because use is the declaration, a typo in a
|
|
1384
|
+
variable name is not an error: it quietly becomes a new external. A host that
|
|
1385
|
+
knows the parameters it intends to expose MAY compile **closed-world**,
|
|
1386
|
+
declaring them (`options.externals` in this implementation). Every free
|
|
1387
|
+
variable that is not declared is then compile error `JQ0005` at its own
|
|
1388
|
+
reference site, and an empty declaration list forbids externals entirely.
|
|
1389
|
+
This changes no document semantics — a document that compiles closed-world
|
|
1390
|
+
behaves identically compiled open — it only decides which documents compile.
|
|
1391
|
+
|
|
1392
|
+
> **Worked example (clause order and `$count`).** Scope follows the
|
|
1393
|
+
> **semantic** clause order of §6.1, not document key order, and `$count`
|
|
1394
|
+
> binds *after* `$where`. So a `$where` that mentions the phrase's own
|
|
1395
|
+
> `$count` name does not see the tuple number — the name is not in scope
|
|
1396
|
+
> yet, and rule 3 makes it an **external**:
|
|
1397
|
+
>
|
|
1398
|
+
> ```json
|
|
1399
|
+
> { "$for": { "b": "$[*]" },
|
|
1400
|
+
> "$where": { "$lt": ["$n", 2] },
|
|
1401
|
+
> "$count": "n",
|
|
1402
|
+
> "$return": ["$b", "$n"] }
|
|
1403
|
+
> ```
|
|
1404
|
+
>
|
|
1405
|
+
> `$n` in `$where` is external (the compiled query reports `["n"]`); `$n` in
|
|
1406
|
+
> `$return` is the tuple number. This is correct and surprising, which is why
|
|
1407
|
+
> it is worth stating: filtering by position is done with `$at` (§6.2), whose
|
|
1408
|
+
> variable is in scope from the binding onward, not with `$count`.
|
|
1409
|
+
|
|
955
1410
|
---
|
|
956
1411
|
|
|
957
1412
|
## 10. Errors
|
|
@@ -974,11 +1429,13 @@ runtime errors as `JsonQueryRuntimeError`. Every error carries:
|
|
|
974
1429
|
| `JQ0002` | Unknown operator / `$`-key outside the vocabulary | XPST0017 |
|
|
975
1430
|
| `JQ0003` | Known phrase with bad arity, value shape, or key combination | XPST0003 |
|
|
976
1431
|
| `JQ0004` | String starting `$` is not a valid path or escape (§3.2) | XPST0003 |
|
|
977
|
-
| `JQ0005` | Variable reference that is neither bound nor
|
|
1432
|
+
| `JQ0005` | Variable reference that is neither bound nor a declared external under a closed-world compilation (§9); also an `$as` member naming a variable not bound by its phrase's `$for`/`$let` (§6.8) | XPST0008 |
|
|
978
1433
|
| `JQ0006` | Version envelope with unknown or non-string `$query` (§4.2) | XQST0031 |
|
|
979
1434
|
| `JQ0007` | Duplicate variable binding within one phrase (§6.3) | XQST0089 |
|
|
980
1435
|
| `JQ0008` | Schema operator (`$valid`/`$assert`/`$as`) in a query compiled without a type-test compiler (§8.11) | XQST0009 |
|
|
981
1436
|
| `JQ0009` | Schema literal rejected by the type-test compiler (invalid embedded schema, §8.11) | XQST0059 |
|
|
1437
|
+
| `JQ0010` | `$call`/`$collation` naming no registered function/collation (§8.12, §6.6) | XPST0017 |
|
|
1438
|
+
| `JQ0011` | Expression nesting deeper than `limits.depth` (§8.12) | XPDY0130 |
|
|
982
1439
|
|
|
983
1440
|
### 10.3 Runtime errors (`JQ2xxx`)
|
|
984
1441
|
|
|
@@ -992,6 +1449,9 @@ runtime errors as `JsonQueryRuntimeError`. Every error carries:
|
|
|
992
1449
|
| `JQ2006` | Reference to an unbound external parameter (§9) | XPDY0002 |
|
|
993
1450
|
| `JQ2007` | Resource guard: an operator result exceeding an implementation limit (`$range` over 2³² items, §8.9) | XPDY0130 |
|
|
994
1451
|
| `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 |
|
|
1452
|
+
| `JQ2009` | An execution limit exceeded: `limits.sequenceItems` on a phrase materialization, `limits.resultItems` at the query boundary, or `limits.steps` expression evaluations (§8.12) | XPDY0130 |
|
|
1453
|
+
| `JQ2010` | A registered `$call` function threw (§8.12) | FOER0000 |
|
|
1454
|
+
| `JQ2011` | The input document is `undefined`, which is not a JSON value (§2.1) | XPDY0002 |
|
|
995
1455
|
|
|
996
1456
|
---
|
|
997
1457
|
|
|
@@ -1061,8 +1521,9 @@ The schema cannot express, and therefore leaves to the compiler (stated in
|
|
|
1061
1521
|
`description`s in the artifacts): fixed clause ordering (semantic, not
|
|
1062
1522
|
structural — every key order is valid JSON), variable scoping and duplicate
|
|
1063
1523
|
detection (`JQ0005`/`JQ0007`), `$as` name binding (`JQ0005`), full grammar
|
|
1064
|
-
of variable-rooted path segments (only the head is pattern-checked),
|
|
1065
|
-
|
|
1524
|
+
of variable-rooted path segments (only the head is pattern-checked), the
|
|
1525
|
+
co-occurrence rules of the extended `$for` binding (`$size`/`$step` require
|
|
1526
|
+
`$window`, `$window` requires `$size`, §6.10), and all runtime typing rules. Schema-literal positions (§8.11) validate as `true` —
|
|
1066
1527
|
draft-neutral by definition; embedded JSON Schemas are deliberately **not**
|
|
1067
1528
|
meta-validated by these artifacts (the type-test compiler is authoritative,
|
|
1068
1529
|
`JQ0009`). Where the schema and this text disagree, this text wins and the
|
|
@@ -1210,6 +1671,20 @@ emit a bespoke query DSL as free text and parsing it hopefully — and the
|
|
|
1210
1671
|
trade is: grammar errors eliminated by construction, semantic errors reduced
|
|
1211
1672
|
to a machine-checkable, machine-repairable residue.
|
|
1212
1673
|
|
|
1674
|
+
Provider structured-output modes support different JSON Schema subsets
|
|
1675
|
+
regardless of the draft they advertise, and the strict ones enforce neither
|
|
1676
|
+
`patternProperties`/`propertyNames` nor asserted `format`s and reject
|
|
1677
|
+
`oneOf`. For those, a third artifact ships beside the two of §12.1:
|
|
1678
|
+
`packages/json/schemas/jaren-query.llm-profile.schema.json`, `$id`
|
|
1679
|
+
`https://jarenjs.dev/schemas/jaren-query/0.1/llm-profile` — a mechanically
|
|
1680
|
+
derived, pure *relaxation* in which those constraints are removed (each
|
|
1681
|
+
restated in the nearest `description`, which the model still reads) and
|
|
1682
|
+
every `oneOf` becomes `anyOf`. Every canonical-valid query document
|
|
1683
|
+
validates under the profile; the reverse is deliberately not guaranteed.
|
|
1684
|
+
Hand the profile to the decoder, keep validating locally against the
|
|
1685
|
+
canonical schema — the package [README](../README.md#generating-queries-with-llms)
|
|
1686
|
+
walks the full pipeline.
|
|
1687
|
+
|
|
1213
1688
|
Because query documents are plain JSON, they also travel well through the
|
|
1214
1689
|
rest of an LLM toolchain: function-call arguments, retrieval filters, and
|
|
1215
1690
|
audit logs all speak JSON already, and a generated query can be validated,
|
|
@@ -1219,3 +1694,190 @@ The same constrained-decoding model applies to complete recursive
|
|
|
1219
1694
|
stylesheets; see
|
|
1220
1695
|
[JSLT-FORMAT Appendix B](./JSLT-FORMAT.md#appendix-b-llm-structured-output-non-normative)
|
|
1221
1696
|
and its mechanically query-derived schema twins.
|
|
1697
|
+
|
|
1698
|
+
## Appendix C. The normalized form (normative)
|
|
1699
|
+
|
|
1700
|
+
The engine compiles in two stages: stage 1 **normalizes** a query
|
|
1701
|
+
document into a frozen abstract syntax tree (the grammar of §§3–9
|
|
1702
|
+
resolved — object partitioning, phrase classification, string forms,
|
|
1703
|
+
scope resolution, cardinality analysis); stage 2 specializes that tree
|
|
1704
|
+
into closures. Stage 2 is an optimisation artifact and changes freely.
|
|
1705
|
+
**Stage 1 is the language resolved, and this appendix publishes it as a
|
|
1706
|
+
contract**: `analyzeQuery` (the package's `./query` subpath) returns the
|
|
1707
|
+
normalized tree so a consumer — a translator, a planner, an analyzer —
|
|
1708
|
+
can walk *the engine's own reading* of a document instead of inventing a
|
|
1709
|
+
second one.
|
|
1710
|
+
|
|
1711
|
+
### C.1 The analysis entry point
|
|
1712
|
+
|
|
1713
|
+
```js
|
|
1714
|
+
analyzeQuery(doc, options) -> {
|
|
1715
|
+
astVersion, // integer; see the compatibility policy (C.7)
|
|
1716
|
+
root, // the frozen node tree (C.3)
|
|
1717
|
+
externals, // [{ name, slot }] in order of first appearance (§9)
|
|
1718
|
+
frameSize, // total frame slots the tree addresses (C.5)
|
|
1719
|
+
dependencies, // { externals, operators, functions, collations }
|
|
1720
|
+
limits, // the normalized limits record, or null
|
|
1721
|
+
}
|
|
1722
|
+
```
|
|
1723
|
+
|
|
1724
|
+
`analyzeQuery` accepts the same options as `compileJsonQuery` and
|
|
1725
|
+
applies the same JQ0xxx rejections, with one deliberate difference:
|
|
1726
|
+
**schema literals do not require `options.compileTypeTest`**. Where
|
|
1727
|
+
compilation without the hook is `JQ0008`, analysis normalizes the schema
|
|
1728
|
+
literal to its `raw` node carrying the frozen schema with **no compiled
|
|
1729
|
+
predicate** (`test` is `null`), so a consumer can analyse a document it
|
|
1730
|
+
could not execute. When the hook IS supplied, analysis compiles the
|
|
1731
|
+
predicate exactly as compilation would (and can therefore still raise
|
|
1732
|
+
`JQ0009`). Everything else — `$call`/`$collation` registry resolution,
|
|
1733
|
+
closed-world externals, limits validation — behaves identically in both
|
|
1734
|
+
modes.
|
|
1735
|
+
|
|
1736
|
+
A caller wanting both pays for one normalization:
|
|
1737
|
+
`compileJsonQuery(doc, { analysis: true })` exposes the same record at
|
|
1738
|
+
`query.analysis` on the compiled query.
|
|
1739
|
+
|
|
1740
|
+
`NODE_KINDS` (same subpath) is the frozen, sorted list of the twelve
|
|
1741
|
+
node kinds of C.3; `AST_VERSION` is the current version integer.
|
|
1742
|
+
|
|
1743
|
+
### C.2 The cardinality lattice
|
|
1744
|
+
|
|
1745
|
+
Every node carries `card`, a static **upper approximation** of its
|
|
1746
|
+
runtime sequence length:
|
|
1747
|
+
|
|
1748
|
+
| value | constant | meaning |
|
|
1749
|
+
|---|---|---|
|
|
1750
|
+
| 0 | `CARD_ZERO` | statically the empty sequence |
|
|
1751
|
+
| 1 | `CARD_ONE` | always exactly one item |
|
|
1752
|
+
| 2 | `CARD_OPT` | zero or one item |
|
|
1753
|
+
| 3 | `CARD_MANY` | any number of items (the top) |
|
|
1754
|
+
|
|
1755
|
+
Two combinators are part of the contract: `joinCard(a, b)` is the least
|
|
1756
|
+
upper bound (the cardinality of "one branch or the other", `$if`), and
|
|
1757
|
+
`sumCard(a, b)` is concatenation (`$seq`): `ZERO` is its identity and
|
|
1758
|
+
any two non-`ZERO` contributions give `MANY`. Path nodes additionally
|
|
1759
|
+
carry `singular` (C.3), the RFC 9535 singular-query judgement of their
|
|
1760
|
+
segment list.
|
|
1761
|
+
|
|
1762
|
+
### C.3 The twelve node kinds
|
|
1763
|
+
|
|
1764
|
+
Every node is a plain frozen object carrying at least
|
|
1765
|
+
`{ kind, card, docPath }`. The complete field sets:
|
|
1766
|
+
|
|
1767
|
+
| kind | fields beyond `kind`/`card`/`docPath` |
|
|
1768
|
+
|---|---|
|
|
1769
|
+
| `literal` | `value` — a frozen JSON value (scalars, `$const` payloads, non-`$` strings) |
|
|
1770
|
+
| `var` | `slot`, `external` (boolean), `name` (`'$'` for the input document, slot 0) |
|
|
1771
|
+
| `path` | `name` (root variable), `rootSlot`, `external`, `rootCard`, `segments` (the frozen RFC 9535 segment list of `parseJSONPath`), `singular` |
|
|
1772
|
+
| `object` | `entries` — `[{ name, expr }]`, the map constructor of Rule 1 |
|
|
1773
|
+
| `map` | `pairs` — `[{ key, value }]`, the general `$map` constructor |
|
|
1774
|
+
| `array` | `elements` — `[node]`, the Rule 3 array constructor |
|
|
1775
|
+
| `raw` | `value` — a frozen verbatim JSON value (operator `raw`/`name`/`schema` argument positions); schema positions also carry `test` — the compiled predicate, or `null` under analysis without a hook |
|
|
1776
|
+
| `op` | `name`, `args` — `[node]`; `$range` under `limits` also carries `limits`; host extension operators also carry `entry` (the host's registry entry, an opaque host value) |
|
|
1777
|
+
| `call` | `name`, `fn` (the registered host function — an opaque host value), `args` |
|
|
1778
|
+
| `let` | `bindings` — `[{ name, slot, expr }]`, `ret` (the degenerate `{$let, $return}` phrase) |
|
|
1779
|
+
| `quant` | `some` (boolean), `bindings` — `[{ name, slot, expr }]`, `satisfies` |
|
|
1780
|
+
| `flwor` | see C.4 |
|
|
1781
|
+
|
|
1782
|
+
**Host-valued members.** `raw.test`, `call.fn`, `op.entry` and an
|
|
1783
|
+
orderby spec's `collation` are the four places the tree carries host
|
|
1784
|
+
functions rather than JSON. A consumer that serializes or diffs the tree
|
|
1785
|
+
MUST treat them as opaque presence/absence facts; everything else in the
|
|
1786
|
+
tree is plain JSON.
|
|
1787
|
+
|
|
1788
|
+
### C.4 The `flwor` node
|
|
1789
|
+
|
|
1790
|
+
The full phrase (§6) normalizes to one node with the clauses in their
|
|
1791
|
+
fixed semantic order regardless of JSON key order:
|
|
1792
|
+
|
|
1793
|
+
- `fold` — `null` or `{ name, slot, expr, docPath }` (§6.9); the initial
|
|
1794
|
+
value is normalized in the ENCLOSING scope.
|
|
1795
|
+
- `forBindings` — `[{ name, slot, expr, atSlot, allowingEmpty, window }]`;
|
|
1796
|
+
`atSlot` is `-1` without `$at`; `window` is `null` or
|
|
1797
|
+
`{ sliding, size, step }` (§6.10).
|
|
1798
|
+
- `letBindings` — `[{ name, slot, expr }]`.
|
|
1799
|
+
- `asChecks` — `null` or `[{ name, slot, isLet, schema, test, docPath }]`
|
|
1800
|
+
(§6.8); `test` is `null` under analysis without a hook.
|
|
1801
|
+
- `where` — `null` or a node.
|
|
1802
|
+
- `groupby` — `null` or `{ keys: [{ name, slot, expr, docPath }],
|
|
1803
|
+
docPath, accSlots }`; `accSlots` is the frozen list of pre-group
|
|
1804
|
+
binding slots that later clauses actually read (barrier liveness — an
|
|
1805
|
+
over-approximation-free artifact of `collectReadSlots`).
|
|
1806
|
+
- `orderby` — `null` or `{ specs: [{ key, desc, emptyGreatest,
|
|
1807
|
+
collation, collationName, docPath }], docPath, liveSlots }`;
|
|
1808
|
+
`liveSlots` is the analogous snapshot liveness list at the sort
|
|
1809
|
+
barrier.
|
|
1810
|
+
- `count` — `null` or `{ name, slot }`.
|
|
1811
|
+
- `ret` — the `$return` node.
|
|
1812
|
+
- `limits` — the normalized limits record, or `null`.
|
|
1813
|
+
|
|
1814
|
+
### C.5 Slots, frames, and external resolution
|
|
1815
|
+
|
|
1816
|
+
A compiled query evaluates against one frame array. **Slot 0 is the
|
|
1817
|
+
input document.** Every binding site — `$for` names, `$at` names, `$let`
|
|
1818
|
+
names, `$fold`'s accumulator, `$groupby` key names, `$count` — and every
|
|
1819
|
+
external parameter is allocated the next slot from a single counter, in
|
|
1820
|
+
normalization order; nested phrases keep allocating in the same frame.
|
|
1821
|
+
`frameSize` is the final counter (one more than the highest slot; under
|
|
1822
|
+
`limits.steps` one extra slot holds the step counter). Scoping is
|
|
1823
|
+
lexical: a name resolves to the innermost binding; a free name is an
|
|
1824
|
+
**external parameter**, allocated a slot at its first appearance (use is
|
|
1825
|
+
the declaration, §9) and reported in `externals` in that order. Under a
|
|
1826
|
+
closed-world compilation (`options.externals`) a free name outside the
|
|
1827
|
+
declared set is `JQ0005` at its own reference site. An external's `card`
|
|
1828
|
+
is `CARD_ONE` (the caller binds one JSON value).
|
|
1829
|
+
|
|
1830
|
+
### C.6 `docPath` and the freezing guarantee
|
|
1831
|
+
|
|
1832
|
+
Every node's `docPath` is an RFC 6901 JSON Pointer into the query
|
|
1833
|
+
document as written: `''` is the document root (or `/$expr`-prefixed
|
|
1834
|
+
under the version envelope, §4), object member names are
|
|
1835
|
+
pointer-escaped, operator argument positions append the operator key and
|
|
1836
|
+
the array index (`/$where/$eq/0`). It is the same pointer surface the
|
|
1837
|
+
`JQ0xxx` errors carry.
|
|
1838
|
+
|
|
1839
|
+
The tree is **deeply frozen at every level** — nodes, binding records,
|
|
1840
|
+
segment lists, captured values. That is a guarantee, not an
|
|
1841
|
+
implementation detail: a consumer may hold, share and index the tree
|
|
1842
|
+
without defensive copies, and MUST NOT mutate it (annotation passes
|
|
1843
|
+
return new trees; see `annotateTypes`). Captured document fragments
|
|
1844
|
+
(`literal`/`raw` values, schemas) are deep-frozen COPIES — the caller's
|
|
1845
|
+
objects are never frozen.
|
|
1846
|
+
|
|
1847
|
+
### C.7 The compatibility policy
|
|
1848
|
+
|
|
1849
|
+
`AST_VERSION` (currently **1**) is bumped when a node kind is added or
|
|
1850
|
+
removed, a published field is removed or retyped, or an invariant of
|
|
1851
|
+
this appendix changes.
|
|
1852
|
+
|
|
1853
|
+
- Adding a **new optional field** to a node is NOT a version bump;
|
|
1854
|
+
consumers must tolerate unknown fields.
|
|
1855
|
+
- Adding a **node kind** IS a version bump: an exhaustive consumer
|
|
1856
|
+
dispatching on `kind` must fail loudly on a kind it does not know,
|
|
1857
|
+
and the version tells it why.
|
|
1858
|
+
- Explicitly **not** promised: the compiled closures, the operator
|
|
1859
|
+
registry's internals (`compile` bodies), evaluation order beyond what
|
|
1860
|
+
§§2–9 already require, and the two liveness lists' exact contents
|
|
1861
|
+
beyond "the slots later clauses read".
|
|
1862
|
+
|
|
1863
|
+
### C.8 Type annotation (optional pass)
|
|
1864
|
+
|
|
1865
|
+
The tree carries cardinality but no value types. `annotateTypes`
|
|
1866
|
+
(same subpath) is a separate, optional pass:
|
|
1867
|
+
|
|
1868
|
+
```js
|
|
1869
|
+
annotateTypes(analysis, { typeOf }) -> analysis'
|
|
1870
|
+
```
|
|
1871
|
+
|
|
1872
|
+
It returns a NEW analysis whose tree mirrors the input with a frozen
|
|
1873
|
+
`type` tag on every node — `{ type: 'unknown' | 'null' | 'boolean' |
|
|
1874
|
+
'number' | 'integer' | 'string' | 'array' | 'object', optional:
|
|
1875
|
+
boolean }` — never mutating the input and never running during
|
|
1876
|
+
compilation. `typeOf(pathNode)` is the caller's answer for path nodes
|
|
1877
|
+
(a store schema, a model — whatever the caller knows); returning
|
|
1878
|
+
`null`/`undefined` means unknown. Literals and constructors type
|
|
1879
|
+
themselves; quantifiers are boolean; operators propagate through the
|
|
1880
|
+
registry's declared `resultType` families (comparison, arithmetic,
|
|
1881
|
+
string, aggregate — everything undeclared yields `unknown`).
|
|
1882
|
+
**`unknown` is always a safe answer; a wrong tag is a defect.** General
|
|
1883
|
+
inference beyond these rules is out of scope here.
|