@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.
Files changed (52) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +149 -64
  3. package/dist/ast.d.ts +21 -5
  4. package/dist/ast.d.ts.map +1 -1
  5. package/dist/ast.js +1 -1
  6. package/dist/ast.js.map +1 -1
  7. package/dist/build.d.ts +7 -1
  8. package/dist/build.d.ts.map +1 -1
  9. package/dist/build.js +7 -3
  10. package/dist/build.js.map +1 -1
  11. package/dist/context.d.ts +19 -5
  12. package/dist/context.d.ts.map +1 -1
  13. package/dist/context.js +6 -9
  14. package/dist/context.js.map +1 -1
  15. package/dist/engine.d.ts +12 -1
  16. package/dist/engine.d.ts.map +1 -1
  17. package/dist/engine.js +235 -83
  18. package/dist/engine.js.map +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +2 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/optimize/analysis.d.ts +3 -2
  23. package/dist/optimize/analysis.d.ts.map +1 -1
  24. package/dist/optimize/analysis.js +14 -15
  25. package/dist/optimize/analysis.js.map +1 -1
  26. package/dist/parser.d.ts.map +1 -1
  27. package/dist/parser.js +196 -114
  28. package/dist/parser.js.map +1 -1
  29. package/dist/print.d.ts.map +1 -1
  30. package/dist/print.js +13 -13
  31. package/dist/print.js.map +1 -1
  32. package/dist/resolve.d.ts.map +1 -1
  33. package/dist/resolve.js +11 -8
  34. package/dist/resolve.js.map +1 -1
  35. package/dist/version.d.ts +1 -1
  36. package/dist/version.js +1 -1
  37. package/dist/walk.d.ts +5 -3
  38. package/dist/walk.d.ts.map +1 -1
  39. package/dist/walk.js +26 -8
  40. package/dist/walk.js.map +1 -1
  41. package/package.json +1 -1
  42. package/src/ast.ts +23 -6
  43. package/src/build.ts +8 -7
  44. package/src/context.ts +22 -11
  45. package/src/engine.ts +236 -90
  46. package/src/index.ts +2 -1
  47. package/src/optimize/analysis.ts +13 -12
  48. package/src/parser.ts +180 -96
  49. package/src/print.ts +12 -10
  50. package/src/resolve.ts +11 -7
  51. package/src/version.ts +1 -1
  52. 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
- const where = f.where === null ? null : resolveExpr(f.where, fail);
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.18";
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.where" | "follow.frontier" | "follow.by";
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; a follow
23
- * `where` is one deeper than the frontier row it tests successors of). */
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.depth ?? 0);
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.depth ?? 0);
161
+ const d = depth + childDepth(node, child, path);
141
162
  let next: Slot;
142
163
  if (Array.isArray(v)) {
143
164
  let changed = false;