@omgbase/oqx 0.18.0 → 0.21.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/CHANGELOG.md +30 -0
- package/README.md +177 -64
- package/dist/ast.d.ts +28 -5
- package/dist/ast.d.ts.map +1 -1
- package/dist/ast.js +1 -1
- package/dist/ast.js.map +1 -1
- package/dist/build.d.ts +11 -2
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +10 -3
- package/dist/build.js.map +1 -1
- package/dist/context.d.ts +19 -5
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +6 -9
- package/dist/context.js.map +1 -1
- package/dist/engine.d.ts +12 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +263 -84
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/lexer.d.ts +3 -3
- package/dist/lexer.d.ts.map +1 -1
- package/dist/lexer.js +79 -2
- package/dist/lexer.js.map +1 -1
- package/dist/optimize/analysis.d.ts +3 -2
- package/dist/optimize/analysis.d.ts.map +1 -1
- package/dist/optimize/analysis.js +18 -15
- package/dist/optimize/analysis.js.map +1 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +250 -118
- package/dist/parser.js.map +1 -1
- package/dist/print.d.ts.map +1 -1
- package/dist/print.js +47 -29
- package/dist/print.js.map +1 -1
- package/dist/resolve.d.ts.map +1 -1
- package/dist/resolve.js +15 -8
- package/dist/resolve.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/walk.d.ts +5 -3
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +34 -11
- package/dist/walk.js.map +1 -1
- package/package.json +1 -1
- package/src/ast.ts +33 -6
- package/src/build.ts +12 -8
- package/src/context.ts +22 -11
- package/src/engine.ts +258 -91
- package/src/index.ts +2 -1
- package/src/lexer.ts +76 -6
- package/src/optimize/analysis.ts +17 -12
- package/src/parser.ts +227 -101
- package/src/print.ts +44 -16
- package/src/resolve.ts +15 -7
- package/src/version.ts +1 -1
- package/src/walk.ts +39 -14
package/dist/walk.d.ts
CHANGED
|
@@ -3,12 +3,14 @@ import type { AstKind, AstNode, Expr, Query, QueryJson, Subquery } from "./ast.j
|
|
|
3
3
|
* root collection (`from <source>` or the directive receiver); `from` the further
|
|
4
4
|
* re-projections; `follow` the `Follow` node itself and `follow.*` its parts.
|
|
5
5
|
* The root node has no clause (`null`). */
|
|
6
|
-
export type Clause = "source" | "from" | "where" | "select" | "orderBy" | "limit" | "offset" | "follow" | "follow.destination" | "follow.
|
|
6
|
+
export type Clause = "source" | "from" | "where" | "select" | "orderBy" | "limit" | "offset" | "follow" | "follow.destination" | "follow.frontier";
|
|
7
7
|
/** Where a visited node sits. `depth` is the scope depth at which the node is
|
|
8
8
|
* evaluated (0 = the root scope: a top-level `source`, `limit`, `offset`; 1 =
|
|
9
9
|
* a top-level row: its `where`, `select`, `order by`, `from` re-projections and
|
|
10
|
-
* `follow`; a block's rows are one deeper than the block's receiver
|
|
11
|
-
* `where`
|
|
10
|
+
* `follow`; a block's rows are one deeper than the block's receiver — a follow
|
|
11
|
+
* destination block's `where` therefore one deeper than the frontier row —
|
|
12
|
+
* and a destination block's `frontier` one level out of its rows, under the
|
|
13
|
+
* walk's enclosing scope). */
|
|
12
14
|
export interface VisitContext {
|
|
13
15
|
/** The ancestors of the node, root first (empty for the root). */
|
|
14
16
|
readonly path: readonly AstNode[];
|
package/dist/walk.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"walk.d.ts","sourceRoot":"","sources":["../src/walk.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAInF;;;2CAG2C;AAC3C,MAAM,MAAM,MAAM,GACd,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,GACvE,QAAQ,GAAG,oBAAoB,GAAG,
|
|
1
|
+
{"version":3,"file":"walk.d.ts","sourceRoot":"","sources":["../src/walk.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAInF;;;2CAG2C;AAC3C,MAAM,MAAM,MAAM,GACd,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,GACvE,QAAQ,GAAG,oBAAoB,GAAG,iBAAiB,CAAC;AAExD;;;;;;8BAM8B;AAC9B,MAAM,WAAW,YAAY;IAC3B,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,SAAS,OAAO,EAAE,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,OAAO;IACtB;iDAC6C;IAC7C,KAAK,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,GAAG,IAAI,CAAC;IACzD,sCAAsC;IACtC,KAAK,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,YAAY,GAAG,IAAI,CAAC;CAChD;AAED;oEACoE;AACpE,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;6DAC6D;AAC7D,eAAO,MAAM,QAAQ,EAAE;IAAE,QAAQ,EAAE,CAAC,IAAI,OAAO,GAAG,SAAS,QAAQ,EAAE;CAmDpE,CAAC;AA0BF,yDAAyD;AACzD,wBAAgB,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAoB3D;AAED;;;4EAG4E;AAC5E,wBAAgB,SAAS,CAAC,CAAC,SAAS,KAAK,GAAG,QAAQ,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,YAAY,KAAK,IAAI,GAAG,CAAC,CAuB5G;AAED;4EAC4E;AAC5E,MAAM,MAAM,QAAQ,CAAC,CAAC,IACpB,CAAC,SAAS,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,GAC5C,CAAC,SAAS,MAAM,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,MAAM,GAAG,KAAK,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,GACrF,CAAC,CAAC;AAEN,wBAAgB,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAWlD;AAED;;gDAEgD;AAChD,wBAAgB,MAAM,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,CAE9C"}
|
package/dist/walk.js
CHANGED
|
@@ -13,6 +13,8 @@ export const CHILDREN = {
|
|
|
13
13
|
{ key: "source", clause: "source" },
|
|
14
14
|
{ key: "from", clause: "from", depth: 1 },
|
|
15
15
|
{ key: "where", clause: "where", depth: 1 },
|
|
16
|
+
// The body's `frontier` (0.20) reads each seed in the row scope, like `where`.
|
|
17
|
+
{ key: "frontier", clause: "follow.frontier", depth: 1 },
|
|
16
18
|
{ key: "follow", clause: "follow", depth: 1 },
|
|
17
19
|
{ key: "orderBy", clause: "orderBy", depth: 1 },
|
|
18
20
|
{ key: "limit", clause: "limit" },
|
|
@@ -22,18 +24,18 @@ export const CHILDREN = {
|
|
|
22
24
|
{ key: "select", clause: "select" },
|
|
23
25
|
{ key: "from", clause: "from" },
|
|
24
26
|
{ key: "where", clause: "where" },
|
|
27
|
+
// A destination block's `frontier` reads the occurrence under the walk's
|
|
28
|
+
// enclosing scope — one level OUT of the block's rows (SEMANTICS §20); any
|
|
29
|
+
// other block's reads its seeds in the row scope, like `where`
|
|
30
|
+
// (`childDepth` tells the two apart by the block's position).
|
|
31
|
+
{ key: "frontier", clause: "follow.frontier", depth: -1 },
|
|
25
32
|
{ key: "follow", clause: "follow" },
|
|
26
33
|
{ key: "orderBy", clause: "orderBy" },
|
|
27
34
|
{ key: "limit", clause: "limit" },
|
|
28
35
|
{ key: "offset", clause: "offset" },
|
|
29
36
|
],
|
|
30
37
|
op: [{ key: "receiver" }, { key: "sub", depth: 1 }],
|
|
31
|
-
follow: [
|
|
32
|
-
{ key: "destinations", clause: "follow.destination" },
|
|
33
|
-
{ key: "where", clause: "follow.where", depth: 1 },
|
|
34
|
-
{ key: "frontier", clause: "follow.frontier" },
|
|
35
|
-
{ key: "by", clause: "follow.by" },
|
|
36
|
-
],
|
|
38
|
+
follow: [{ key: "destinations", clause: "follow.destination" }],
|
|
37
39
|
order: [{ key: "expr" }],
|
|
38
40
|
field: [{ key: "expr" }],
|
|
39
41
|
collect: [{ key: "op" }],
|
|
@@ -53,10 +55,28 @@ export const CHILDREN = {
|
|
|
53
55
|
logical: [{ key: "left" }, { key: "right" }],
|
|
54
56
|
in: [{ key: "left" }, { key: "right" }],
|
|
55
57
|
range: [{ key: "lo" }, { key: "hi" }],
|
|
58
|
+
tuple: [{ key: "items" }],
|
|
59
|
+
// The interpolations; the text parts between them are strings, not nodes.
|
|
60
|
+
template: [{ key: "parts" }],
|
|
56
61
|
};
|
|
57
62
|
function slot(node, key) {
|
|
58
63
|
return node[key] ?? null;
|
|
59
64
|
}
|
|
65
|
+
/** A block is a follow destination block when its `op` is a direct destination
|
|
66
|
+
* of a `follow` node. `path` is the ancestors with the block itself last. */
|
|
67
|
+
function isDestinationBlock(path) {
|
|
68
|
+
const op = path[path.length - 2];
|
|
69
|
+
const owner = path[path.length - 3];
|
|
70
|
+
return op?.kind === "op" && owner?.kind === "follow";
|
|
71
|
+
}
|
|
72
|
+
/** How much deeper a child's scope is: the table's value, except a block's
|
|
73
|
+
* `frontier`, which is one level OUT only for a destination block (the walk's
|
|
74
|
+
* enclosing scope) and the row scope itself for a body with its own `follow`. */
|
|
75
|
+
function childDepth(node, child, path) {
|
|
76
|
+
if (node.kind === "subquery" && child.key === "frontier" && !isDestinationBlock(path))
|
|
77
|
+
return 0;
|
|
78
|
+
return child.depth ?? 0;
|
|
79
|
+
}
|
|
60
80
|
/** Walk a tree depth-first in canonical source order. */
|
|
61
81
|
export function visit(root, visitor) {
|
|
62
82
|
const path = [];
|
|
@@ -70,10 +90,12 @@ export function visit(root, visitor) {
|
|
|
70
90
|
if (v === null)
|
|
71
91
|
continue;
|
|
72
92
|
const c = child.clause ?? clause;
|
|
73
|
-
const d = depth + (child
|
|
74
|
-
if (Array.isArray(v))
|
|
93
|
+
const d = depth + childDepth(node, child, path);
|
|
94
|
+
if (Array.isArray(v)) {
|
|
75
95
|
for (const x of v)
|
|
76
|
-
|
|
96
|
+
if (typeof x !== "string")
|
|
97
|
+
go(x, c, d);
|
|
98
|
+
}
|
|
77
99
|
else
|
|
78
100
|
go(v, c, d);
|
|
79
101
|
}
|
|
@@ -98,11 +120,12 @@ export function transform(root, f) {
|
|
|
98
120
|
if (v === null)
|
|
99
121
|
continue;
|
|
100
122
|
const c = child.clause ?? clause;
|
|
101
|
-
const d = depth + (child
|
|
123
|
+
const d = depth + childDepth(node, child, path);
|
|
102
124
|
let next;
|
|
103
125
|
if (Array.isArray(v)) {
|
|
104
126
|
let changed = false;
|
|
105
|
-
const mapped = v.map((x) => {
|
|
127
|
+
const mapped = v.map((x) => { if (typeof x === "string")
|
|
128
|
+
return x; const y = go(x, c, d); if (y !== x)
|
|
106
129
|
changed = true; return y; });
|
|
107
130
|
next = changed ? mapped : v;
|
|
108
131
|
}
|
package/dist/walk.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"walk.js","sourceRoot":"","sources":["../src/walk.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,4EAA4E;AAC5E,+EAA+E;AAC/E,6EAA6E;AAC7E,sDAAsD;AAGtD,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"walk.js","sourceRoot":"","sources":["../src/walk.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/ast.ts
CHANGED
|
@@ -35,6 +35,10 @@ export type UnaryOp = "!" | "-";
|
|
|
35
35
|
/** The operator of a `logical` expression. */
|
|
36
36
|
export type LogicalOp = "&&" | "||";
|
|
37
37
|
|
|
38
|
+
/** One part of a template literal (since 0.21): a decoded text run, or an
|
|
39
|
+
* interpolated expression. The strings are not nodes (no `kind`, no span). */
|
|
40
|
+
export type TemplatePart = string | Expr;
|
|
41
|
+
|
|
38
42
|
/** Scalar value/predicate expression, evaluated against a row scope + bindings. */
|
|
39
43
|
export type Expr =
|
|
40
44
|
| { kind: "lit"; span: Span; value: string | number | boolean | null }
|
|
@@ -53,6 +57,17 @@ export type Expr =
|
|
|
53
57
|
// (`..5` / `5..`); `exclusiveEnd` distinguishes `1...5` from `1..5`. Evaluates
|
|
54
58
|
// to a runtime range value (see semantics.makeRange); primarily the RHS of `in`.
|
|
55
59
|
| { kind: "range"; span: Span; lo: Expr | null; hi: Expr | null; exclusiveEnd: boolean }
|
|
60
|
+
// A tuple `(e1, e2, …)` (since 0.20): two or more expressions whose values
|
|
61
|
+
// concatenate with one level of flattening (SEMANTICS §3b) — a value and a
|
|
62
|
+
// receiver (`(a, b) { … }`, `follow (a, b) { depth 2 }`). One expression in
|
|
63
|
+
// parentheses is grouping and leaves no node.
|
|
64
|
+
| { kind: "tuple"; span: Span; items: Expr[] }
|
|
65
|
+
// A template literal `` `text ${expr} text` `` (since 0.21): `parts` alternates
|
|
66
|
+
// strictly — text, interpolation, text, … — so it always has an odd length and
|
|
67
|
+
// begins and ends with a (possibly empty) text part; the text is decoded (the
|
|
68
|
+
// GRAMMAR §1 escapes resolved). Evaluates to the concatenation of the text and
|
|
69
|
+
// the interpolated values' string forms (SEMANTICS §7b).
|
|
70
|
+
| { kind: "template"; span: Span; parts: TemplatePart[] }
|
|
56
71
|
// A value-position directive (since 0.17): `recv first { … }.pay`, and what a
|
|
57
72
|
// bracket lookup `x[…]` desugars to. `isExpr` is false for it — an op is an
|
|
58
73
|
// expression only by position; discriminate on `kind === "op"`.
|
|
@@ -104,16 +119,13 @@ export interface OpNode {
|
|
|
104
119
|
* per frontier row, inside which `^` is that row. Discriminate on `kind === "op"`. */
|
|
105
120
|
export type FollowDestination = Expr | OpNode;
|
|
106
121
|
|
|
107
|
-
/** The recursive `follow` clause.
|
|
122
|
+
/** The recursive `follow` clause (since 0.20 just the destinations: a
|
|
123
|
+
* destination's `where`, `frontier` and `depth` live in its own block). */
|
|
108
124
|
export interface Follow {
|
|
109
125
|
kind: "follow";
|
|
110
126
|
span: Span;
|
|
111
127
|
destinations: FollowDestination[]; // ≥ 1, in source order; the walk is their union
|
|
112
128
|
distinct: boolean;
|
|
113
|
-
where: Expr | null; // successor predicate: which successors keep participating
|
|
114
|
-
frontier: Expr | null; // boundary predicate: cut a relation that could continue
|
|
115
|
-
depth: number | null; // 1..8 cap; null = the hard cap
|
|
116
|
-
by: Expr | null; // identity expression for cycle detection / dedup
|
|
117
129
|
}
|
|
118
130
|
|
|
119
131
|
/** A nested (sub)query body — the `{ … }` of a directive. */
|
|
@@ -125,6 +137,16 @@ export interface Subquery {
|
|
|
125
137
|
select: SelectItem[];
|
|
126
138
|
orderBy: OrderSpec[] | null;
|
|
127
139
|
follow: Follow | null;
|
|
140
|
+
/** `frontier P` / `depth n` (since 0.20). In a `follow` destination block
|
|
141
|
+
* `frontier` marks the occurrences reached through this destination for
|
|
142
|
+
* which `P` holds (not expanded; `P` read one level out, under the walk's
|
|
143
|
+
* enclosing scope), and `depth n` makes the destination expand a frontier
|
|
144
|
+
* row only while that row's `$depth < n` (1..8; `null` = the hard cap 8).
|
|
145
|
+
* In any other block `frontier` is legal only with a `follow` and marks the
|
|
146
|
+
* walk's SEEDS, read in the seed's scope like `where` (as on `Query`);
|
|
147
|
+
* `depth` is destination-only. Both `null` when absent. */
|
|
148
|
+
frontier: Expr | null;
|
|
149
|
+
depth: number | null;
|
|
128
150
|
/** `values` — scalar projection mode: the (single) projected expression is the
|
|
129
151
|
* row's result itself rather than being wrapped in a `{ name: value }` record,
|
|
130
152
|
* so `name values` yields `["Bob", …]` and `$it values` yields the rows. */
|
|
@@ -159,6 +181,11 @@ export interface Query {
|
|
|
159
181
|
orderBy: OrderSpec[] | null;
|
|
160
182
|
consumer: Consumer;
|
|
161
183
|
follow: Follow | null;
|
|
184
|
+
/** `frontier P` (since 0.20) — legal only when the body has a `follow`: a
|
|
185
|
+
* seed for which `P` holds (read in the seed's own scope, `^` the enclosing
|
|
186
|
+
* scope, like `where`) is a frontier (`$stop == "frontier"`, not expanded).
|
|
187
|
+
* `null` without one. */
|
|
188
|
+
frontier: Expr | null;
|
|
162
189
|
/** `distinct` — dedup the result rows by their projected value (see OpNode). */
|
|
163
190
|
distinct: boolean;
|
|
164
191
|
/** `values` — scalar projection mode (see Subquery). */
|
|
@@ -176,7 +203,7 @@ export type AstKind = AstNode["kind"];
|
|
|
176
203
|
|
|
177
204
|
/** The expression kinds (`Expr["kind"]`), for `isExpr`. */
|
|
178
205
|
const EXPR_KINDS: ReadonlySet<string> = new Set([
|
|
179
|
-
"lit", "ident", "outer", "binding", "member", "call", "unary", "required", "binary", "logical", "in", "range",
|
|
206
|
+
"lit", "ident", "outer", "binding", "member", "call", "unary", "required", "binary", "logical", "in", "range", "tuple", "template",
|
|
180
207
|
]);
|
|
181
208
|
|
|
182
209
|
/** Whether a node is a scalar `Expr` (an `op` in expression position is not: it
|
package/src/build.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import type {
|
|
7
7
|
BinaryOp, Consumer, CountCmp, Expr, Follow, FollowDestination, LogicalOp, OpNode, OrderSpec, Query, SelectItem, Span,
|
|
8
|
-
Subquery, UnaryOp, Where,
|
|
8
|
+
Subquery, TemplatePart, UnaryOp, Where,
|
|
9
9
|
} from "./ast.ts";
|
|
10
10
|
|
|
11
11
|
/** A fresh empty span (never shared: spans are mutable tuples). */
|
|
@@ -26,6 +26,11 @@ export const binary = (op: BinaryOp, left: Expr, right: Expr): Expr => ({ kind:
|
|
|
26
26
|
export const logical = (op: LogicalOp, left: Expr, right: Expr): Expr => ({ kind: "logical", span: span(), op, left, right });
|
|
27
27
|
export const inOp = (left: Expr, right: Expr): Expr => ({ kind: "in", span: span(), left, right });
|
|
28
28
|
export const range = (lo: Expr | null, hi: Expr | null, exclusiveEnd = false): Expr => ({ kind: "range", span: span(), lo, hi, exclusiveEnd });
|
|
29
|
+
/** `(e1, e2, …)` — a tuple of two or more expressions (SEMANTICS §3b). */
|
|
30
|
+
export const tuple = (items: Expr[]): Expr => ({ kind: "tuple", span: span(), items });
|
|
31
|
+
/** `` `text ${expr} …` `` — a template literal (SEMANTICS §7b); `parts` alternates
|
|
32
|
+
* text, expression, text, … (odd length, text first and last). */
|
|
33
|
+
export const template = (parts: TemplatePart[]): Expr => ({ kind: "template", span: span(), parts });
|
|
29
34
|
|
|
30
35
|
export const scalar = (expr: Expr): Where => ({ kind: "scalar", span: span(), expr });
|
|
31
36
|
export const and = (parts: Where[]): Where => ({ kind: "and", span: span(), parts });
|
|
@@ -40,7 +45,7 @@ export const order = (expr: Expr, desc = false): OrderSpec => ({ kind: "order",
|
|
|
40
45
|
export function subquery(parts: Partial<Omit<Subquery, "kind" | "span">> = {}): Subquery {
|
|
41
46
|
return {
|
|
42
47
|
kind: "subquery", span: span(),
|
|
43
|
-
from: [], where: null, select: [], orderBy: null, follow: null, values: false, limit: null, offset: null,
|
|
48
|
+
from: [], where: null, select: [], orderBy: null, follow: null, frontier: null, depth: null, values: false, limit: null, offset: null,
|
|
44
49
|
...parts,
|
|
45
50
|
};
|
|
46
51
|
}
|
|
@@ -53,11 +58,10 @@ export function op(
|
|
|
53
58
|
return { kind: "op", span: span(), receiver, op: consumer, sub, countCmp: opts.countCmp ?? null, distinct: opts.distinct ?? false };
|
|
54
59
|
}
|
|
55
60
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
):
|
|
60
|
-
return { kind: "follow", span: span(), destinations, distinct: false, where: null, frontier: null, depth: null, by: null, ...opts };
|
|
61
|
+
/** `follow [distinct] <destinations>`; a destination's `where`/`frontier`/`depth`
|
|
62
|
+
* belong to its block (`op(recv, "collect", subquery({ depth: 2 }))`). */
|
|
63
|
+
export function follow(destinations: FollowDestination[], opts: { distinct?: boolean } = {}): Follow {
|
|
64
|
+
return { kind: "follow", span: span(), destinations, distinct: opts.distinct ?? false };
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
/** A top-level query over `source`; every other clause defaults to empty/absent
|
|
@@ -65,7 +69,7 @@ export function follow(
|
|
|
65
69
|
export function query(source: Expr, parts: Partial<Omit<Query, "kind" | "span" | "source">> = {}): Query {
|
|
66
70
|
return {
|
|
67
71
|
kind: "query", span: span(), source,
|
|
68
|
-
from: [], where: null, select: [], orderBy: null, consumer: "collect", follow: null,
|
|
72
|
+
from: [], where: null, select: [], orderBy: null, consumer: "collect", follow: null, frontier: null,
|
|
69
73
|
distinct: false, values: false, limit: null, offset: null,
|
|
70
74
|
...parts,
|
|
71
75
|
};
|
package/src/context.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// The tier-2 seam: a DataContext binds OQX's query semantics to a concrete data
|
|
2
2
|
// model. The engine never touches host objects directly — it asks the context to
|
|
3
3
|
// resolve named roots, read properties/relations, coerce a relation result into
|
|
4
|
-
// rows
|
|
5
|
-
//
|
|
4
|
+
// rows (and say which objects are one row rather than a collection), name a
|
|
5
|
+
// row's identity (`$key`: `follow` dedup, `distinct`, `is`), and optionally
|
|
6
|
+
// supply custom scalar functions/methods. This is what lets the same query semantics run over
|
|
6
7
|
// plain objects, a lazy ORM graph, or a remote API without changing the engine.
|
|
7
8
|
//
|
|
8
9
|
// (Performant execution over a real store is the tier-3 seam — see planner.ts —
|
|
@@ -45,6 +46,13 @@ export interface DataContext {
|
|
|
45
46
|
get(row: unknown, key: string): unknown;
|
|
46
47
|
/** Coerce a relation/source value into rows (may be lazy). */
|
|
47
48
|
toRows(value: unknown): Iterable<unknown>;
|
|
49
|
+
/** Optional: whether an object this context handed out in row position is
|
|
50
|
+
* ONE row — a record, not a collection (SEMANTICS §3, since 0.20) — so a block
|
|
51
|
+
* over it yields one result rather than a one-element list (`doc { title }`
|
|
52
|
+
* is a record; `docs { title }` a list). Asked only for non-array objects;
|
|
53
|
+
* absent, any non-iterable object is one row and any iterable (a `Set`, a
|
|
54
|
+
* lazy handle with `Symbol.iterator`) is a collection. */
|
|
55
|
+
isSingle?(value: unknown): boolean;
|
|
48
56
|
/** Optional: a value this context handed out as a stand-in for a collection
|
|
49
57
|
* it has not read yet (a lazy table handle), resolved to what it stands for.
|
|
50
58
|
* The engine calls it on every value it is about to observe AS A VALUE — an
|
|
@@ -57,8 +65,15 @@ export interface DataContext {
|
|
|
57
65
|
* seen by the language. Absent = identity; a context whose handle already
|
|
58
66
|
* behaves as the collection (a `Proxy` over an array) needs none. */
|
|
59
67
|
materialize?(value: unknown): unknown;
|
|
60
|
-
/**
|
|
61
|
-
|
|
68
|
+
/** Optional: the identity a row is known by — `$key` (SEMANTICS §16, since
|
|
69
|
+
* 0.20): what `follow` cycle detection, the per-step union, `follow distinct`,
|
|
70
|
+
* `distinct` over unprojected rows and `is` compare, and what reading `$key`
|
|
71
|
+
* on the row returns. Absent, a row's identity is its structural value (an
|
|
72
|
+
* entry's is its key either way). A store-backed context answers its row id. */
|
|
73
|
+
identityOf?(row: unknown): unknown;
|
|
74
|
+
/** @deprecated The pre-0.20 spelling of `identityOf`; read when `identityOf`
|
|
75
|
+
* is absent. Removed in the next minor. */
|
|
76
|
+
identity?(row: unknown): unknown;
|
|
62
77
|
/** Optional custom free function; return `{ handled: false }` to defer. */
|
|
63
78
|
callFunction?(name: string, args: unknown[]): CallResult;
|
|
64
79
|
/** Optional custom method; return `{ handled: false }` to defer. */
|
|
@@ -92,8 +107,9 @@ export interface DefaultContextOptions {
|
|
|
92
107
|
/** The default context: ordinary JavaScript objects. Named roots come from a
|
|
93
108
|
* plain `{ name: collection }` map, which is also the root object (`^$it` from
|
|
94
109
|
* a top-level row); properties are OWN keys only (an array exposes only its
|
|
95
|
-
* integer indices, a primitive has none); identity
|
|
96
|
-
*
|
|
110
|
+
* integer indices, a primitive has none); it names no identity of its own, so a
|
|
111
|
+
* row's `$key` is its structural value (SEMANTICS §16) unless a query projects
|
|
112
|
+
* one (`$key: id`). */
|
|
97
113
|
export class DefaultContext implements DataContext {
|
|
98
114
|
private roots: Record<string, unknown>;
|
|
99
115
|
readonly regexDialect: RegexDialect;
|
|
@@ -121,11 +137,6 @@ export class DefaultContext implements DataContext {
|
|
|
121
137
|
return coerceCollection(value);
|
|
122
138
|
}
|
|
123
139
|
|
|
124
|
-
identity(row: unknown): unknown {
|
|
125
|
-
if (row != null && typeof row === "object" && "id" in row) return (row as Record<string, unknown>).id;
|
|
126
|
-
return row;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
140
|
callFunction(name: string, args: unknown[]): CallResult {
|
|
130
141
|
const fn = Object.hasOwn(BUILTIN_FUNCTIONS, name) ? BUILTIN_FUNCTIONS[name] : undefined;
|
|
131
142
|
return fn ? { handled: true, value: fn(args) } : { handled: false };
|