@omgbase/oqx 0.18.0 → 0.20.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 +24 -0
- package/README.md +149 -64
- package/dist/ast.d.ts +21 -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 +7 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +7 -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 +235 -83
- 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/optimize/analysis.d.ts +3 -2
- package/dist/optimize/analysis.d.ts.map +1 -1
- package/dist/optimize/analysis.js +14 -15
- package/dist/optimize/analysis.js.map +1 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +196 -114
- package/dist/parser.js.map +1 -1
- package/dist/print.d.ts.map +1 -1
- package/dist/print.js +13 -13
- package/dist/print.js.map +1 -1
- package/dist/resolve.d.ts.map +1 -1
- package/dist/resolve.js +11 -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 +26 -8
- package/dist/walk.js.map +1 -1
- package/package.json +1 -1
- package/src/ast.ts +23 -6
- package/src/build.ts +8 -7
- package/src/context.ts +22 -11
- package/src/engine.ts +236 -90
- package/src/index.ts +2 -1
- package/src/optimize/analysis.ts +13 -12
- package/src/parser.ts +180 -96
- package/src/print.ts +12 -10
- package/src/resolve.ts +11 -7
- package/src/version.ts +1 -1
- package/src/walk.ts +32 -11
package/src/resolve.ts
CHANGED
|
@@ -61,12 +61,16 @@ function resolveBody<T extends Query | Subquery>(node: T, fail: Fail): T {
|
|
|
61
61
|
});
|
|
62
62
|
const limit = node.limit === null ? null : resolveExpr(node.limit, fail);
|
|
63
63
|
const offset = node.offset === null ? null : resolveExpr(node.offset, fail);
|
|
64
|
+
// `frontier` (0.20) is not rewritten against the select: a destination
|
|
65
|
+
// block's reads the occurrence, a body's reads the seed row — not a projected
|
|
66
|
+
// record (like `order by`).
|
|
67
|
+
const frontier = node.frontier === null ? null : resolveExpr(node.frontier, fail);
|
|
64
68
|
const source = node.kind === "query" ? resolveExpr(node.source, fail) : null;
|
|
65
69
|
const same = where2 === node.where && select === node.select && follow === node.follow && from === node.from
|
|
66
|
-
&& orderBy === node.orderBy && limit === node.limit && offset === node.offset
|
|
70
|
+
&& orderBy === node.orderBy && limit === node.limit && offset === node.offset && frontier === node.frontier
|
|
67
71
|
&& (node.kind !== "query" || source === node.source);
|
|
68
72
|
if (same) return node;
|
|
69
|
-
const out = { ...node, where: where2, select, follow, from, orderBy, limit, offset };
|
|
73
|
+
const out = { ...node, where: where2, select, follow, frontier, from, orderBy, limit, offset };
|
|
70
74
|
if (node.kind === "query") (out as Query).source = source!;
|
|
71
75
|
return out;
|
|
72
76
|
}
|
|
@@ -79,11 +83,7 @@ function resolveOp(op: OpNode, fail: Fail): OpNode {
|
|
|
79
83
|
|
|
80
84
|
function resolveFollow(f: Follow, fail: Fail): Follow {
|
|
81
85
|
const destinations = mapSame(f.destinations, (d: FollowDestination) => (d.kind === "op" ? resolveOp(d, fail) : resolveExpr(d, fail)));
|
|
82
|
-
|
|
83
|
-
const frontier = f.frontier === null ? null : resolveExpr(f.frontier, fail);
|
|
84
|
-
const by = f.by === null ? null : resolveExpr(f.by, fail);
|
|
85
|
-
if (destinations === f.destinations && where === f.where && frontier === f.frontier && by === f.by) return f;
|
|
86
|
-
return { ...f, destinations, where, frontier, by };
|
|
86
|
+
return destinations === f.destinations ? f : { ...f, destinations };
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
// Nested blocks inside a where tree are their own scopes: resolve each against
|
|
@@ -137,6 +137,10 @@ function mapExpr(e: Expr, f: (e: Expr) => Expr | null): Expr {
|
|
|
137
137
|
const hi = e.hi ? go(e.hi) : null;
|
|
138
138
|
return lo === e.lo && hi === e.hi ? e : { ...e, lo, hi };
|
|
139
139
|
}
|
|
140
|
+
case "tuple": {
|
|
141
|
+
const items = mapSame(e.items, go);
|
|
142
|
+
return items === e.items ? e : { ...e, items };
|
|
143
|
+
}
|
|
140
144
|
case "op": { // the receiver is read in this scope; the block is its own scope
|
|
141
145
|
const receiver = go(e.receiver);
|
|
142
146
|
return receiver === e.receiver ? e : { ...e, receiver };
|
package/src/version.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** The OQX language version (`spec/oqx/VERSION`, `major.minor`) this implementation
|
|
2
2
|
* conforms to — distinct from the package's own semver, whose `major.minor` it
|
|
3
3
|
* equals. Stamped on every `toJSON` document. */
|
|
4
|
-
export const LANGUAGE_VERSION = "0.
|
|
4
|
+
export const LANGUAGE_VERSION = "0.20";
|
package/src/walk.ts
CHANGED
|
@@ -14,13 +14,15 @@ import { LANGUAGE_VERSION } from "./version.ts";
|
|
|
14
14
|
* The root node has no clause (`null`). */
|
|
15
15
|
export type Clause =
|
|
16
16
|
| "source" | "from" | "where" | "select" | "orderBy" | "limit" | "offset"
|
|
17
|
-
| "follow" | "follow.destination" | "follow.
|
|
17
|
+
| "follow" | "follow.destination" | "follow.frontier";
|
|
18
18
|
|
|
19
19
|
/** Where a visited node sits. `depth` is the scope depth at which the node is
|
|
20
20
|
* evaluated (0 = the root scope: a top-level `source`, `limit`, `offset`; 1 =
|
|
21
21
|
* a top-level row: its `where`, `select`, `order by`, `from` re-projections and
|
|
22
|
-
* `follow`; a block's rows are one deeper than the block's receiver
|
|
23
|
-
* `where`
|
|
22
|
+
* `follow`; a block's rows are one deeper than the block's receiver — a follow
|
|
23
|
+
* destination block's `where` therefore one deeper than the frontier row —
|
|
24
|
+
* and a destination block's `frontier` one level out of its rows, under the
|
|
25
|
+
* walk's enclosing scope). */
|
|
24
26
|
export interface VisitContext {
|
|
25
27
|
/** The ancestors of the node, root first (empty for the root). */
|
|
26
28
|
readonly path: readonly AstNode[];
|
|
@@ -52,6 +54,8 @@ export const CHILDREN: { readonly [K in AstKind]: readonly ChildKey[] } = {
|
|
|
52
54
|
{ key: "source", clause: "source" },
|
|
53
55
|
{ key: "from", clause: "from", depth: 1 },
|
|
54
56
|
{ key: "where", clause: "where", depth: 1 },
|
|
57
|
+
// The body's `frontier` (0.20) reads each seed in the row scope, like `where`.
|
|
58
|
+
{ key: "frontier", clause: "follow.frontier", depth: 1 },
|
|
55
59
|
{ key: "follow", clause: "follow", depth: 1 },
|
|
56
60
|
{ key: "orderBy", clause: "orderBy", depth: 1 },
|
|
57
61
|
{ key: "limit", clause: "limit" },
|
|
@@ -61,18 +65,18 @@ export const CHILDREN: { readonly [K in AstKind]: readonly ChildKey[] } = {
|
|
|
61
65
|
{ key: "select", clause: "select" },
|
|
62
66
|
{ key: "from", clause: "from" },
|
|
63
67
|
{ key: "where", clause: "where" },
|
|
68
|
+
// A destination block's `frontier` reads the occurrence under the walk's
|
|
69
|
+
// enclosing scope — one level OUT of the block's rows (SEMANTICS §20); any
|
|
70
|
+
// other block's reads its seeds in the row scope, like `where`
|
|
71
|
+
// (`childDepth` tells the two apart by the block's position).
|
|
72
|
+
{ key: "frontier", clause: "follow.frontier", depth: -1 },
|
|
64
73
|
{ key: "follow", clause: "follow" },
|
|
65
74
|
{ key: "orderBy", clause: "orderBy" },
|
|
66
75
|
{ key: "limit", clause: "limit" },
|
|
67
76
|
{ key: "offset", clause: "offset" },
|
|
68
77
|
],
|
|
69
78
|
op: [{ key: "receiver" }, { key: "sub", depth: 1 }],
|
|
70
|
-
follow: [
|
|
71
|
-
{ key: "destinations", clause: "follow.destination" },
|
|
72
|
-
{ key: "where", clause: "follow.where", depth: 1 },
|
|
73
|
-
{ key: "frontier", clause: "follow.frontier" },
|
|
74
|
-
{ key: "by", clause: "follow.by" },
|
|
75
|
-
],
|
|
79
|
+
follow: [{ key: "destinations", clause: "follow.destination" }],
|
|
76
80
|
order: [{ key: "expr" }],
|
|
77
81
|
field: [{ key: "expr" }],
|
|
78
82
|
collect: [{ key: "op" }],
|
|
@@ -92,6 +96,7 @@ export const CHILDREN: { readonly [K in AstKind]: readonly ChildKey[] } = {
|
|
|
92
96
|
logical: [{ key: "left" }, { key: "right" }],
|
|
93
97
|
in: [{ key: "left" }, { key: "right" }],
|
|
94
98
|
range: [{ key: "lo" }, { key: "hi" }],
|
|
99
|
+
tuple: [{ key: "items" }],
|
|
95
100
|
};
|
|
96
101
|
|
|
97
102
|
type Slot = AstNode | AstNode[] | null;
|
|
@@ -100,6 +105,22 @@ function slot(node: AstNode, key: string): Slot {
|
|
|
100
105
|
return (node as unknown as Record<string, Slot>)[key] ?? null;
|
|
101
106
|
}
|
|
102
107
|
|
|
108
|
+
/** A block is a follow destination block when its `op` is a direct destination
|
|
109
|
+
* of a `follow` node. `path` is the ancestors with the block itself last. */
|
|
110
|
+
function isDestinationBlock(path: readonly AstNode[]): boolean {
|
|
111
|
+
const op = path[path.length - 2];
|
|
112
|
+
const owner = path[path.length - 3];
|
|
113
|
+
return op?.kind === "op" && owner?.kind === "follow";
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** How much deeper a child's scope is: the table's value, except a block's
|
|
117
|
+
* `frontier`, which is one level OUT only for a destination block (the walk's
|
|
118
|
+
* enclosing scope) and the row scope itself for a body with its own `follow`. */
|
|
119
|
+
function childDepth(node: AstNode, child: ChildKey, path: readonly AstNode[]): number {
|
|
120
|
+
if (node.kind === "subquery" && child.key === "frontier" && !isDestinationBlock(path)) return 0;
|
|
121
|
+
return child.depth ?? 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
103
124
|
/** Walk a tree depth-first in canonical source order. */
|
|
104
125
|
export function visit(root: AstNode, visitor: Visitor): void {
|
|
105
126
|
const path: AstNode[] = [];
|
|
@@ -112,7 +133,7 @@ export function visit(root: AstNode, visitor: Visitor): void {
|
|
|
112
133
|
const v = slot(node, child.key);
|
|
113
134
|
if (v === null) continue;
|
|
114
135
|
const c = child.clause ?? clause;
|
|
115
|
-
const d = depth + (child
|
|
136
|
+
const d = depth + childDepth(node, child, path);
|
|
116
137
|
if (Array.isArray(v)) for (const x of v) go(x, c, d);
|
|
117
138
|
else go(v, c, d);
|
|
118
139
|
}
|
|
@@ -137,7 +158,7 @@ export function transform<T extends Query | Subquery>(root: T, f: (expr: Expr, c
|
|
|
137
158
|
const v = slot(node, child.key);
|
|
138
159
|
if (v === null) continue;
|
|
139
160
|
const c = child.clause ?? clause;
|
|
140
|
-
const d = depth + (child
|
|
161
|
+
const d = depth + childDepth(node, child, path);
|
|
141
162
|
let next: Slot;
|
|
142
163
|
if (Array.isArray(v)) {
|
|
143
164
|
let changed = false;
|