@omgbase/oqx 0.24.0 → 0.26.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 +13 -0
- package/README.md +267 -202
- package/dist/ast.d.ts +19 -1
- package/dist/ast.d.ts.map +1 -1
- package/dist/ast.js.map +1 -1
- package/dist/build.d.ts +3 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +4 -2
- package/dist/build.js.map +1 -1
- package/dist/engine.d.ts +2 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +95 -44
- package/dist/engine.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 +12 -2
- package/dist/optimize/analysis.js.map +1 -1
- package/dist/optimize/ir.d.ts +4 -1
- package/dist/optimize/ir.d.ts.map +1 -1
- package/dist/optimize/ir.js +1 -0
- package/dist/optimize/ir.js.map +1 -1
- package/dist/optimize/rules.d.ts.map +1 -1
- package/dist/optimize/rules.js +3 -2
- package/dist/optimize/rules.js.map +1 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +206 -42
- package/dist/parser.js.map +1 -1
- package/dist/print.d.ts +2 -2
- package/dist/print.d.ts.map +1 -1
- package/dist/print.js +88 -25
- package/dist/print.js.map +1 -1
- package/dist/references.d.ts.map +1 -1
- package/dist/references.js +39 -1
- package/dist/references.js.map +1 -1
- package/dist/resolve.d.ts.map +1 -1
- package/dist/resolve.js +9 -2
- 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 +1 -1
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +5 -0
- package/dist/walk.js.map +1 -1
- package/package.json +1 -1
- package/src/ast.ts +20 -1
- package/src/build.ts +5 -3
- package/src/engine.ts +89 -39
- package/src/optimize/analysis.ts +9 -2
- package/src/optimize/ir.ts +5 -1
- package/src/optimize/rules.ts +3 -2
- package/src/parser.ts +193 -41
- package/src/print.ts +92 -30
- package/src/references.ts +34 -2
- package/src/resolve.ts +10 -3
- package/src/version.ts +1 -1
- package/src/walk.ts +6 -0
package/src/references.ts
CHANGED
|
@@ -25,7 +25,11 @@
|
|
|
25
25
|
// (`target.contains("x")` references `target`); a name a nested block
|
|
26
26
|
// LIFTS onto a scope (`^open: value`, SEMANTICS §19) is a binding of that
|
|
27
27
|
// scope, so reading it there (`select open`, `^open` from deeper) is not a
|
|
28
|
-
// reference either
|
|
28
|
+
// reference either — and neither is a name a `flatten` bound (0.25,
|
|
29
|
+
// SEMANTICS §25) in the clauses after it: a later `flatten`, `group by`,
|
|
30
|
+
// `having`, the select, `order by`, or `^name` from a block under them; in
|
|
31
|
+
// its own expression and before the clause (`where`, `frontier`, `follow`)
|
|
32
|
+
// the name is still the row's property;
|
|
29
33
|
// • a `select` alias is not a reference where the language resolves it
|
|
30
34
|
// (SEMANTICS §14): in the same body's `where`, and in the items to its
|
|
31
35
|
// right — a bare identifier naming such an alias, or a chain headed by it,
|
|
@@ -54,7 +58,7 @@
|
|
|
54
58
|
// are cut after the last step at its scope's depth. The empty list is the root
|
|
55
59
|
// scope.
|
|
56
60
|
|
|
57
|
-
import type { AstNode, Expr, Follow, GroupBy, Query, SelectItem, Span, Subquery } from "./ast.ts";
|
|
61
|
+
import type { AstNode, Expr, Flatten, Follow, GroupBy, Query, SelectItem, Span, Subquery } from "./ast.ts";
|
|
58
62
|
import type { Clause } from "./walk.ts";
|
|
59
63
|
import { visit } from "./walk.ts";
|
|
60
64
|
|
|
@@ -122,6 +126,9 @@ export function references(root: Query | Subquery): Reference[] {
|
|
|
122
126
|
const levels = head.kind === "outer" ? head.levels : 0;
|
|
123
127
|
const depth = ctx.depth - levels;
|
|
124
128
|
if (isLiftBound(name, depth, ctx.path, lifts)) return false;
|
|
129
|
+
// A name a `flatten` bound (0.25) is an alias of the expression already
|
|
130
|
+
// reported in clause `flatten`, read in the clauses after it.
|
|
131
|
+
if (isFlattenBound(name, depth, ctx.path, node)) return false;
|
|
125
132
|
// At a group scope (0.24) a bare key name is the key — an alias of the
|
|
126
133
|
// expression reported in clause `groupBy` — and `rows` is the language's
|
|
127
134
|
// value; neither reads a row.
|
|
@@ -191,6 +198,31 @@ function liftsLandingOn(body: Query | Subquery): ReadonlySet<string> {
|
|
|
191
198
|
return names;
|
|
192
199
|
}
|
|
193
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Whether `name`, read at scope `depth`, is bound there by a `flatten` of the
|
|
203
|
+
* body whose rows are at `depth` (SEMANTICS §25) — the position lies in a
|
|
204
|
+
* clause evaluated after that flatten: a later `flatten`, `group by`, `having`,
|
|
205
|
+
* the select, or `order by` (`where`, `frontier`, `follow` and the body's
|
|
206
|
+
* sources run before any flatten, and a flatten's own expression before itself).
|
|
207
|
+
*/
|
|
208
|
+
function isFlattenBound(name: string, depth: number, path: readonly AstNode[], node: AstNode): boolean {
|
|
209
|
+
const i = bodyIndexAt(path, depth);
|
|
210
|
+
if (i === -1) return false;
|
|
211
|
+
const body = path[i] as Query | Subquery;
|
|
212
|
+
if (body.flatten.length === 0) return false;
|
|
213
|
+
const child = path[i + 1] ?? node;
|
|
214
|
+
let upto: number;
|
|
215
|
+
if (body.select.includes(child as SelectItem) || child === body.groupBy || child === body.having || (body.orderBy?.includes(child as never) ?? false)) {
|
|
216
|
+
upto = body.flatten.length;
|
|
217
|
+
} else {
|
|
218
|
+
const k = body.flatten.indexOf(child as Flatten);
|
|
219
|
+
if (k === -1) return false;
|
|
220
|
+
upto = k;
|
|
221
|
+
}
|
|
222
|
+
for (let j = 0; j < upto; j++) if (body.flatten[j]!.name === name) return true;
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
|
|
194
226
|
/**
|
|
195
227
|
* Whether `name`, read at scope `depth`, is a value of a GROUP scope there
|
|
196
228
|
* (SEMANTICS §24): the body whose rows are at `depth` has a `group by`, the
|
package/src/resolve.ts
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
// given. Untouched nodes are returned by reference, so a cached query keeps the
|
|
27
27
|
// identity of every block the rewrite did not reach.
|
|
28
28
|
|
|
29
|
-
import type { Expr, Follow, FollowDestination, GroupBy, OpNode, Query, SelectItem, Subquery, Where } from "./ast.ts";
|
|
29
|
+
import type { Expr, Flatten, Follow, FollowDestination, GroupBy, OpNode, Query, SelectItem, Subquery, Where } from "./ast.ts";
|
|
30
30
|
import { OqxError } from "./errors.ts";
|
|
31
31
|
|
|
32
32
|
/** How a resolution failure is reported: the parser raises its positioned parse
|
|
@@ -61,6 +61,13 @@ function resolveBody<T extends Query | Subquery>(node: T, fail: Fail): T {
|
|
|
61
61
|
// The keys are read on the rows, not rewritten (a key IS the name the post-group
|
|
62
62
|
// clauses read); their nested blocks resolve against their own select.
|
|
63
63
|
const groupBy = node.groupBy === null ? null : resolveGroupBy(node.groupBy, fail);
|
|
64
|
+
// A `flatten` expression (0.25) reads the row, not the projection — like
|
|
65
|
+
// `order by` and `frontier` it is not rewritten against the select; its
|
|
66
|
+
// nested blocks resolve against their own select.
|
|
67
|
+
const flatten = mapSame(node.flatten, (f: Flatten) => {
|
|
68
|
+
const expr = resolveExpr(f.expr, fail);
|
|
69
|
+
return expr === f.expr ? f : { ...f, expr };
|
|
70
|
+
});
|
|
64
71
|
const from = mapSame(node.from, (e) => resolveExpr(e, fail));
|
|
65
72
|
const orderBy = node.orderBy === null ? null : mapSame(node.orderBy, (o) => {
|
|
66
73
|
const expr = resolveExpr(o.expr, fail);
|
|
@@ -75,10 +82,10 @@ function resolveBody<T extends Query | Subquery>(node: T, fail: Fail): T {
|
|
|
75
82
|
const source = node.kind === "query" ? resolveExpr(node.source, fail) : null;
|
|
76
83
|
const same = where2 === node.where && select === node.select && follow === node.follow && from === node.from
|
|
77
84
|
&& orderBy === node.orderBy && limit === node.limit && offset === node.offset && frontier === node.frontier
|
|
78
|
-
&& groupBy === node.groupBy && having2 === node.having
|
|
85
|
+
&& groupBy === node.groupBy && having2 === node.having && flatten === node.flatten
|
|
79
86
|
&& (node.kind !== "query" || source === node.source);
|
|
80
87
|
if (same) return node;
|
|
81
|
-
const out = { ...node, where: where2, select, follow, frontier, groupBy, having: having2, from, orderBy, limit, offset };
|
|
88
|
+
const out = { ...node, where: where2, select, follow, frontier, flatten, groupBy, having: having2, from, orderBy, limit, offset };
|
|
82
89
|
if (node.kind === "query") (out as Query).source = source!;
|
|
83
90
|
return out;
|
|
84
91
|
}
|
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.26";
|
package/src/walk.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { LANGUAGE_VERSION } from "./version.ts";
|
|
|
15
15
|
export type Clause =
|
|
16
16
|
| "source" | "from" | "where" | "select" | "orderBy" | "limit" | "offset"
|
|
17
17
|
| "follow" | "follow.destination" | "follow.frontier"
|
|
18
|
+
| "flatten" // a `flatten` node and its expression (0.25)
|
|
18
19
|
| "groupBy" | "having"; // the `group by` keys and the post-group predicate (0.24)
|
|
19
20
|
|
|
20
21
|
/** Where a visited node sits. `depth` is the scope depth at which the node is
|
|
@@ -58,6 +59,9 @@ export const CHILDREN: { readonly [K in AstKind]: readonly ChildKey[] } = {
|
|
|
58
59
|
// The body's `frontier` (0.20) reads each seed in the row scope, like `where`.
|
|
59
60
|
{ key: "frontier", clause: "follow.frontier", depth: 1 },
|
|
60
61
|
{ key: "follow", clause: "follow", depth: 1 },
|
|
62
|
+
// A `flatten` expression is read on each row, like `where` (0.25); a
|
|
63
|
+
// flattened row sits at the same depth as its parent.
|
|
64
|
+
{ key: "flatten", clause: "flatten", depth: 1 },
|
|
61
65
|
// `group by` keys are read on the rows, `having` on the groups — both at
|
|
62
66
|
// the row depth (the group scope sits where the rows did; 0.24).
|
|
63
67
|
{ key: "groupBy", clause: "groupBy", depth: 1 },
|
|
@@ -76,6 +80,7 @@ export const CHILDREN: { readonly [K in AstKind]: readonly ChildKey[] } = {
|
|
|
76
80
|
// (`childDepth` tells the two apart by the block's position).
|
|
77
81
|
{ key: "frontier", clause: "follow.frontier", depth: -1 },
|
|
78
82
|
{ key: "follow", clause: "follow" },
|
|
83
|
+
{ key: "flatten", clause: "flatten" },
|
|
79
84
|
{ key: "groupBy", clause: "groupBy" },
|
|
80
85
|
{ key: "having", clause: "having" },
|
|
81
86
|
{ key: "orderBy", clause: "orderBy" },
|
|
@@ -84,6 +89,7 @@ export const CHILDREN: { readonly [K in AstKind]: readonly ChildKey[] } = {
|
|
|
84
89
|
],
|
|
85
90
|
op: [{ key: "receiver" }, { key: "sub", depth: 1 }],
|
|
86
91
|
follow: [{ key: "destinations", clause: "follow.destination" }],
|
|
92
|
+
flatten: [{ key: "expr" }],
|
|
87
93
|
groupBy: [{ key: "keys" }],
|
|
88
94
|
key: [{ key: "expr" }],
|
|
89
95
|
order: [{ key: "expr" }],
|