@omgbase/oqx 0.15.0 → 0.18.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 +36 -0
- package/README.md +170 -17
- package/dist/ast.d.ts +76 -22
- package/dist/ast.d.ts.map +1 -1
- package/dist/ast.js +21 -6
- package/dist/ast.js.map +1 -1
- package/dist/build.d.ts +37 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +54 -0
- package/dist/build.js.map +1 -0
- package/dist/context.d.ts +13 -3
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +7 -3
- package/dist/context.js.map +1 -1
- package/dist/engine.d.ts +1 -2
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +52 -23
- package/dist/engine.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +2 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +19 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +34 -12
- package/dist/index.js.map +1 -1
- package/dist/lexer.d.ts +15 -1
- package/dist/lexer.d.ts.map +1 -1
- package/dist/lexer.js +104 -51
- package/dist/lexer.js.map +1 -1
- package/dist/optimize/analysis.d.ts.map +1 -1
- package/dist/optimize/analysis.js +5 -3
- package/dist/optimize/analysis.js.map +1 -1
- package/dist/optimize/ir.d.ts +2 -2
- package/dist/optimize/ir.d.ts.map +1 -1
- package/dist/optimize/ir.js +3 -3
- package/dist/optimize/ir.js.map +1 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +616 -251
- package/dist/parser.js.map +1 -1
- package/dist/plan.d.ts.map +1 -1
- package/dist/plan.js +4 -2
- package/dist/plan.js.map +1 -1
- package/dist/print.d.ts +17 -0
- package/dist/print.d.ts.map +1 -0
- package/dist/print.js +266 -0
- package/dist/print.js.map +1 -0
- package/dist/resolve.d.ts +23 -0
- package/dist/resolve.d.ts.map +1 -0
- package/dist/resolve.js +330 -0
- package/dist/resolve.js.map +1 -0
- package/dist/version.d.ts +5 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/dist/walk.d.ts +54 -0
- package/dist/walk.d.ts.map +1 -0
- package/dist/walk.js +139 -0
- package/dist/walk.js.map +1 -0
- package/package.json +1 -1
- package/src/ast.ts +104 -35
- package/src/build.ts +72 -0
- package/src/context.ts +16 -3
- package/src/engine.ts +55 -26
- package/src/errors.ts +3 -2
- package/src/index.ts +38 -13
- package/src/lexer.ts +102 -51
- package/src/optimize/analysis.ts +4 -3
- package/src/optimize/ir.ts +4 -4
- package/src/parser.ts +573 -225
- package/src/plan.ts +3 -2
- package/src/print.ts +274 -0
- package/src/resolve.ts +305 -0
- package/src/version.ts +4 -0
- package/src/walk.ts +180 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,42 @@ All notable changes to `@omgbase/oqx` are recorded here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow
|
|
5
5
|
[Semantic Versioning](https://semver.org/) (pre-1.0: a minor bump may break).
|
|
6
6
|
|
|
7
|
+
## [0.18.0] - 2026-10-10
|
|
8
|
+
|
|
9
|
+
### Minor
|
|
10
|
+
- **OQX 0.17 — join syntax sugar** (`spec/oqx` 0.16 → 0.17; `cases/sugar.json`, 134 new cases, 1131 in all; GRAMMAR §2/§4, SEMANTICS §5b/§14, AST.md). Everything that is pure sugar desugars **in the parser** to existing nodes, so the optimizer, the pushdown planners and `print` see the explicit form; two shapes are new.
|
|
11
|
+
|
|
12
|
+
- **A receiver block without a consumer is `collect`**: `x { … }` ≡ `x collect { … }` in select position, where position and as a whole query (`docs { age > 15 }`). Carve-out: after the last `follow` destination a brace is still the options block (`follow children { depth 2 }`); a destination block keeps its consumer.
|
|
13
|
+
- **Where-first bodies**: a block whose leading expression is *syntactically* a predicate (a comparison, `in`, `&&`/`||`, a prefix `!`/`is`/`not`, an infix `is`, a call, a literal, a binding, a parenthesized expression, a postfix `!` or bracket chain, a consumer test — anything that is not a bare name, a dotted navigation or a lift) drops `where`: `people exists { age > 50 }`, `jobs collect { pay > 2 }`. Bare names stay projections (`docs { is active }` is the bare-field filter); `{ name where age >= 18 }` keeps the keyword; `{ age >= 18, name }` is an error suggesting that form. The top level is unchanged: `age > 15 from people` is still the parse error (where would precede from).
|
|
14
|
+
- **Brackets**: `x[p]` → `x first { where p }`; `x[p]!` → `x single { where p }` under a `required` (zero matches is the required error, many the `single` error); `x[n]` / `x[${i}]` → `x first { offset n }` (out of range is absent; a non-integer binding is the `offset` eval error); `x[n]!` → required positional. Chains compose (`refs(company)[0].name`, `jobs[pay > 2][0]`); a negative or fractional literal index is a parse error; `[`/`]` are tokens.
|
|
15
|
+
- **Postfix `!` = required** — new node `{ kind: "required", expr }` / `Expr::Required`: the value, or the eval error `` `<expr>!` is absent `` (`on <id>` when the row has a scalar identity). Never a filter or a coercion (`0!`, `""!` are values); tightest precedence (`refs(c)[0]!.name` vs `refs(c)[0].name!`); a `!` right after a complete operand is postfix, a `!` at operand start is the negation (`!=` stays one token: write `a! == b` with the space).
|
|
16
|
+
- **`is` / `not` / `and` / `or`** become keywords (reserved: `from where select is not and or` + the literal words). Prefix `is x` ≡ `!!x`, `not x` ≡ `!x`. Infix `x is y` / `x is not y` — new `binary` ops `"is"` / `"is not"` (`BinaryOp::Is` / `IsNot`) — compare **identity** per SEMANTICS §16 (a row's `id` when present, else structural), so `$it is ^$it` compares rows and for scalars `is` ≡ `==`; comparison precedence, non-chainable. `and` / `or` are exact synonyms of `&&` / `||` (same nodes, precedence, short-circuit and value semantics; `print` still writes the symbols). `in` over rows is unchanged (deferred).
|
|
17
|
+
- **Alias reuse in `select`**: an item may reference the items to its left (`boss: ^people[id == ^manager], bossName: boss.name`), inlined by `resolveAliases` / `resolve_aliases` like a `where` alias; a reference to an item to the right is a parse error (a cycle when it refers back). **Behavior change:** `a: b, b: a` is now the cycle error (it was a field swap), and `senior: age > 50, s: senior` projects the alias (it was the row field `senior`).
|
|
18
|
+
- **A directive may stand in value position** (`jobs first { }.pay`): this is how `print` writes a bracket chain back; `op` joins the `Expr` family by position (`isExpr` stays false for it; `transform` rebuilds it without handing it to the mapping). AST.md §8 records the additions (minor; nothing renamed or removed).
|
|
19
|
+
- Existing fixtures that turned from error into meaning: `people exists { age > 50 }`-style blocks (`where` ×4, `errors-parse` consumer-after-projection re-queried as `{ name, jobs exists { } }`, `projection` call-in-block re-queried as `{ name, has(budget) }`), the `[` lex-error case (now a token).
|
|
20
|
+
- **omgbase binding**: the pushdown planners decline `is` / `is not` (identity is the engine's structural notion, not SQL's `IS`), `required` and value-position directives (they may raise), so they stay residual; `query_syntax` gains a "Sugar" block; `docs/query-language.md` §3.6. The surface fixture `implicit-where-in-block` re-queried as `{ kind == "md:task", name }` (still the loud error pointing at `where`).
|
|
21
|
+
- `@omgbase/oqx-syntax`: `[`/`]` are bracket punctuation (no longer lex errors), `is`/`not`/`and`/`or` are keywords highlighted as word operators, `LANGUAGE_VERSION` 0.17; assets regenerated.
|
|
22
|
+
- **OQX 0.18 — the root scope is a row, absolute scope references, canonical `and`/`or`** (`spec/oqx` 0.17 → 0.18; `cases/absolute-refs.json` new (24), `outer-refs` +6 (the `$it is absent at the root scope` case re-queried as the root-object cases), `limit-offset` +2: 1163 cases in all; GRAMMAR §1/§3/§4/§5/§6, SEMANTICS §2/§8/§18/§19, AST.md §1/§3/§6/§8, README).
|
|
23
|
+
|
|
24
|
+
- **The root scope is a row: the host's root object.** SEMANTICS §2 step 1 no longer reads "absent at the root": `$it` at the root scope is the host's root object, so from a top-level row `^$it` is that object, `entries(^$it)` enumerates the named roots in insertion order, `^$it.people` ≡ `^people` (and works as a receiver), `^^$it` from depth two is the same object, and `$it values from $it` is the object as one row. The seam gained **`DataContext.rootObject?(): unknown`** (TypeScript, optional — absent keeps the root row absent) / **`fn root_object(&self) -> Value`** (Rust, provided method defaulting to `Value::Undefined`); `DefaultContext` returns the roots record it was constructed with. Step 5 at the root still reads `ctx.root(name)` — a bare name is never a property of the root object — so hosts with lazy roots are unaffected, and a host-provided root-level name (omgbase will expose the repository id as `$id`, cashing in `$repo.$id` → `^$id` from depth one, `^^$id` from depth two) is readable through `root(name)`. A top-level `limit`/`offset` is evaluated in the root scope *without* its row (SEMANTICS §18): `^$it` reached through a bracket inside the bound is absent, pinned.
|
|
25
|
+
- **`N^name` — absolute scope references.** An unsigned integer literal immediately followed by `^` (no whitespace) names a scope by its syntactic depth (root 0, top-level rows 1, +1 per block body, a destination block's rows one below the frontier row, a follow `where` one deeper than the frontier row — the `visit` depth): `0^docs`, `0^$it`, `1^$path`, `0^$id`, and as a lift target `1^tasks: text`. It **desugars in the parser** to the existing `outer` node (or lift) with `levels = currentDepth − N`; no new AST shape, `print` writes the carets. `N ≥ currentDepth` is the parse error `scope N does not enclose this block (the current scope is depth D)`; `1 ^x` (a space) and `1.0^x` stay the stray-`^` error. Composes with brackets, calls and receivers (`0^docs[0]`, `x[p == 0^flag]`, `entries(0^$it)`, `0^$it.docs collect { … }`, `follow 0^people collect { where manager == 1^id }`, `follow next { where prev == 1^id && 0^k == 2 }`, `limit 1^n`). Both parsers now track the scope depth of every position.
|
|
26
|
+
- **Canonical `print` writes `and` / `or`** for the `where` tree's `and`/`or` nodes and for `logical` expressions (both engines); `!` stays `!`, `is`/`is not` were already words, the JSON `logical.op` stays `"&&"`/`"||"`. Consequence: every query text omgbase's `graph` macro prints flips from `||` to `or` — the pinned `graph` query strings in `spec/surface` (`reads.json`, and the `omgbase-surface` unit test for the multi-root seed) are regenerated; nothing else in the surface fixtures changes.
|
|
27
|
+
- **omgbase binding** (`@omgbase/core` `oqx-js/context.ts`, `omgbase-surface` `context.rs`): the root object is the repository root (`$repo`), so `^$it` from a top-level row is `$repo` and `^$it.docs` the docs scan. The `^docs`/`$repo` redesign and the `$id` root name are a separate change.
|
|
28
|
+
- `@omgbase/oqx-syntax`: the `N^` lexeme highlights as one outer-reference token (`lift`), `LANGUAGE_VERSION` 0.18; assets regenerated.
|
|
29
|
+
|
|
30
|
+
## [0.16.0] - 2026-10-09
|
|
31
|
+
|
|
32
|
+
### Minor
|
|
33
|
+
- **OQX 0.16 — the AST is a first-class contract** (`spec/oqx/AST.md`, fixtures `cases/ast.json` + `cases/ast-spans.json`, 70 new cases; 959 in all). Both engines now produce one normalized tree a tool can reflect on without re-parsing:
|
|
34
|
+
|
|
35
|
+
- **Spans on every node**: `span: [start, end)` in Unicode code points over the raw source (a template's `rawSource`, where a binding occupies its `${n}` marker). The TypeScript lexer counted UTF-16 units; it now counts code points like the Rust one, so the `(at offset N)` of a lex/parse error is the same number in both engines. `toUtf16(span, source)` converts for editors.
|
|
36
|
+
- **Normalized shape**: `kind` on every node (`query`, `subquery`, `follow`, `order` added), optionals materialized (`where`/`limit`/`offset`/`countCmp: null`, `distinct`/`values: false`), operators and consumers serialized as their source words, the never-produced `Expr.index` variant removed. `toJSON(query)` / `ast_to_json(&query)` emit `{ "oqx": "0.16", "kind": "query", … }`; the Rust crate derives serde under the `json` feature (`query_from_json` reads it back).
|
|
37
|
+
- **`where` keeps its surface form**: the parser no longer inlines `select` aliases into `where` (it only validates them — every `errors-parse` fixture is unchanged); the substitution is the exported pure `resolveAliases` / `resolve_aliases`, applied exactly once by the run entry points (`oqx`, `run`, `execute`, `runQuery`; `run_query`, `execute`). **An `Engine.run` now evaluates the query it is given** — code that drives `InMemoryEngine`/`PlannedEngine` directly with a query whose `where` uses aliases must resolve first (resolution is not idempotent: `name: name.upper() … where name`).
|
|
38
|
+
- **Traversal**: `visit(root, { enter, leave })` with `{ path, clause, depth }` (scope depth, 0 = root) and `transform(root, f)`, both driven by one child-key table (`CHILDREN`; Rust `walk::visit`/`transform` with a `Visitor` trait, `Node`, `Clause`); `stripSpans` / `strip_spans`; builders `build.*` / `oqx::build::*` for hand-made nodes (empty span `[0, 0]`).
|
|
39
|
+
- **Canonical printer**: `print(node)` / `print_query` — single spaces, `select` written at the top level, every item aliased, double-quoted strings, minimal parentheses from the precedence table; `printTemplate` → `{ strings, count, indices }` for a tree with bindings (`print` throws, new error stage `print`). Law, enforced by both spec runners over every fixture query: `strip(parse(print(parse(q)))) ≡ strip(parse(q))`.
|
|
40
|
+
- **Migration** for anyone constructing AST literals (after 0.14's `receiver` → `destinations`): add `kind` to `Query`/`Subquery`/`Follow`/`OrderSpec`, `span` to every node, write the materialized fields (`limit: null`, `countCmp: null`, `distinct: false`, `values: false`), drop `index`; or use `build.*`. Rust: struct variants gained `span` (match with `..`), `Expr::Lit(v)` is `Expr::Lit { value, span }`, `Token` gained `end`.
|
|
41
|
+
- **omgbase** (`@omgbase/core`, `omgbase-surface`): the runner's `$self` row-function rewrite is a `transform`, the `semantic("…")` scan a `visit`, aliases are resolved once before the runner's own rewrites; the `graph` macro builds its `follow` walk with the builders and `print`s it instead of splicing strings — the generated query text and every `spec/surface` result are unchanged. `@omgbase/oqx-syntax`'s `LANGUAGE_VERSION` moves to `0.16` (assets regenerated).
|
|
42
|
+
|
|
7
43
|
## [0.15.0] - 2026-10-09
|
|
8
44
|
|
|
9
45
|
### Minor
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ JavaScript tagged template.
|
|
|
16
16
|
> OQX is a language with more than one implementation; the specification is
|
|
17
17
|
> [`spec/oqx`](https://github.com/omgbase/omgbase/tree/main/spec/oqx). The
|
|
18
18
|
> language version is the package version's `major.minor` (`LANGUAGE_VERSION`,
|
|
19
|
-
> `"0.
|
|
19
|
+
> `"0.17"`); the patch digit is this implementation's own.
|
|
20
20
|
> Requirements: Node ≥ 22.13 for `@omgbase/oqx/sqlite`; Node ≥ 22.18 to run the
|
|
21
21
|
> test suite (see [Requirements](#requirements)).
|
|
22
22
|
|
|
@@ -65,9 +65,12 @@ from <collection> from ${people}
|
|
|
65
65
|
|
|
66
66
|
**`select` is the only keyword you may drop, and only when the projection comes
|
|
67
67
|
first** (`name, id from ${people}`). Every other clause always carries its
|
|
68
|
-
keyword —
|
|
69
|
-
`where
|
|
70
|
-
|
|
68
|
+
keyword — with one piece of sugar (0.17): a **block** whose leading expression
|
|
69
|
+
is syntactically a predicate is a `where`-first body (`jobs exists { !end }` ≡
|
|
70
|
+
`jobs exists { where !end }`; a bare name still projects, so `jobs { employer }`
|
|
71
|
+
projects and `jobs { is current }` filters — see §10). At the top level a
|
|
72
|
+
predicate always needs `where`, and `from ${people} count` is an error rather
|
|
73
|
+
than a projection of a field called `count`. Writing a clause out of
|
|
71
74
|
order is a parse error that names the order (`` `select` must come before
|
|
72
75
|
`from` — OQX clause order is select, from, where, follow, order by, limit,
|
|
73
76
|
offset ``). The same body grammar applies inside every consumer block
|
|
@@ -171,7 +174,10 @@ oqx`employee: $it from ${people} where name == "Bob"`; // [{ employee: <
|
|
|
171
174
|
```
|
|
172
175
|
|
|
173
176
|
Inside a nested block `$it` is the inner item; the enclosing row is `^$it`
|
|
174
|
-
(§3). At the root scope
|
|
177
|
+
(§3). At the root scope `$it` is the context's **root object** (language 0.18;
|
|
178
|
+
`DataContext.rootObject()` — `DefaultContext` returns the roots record, so
|
|
179
|
+
`^$it` from a top-level row is `{ people, … }` and `entries(^$it)` lists the
|
|
180
|
+
named roots). `$it` is the only name
|
|
175
181
|
for the current item (language 0.15; through 0.14 it was spelled `$value`, and
|
|
176
182
|
there is no synonym): `$value` is now an ordinary property read, like any other
|
|
177
183
|
non-metadata `$name` — a row `{ "$value": 3 }` projects `3`, a row without one
|
|
@@ -259,9 +265,10 @@ against the value of `x`; a string in `x` can't inject operators or identifiers.
|
|
|
259
265
|
|
|
260
266
|
**`where` sees the `select` aliases.** A name defined in the same body's
|
|
261
267
|
projection may be used in its `where`; an alias shadows a same-named field
|
|
262
|
-
there.
|
|
263
|
-
|
|
264
|
-
|
|
268
|
+
there. The alias's expression is substituted inline before evaluation
|
|
269
|
+
(`resolveAliases`, applied once by the run entry points — the parsed tree keeps
|
|
270
|
+
what you wrote), so it costs nothing at run time and a storage planner still
|
|
271
|
+
sees an ordinary predicate:
|
|
265
272
|
|
|
266
273
|
```js
|
|
267
274
|
oqx`select name, adult: age >= 18 from ${people} where adult`; // ≡ where age >= 18
|
|
@@ -280,7 +287,11 @@ an alias. `order by` is not rewritten — it reads row fields (§8).
|
|
|
280
287
|
against the **current row only**. If the row lacks that property the value is
|
|
281
288
|
absent — it never falls through to an enclosing row. To correlate with an
|
|
282
289
|
enclosing scope you say so explicitly with `^name` ("exactly one scope out";
|
|
283
|
-
`^^name` for two, and so on):
|
|
290
|
+
`^^name` for two, and so on) — or by **depth** with `N^name` (language 0.18):
|
|
291
|
+
`0^people` is the root scope's `people` from any depth, `1^name` the top-level
|
|
292
|
+
row's `name`, `0^$it` the root object. `N^` is an integer touching a caret
|
|
293
|
+
(`1 ^x` is not one); it is pure sugar for the caret form with `levels = depth −
|
|
294
|
+
N`, and `N ≥ the current depth` is a parse error.
|
|
284
295
|
|
|
285
296
|
```js
|
|
286
297
|
const accounts = [
|
|
@@ -642,14 +653,61 @@ with `^` the enclosing scope, as the body does. Give `follow` a stable identity
|
|
|
642
653
|
(`.id` or `by`) when your relation returns fresh objects rather than shared
|
|
643
654
|
references.
|
|
644
655
|
|
|
656
|
+
### 10. Sugar (0.17)
|
|
657
|
+
|
|
658
|
+
Every form below is shorthand for an explicit directive — it **desugars in the
|
|
659
|
+
parser** to the nodes you already know (`print` writes the explicit form, the
|
|
660
|
+
optimizer and any pushdown planner see it), so nothing here has semantics of its
|
|
661
|
+
own except the postfix `!` and the infix `is`:
|
|
662
|
+
|
|
663
|
+
```js
|
|
664
|
+
// A receiver block without a consumer is `collect`; a block whose LEADING
|
|
665
|
+
// expression is syntactically a predicate drops `where`.
|
|
666
|
+
oqx`name, current: jobs { !end_date } from ${people}`
|
|
667
|
+
// ≡ name, current: jobs collect { where !end_date } from ${people}
|
|
668
|
+
oqx`${people} { age > 40 }` // ≡ ${people} collect { where age > 40 }
|
|
669
|
+
oqx`${people} count { jobs exists { } }` // a consumer test is predicate-shaped too
|
|
670
|
+
oqx`${people} first { name }` // a bare name still projects — unchanged
|
|
671
|
+
oqx`${people} first { is active }` // the bare-field filter: `is x` ≡ `!!x`, `not x` ≡ `!x`
|
|
672
|
+
|
|
673
|
+
// Brackets: `[p]` is `first { where p }`, `[n]` is `first { offset n }`;
|
|
674
|
+
// a trailing `!` requires the result (and makes the predicate form `single`).
|
|
675
|
+
oqx`name, boss: ^people[id == ^manager].name from ${people}` // the first match, navigated
|
|
676
|
+
oqx`name, first_job: jobs[0], primary: jobs[pay > 2]! from ${people}`
|
|
677
|
+
// jobs[0] ≡ jobs first { offset 0 } (out of range ⇒ absent)
|
|
678
|
+
// jobs[pay > 2]! ≡ jobs single { where pay > 2 }! (zero or many matches ⇒ an eval error)
|
|
679
|
+
|
|
680
|
+
// Postfix `!` — required: the value, or an eval error naming the expression
|
|
681
|
+
// (and the row's id). Never a filter, never a coercion: 0! and ""! are values.
|
|
682
|
+
oqx`select id!, title from ${docs}` // insist on an identity before a write
|
|
683
|
+
// Tightest precedence: `refs(c)[0]!.name` requires the lookup, `refs(c)[0].name!` the name.
|
|
684
|
+
|
|
685
|
+
// `is` / `is not` compare IDENTITY (a row's id when present, else structurally):
|
|
686
|
+
oqx`name from ${people} where manager is null` // absent (same as == null)
|
|
687
|
+
oqx`${people} count { ^people exists { $it is ^$it } }` // rows compare; `==` leaves that unspecified
|
|
688
|
+
// `and` / `or` are exact synonyms of `&&` / `||`: `title or path` coalesces.
|
|
689
|
+
// `print` writes the words (language 0.18); `!` stays `!`.
|
|
690
|
+
|
|
691
|
+
// A select item may use the items to its left (inlined like a `where` alias):
|
|
692
|
+
oqx`boss: ^people[id == ^manager], bossName: boss.name from ${people}`
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
Reserved words, never a bare field name: `from where select is not and or` and
|
|
696
|
+
the literal words `true false null`. The top level is unchanged — `age > 15
|
|
697
|
+
from ${people}` stays an error, because `where` would precede `from`.
|
|
698
|
+
|
|
645
699
|
### Cheat-sheet
|
|
646
700
|
|
|
647
701
|
```
|
|
648
702
|
select … from … where … follow … order by … limit N offset N the fixed clause order (each at most once)
|
|
649
703
|
name, alias: expr, nested: rel collect { … } projection (`select` may be dropped only here, in first position)
|
|
650
704
|
from ${source} source collection (required at the top level)
|
|
651
|
-
where a == b && rel exists { where … } || !c predicate tree + nested ops (`
|
|
652
|
-
|
|
705
|
+
where a == b && rel exists { where … } || !c predicate tree + nested ops (`and`/`or` ≡ `&&`/`||`)
|
|
706
|
+
rel { pay > 2 } / rel { employer } a block without a consumer is collect; a leading predicate is where-first, a bare name projects
|
|
707
|
+
rel[p] / rel[p]! / rel[0] / rel[0]! first match / exactly one (required) / positional / required positional
|
|
708
|
+
x! required: the value, or an error when absent (tightest; `a!.b` vs `a.b!`)
|
|
709
|
+
is x / not x / x is y / x is not y truthiness (≡ !!x / !x) / identity (a row's id, else structural; absent ≡ null)
|
|
710
|
+
where alias / a: expr, b: a `where` and later items may use this body's select aliases (resolved before evaluation; an alias shadows a field)
|
|
653
711
|
where rel none { … } zero rows (≡ !rel exists { … }; "all" = none over the complement)
|
|
654
712
|
where x in lo..hi / lo...hi / ..hi / lo.. range membership (incl. / excl. / open-ended)
|
|
655
713
|
where x in range(field) coerce a string field to a range, then test coverage
|
|
@@ -669,6 +727,78 @@ follow ^people collect { where manager == ^id } a destination block: successors
|
|
|
669
727
|
${source} <collect|exists|none|count|first|single> { … } whole-query consumer
|
|
670
728
|
```
|
|
671
729
|
|
|
730
|
+
## The AST: reflecting on a query
|
|
731
|
+
|
|
732
|
+
`parse(source)` returns the query's abstract syntax tree, and since language
|
|
733
|
+
0.16 that tree is a **public contract** shared by both implementations
|
|
734
|
+
([`spec/oqx/AST.md`](https://github.com/omgbase/omgbase/tree/main/spec/oqx/AST.md)):
|
|
735
|
+
a tool reflects on a query — which relations it names, which clauses it has,
|
|
736
|
+
where in the text each piece sits — without re-parsing. The objects are plain
|
|
737
|
+
data (no parent pointers, no methods) and *are* the JSON shape.
|
|
738
|
+
|
|
739
|
+
- **Every node has `kind` and `span`.** `span` is `[start, end)` in Unicode
|
|
740
|
+
**code points** over the raw source (for a template, `rawSource(strings)`,
|
|
741
|
+
where a binding occupies its `${n}` marker); `toUtf16(span, source)` converts
|
|
742
|
+
for an editor that counts UTF-16 units. A parenthesized operand spans its
|
|
743
|
+
parentheses; a block (`subquery`) spans its braces.
|
|
744
|
+
- **Optionals are materialized** — `where: null`, `limit: null`, `countCmp: null`,
|
|
745
|
+
`distinct: false`, `values: false` — so nothing has to be defaulted.
|
|
746
|
+
- **`where` keeps its surface form.** A `select` alias used in `where` stays an
|
|
747
|
+
`ident` in the tree; `resolveAliases(query)` is the pure substitution the run
|
|
748
|
+
entry points (`oqx`, `run`, `execute`, `runQuery`) apply exactly once before
|
|
749
|
+
evaluation. An `Engine.run` evaluates the query it is given — if you drive an
|
|
750
|
+
engine yourself, resolve first.
|
|
751
|
+
|
|
752
|
+
```js
|
|
753
|
+
import { parse, visit, transform, print, printTemplate, toJSON, stripSpans, build } from "@omgbase/oqx";
|
|
754
|
+
|
|
755
|
+
const q = parse('select name, n: jobs collect { employer } from people where jobs exists { where !end }');
|
|
756
|
+
|
|
757
|
+
// visit: every node, with its ancestors, the clause it sits in, and the scope depth
|
|
758
|
+
const relations = [];
|
|
759
|
+
visit(q, {
|
|
760
|
+
enter(node, { clause, depth }) {
|
|
761
|
+
if (node.kind === "op") relations.push({ relation: print(node.receiver), clause, depth });
|
|
762
|
+
},
|
|
763
|
+
});
|
|
764
|
+
// → [{ relation: "jobs", clause: "select", depth: 1 }, { relation: "jobs", clause: "where", depth: 1 }]
|
|
765
|
+
|
|
766
|
+
// transform: rebuild with every expression mapped (children first; untouched nodes keep identity + spans)
|
|
767
|
+
const rewritten = transform(q, (e) => (e.kind === "ident" && e.name === "name" ? build.member(build.ident("person"), "name") : e));
|
|
768
|
+
|
|
769
|
+
// print: the canonical source — single spaces, `select` written, minimal parentheses
|
|
770
|
+
print(rewritten);
|
|
771
|
+
// 'select name: person.name, n: jobs collect { employer: employer } from people where jobs exists { where !end }'
|
|
772
|
+
|
|
773
|
+
toJSON(q); // { oqx: "0.17", kind: "query", span: [0, 93], … } — the shared JSON document
|
|
774
|
+
stripSpans(q); // the same tree without spans, for shape comparisons
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
`visit(root, { enter?, leave? })` walks depth-first in canonical source order;
|
|
778
|
+
`enter` may return `false` to skip a subtree. Its context is `{ path, clause,
|
|
779
|
+
depth }` — the ancestors, the clause (`source` · `from` · `where` · `select` ·
|
|
780
|
+
`orderBy` · `limit` · `offset` · `follow` · `follow.destination` · `follow.where`
|
|
781
|
+
· `follow.frontier` · `follow.by`; `null` at the root) and the **scope depth**
|
|
782
|
+
the node is evaluated at (0 = the root scope: a top-level source or bound; 1 =
|
|
783
|
+
a top-level row; a block's body is one deeper than its receiver). Both walks
|
|
784
|
+
are driven by one child-key table, `CHILDREN`, so a future field is added in one
|
|
785
|
+
place.
|
|
786
|
+
|
|
787
|
+
`print(node)` is canonical, and the law `stripSpans(parse(print(parse(q))))`
|
|
788
|
+
≡ `stripSpans(parse(q))` holds for every spec fixture (both runners check it);
|
|
789
|
+
`print(parse(q)) === q` is *not* a law — spelling is normalized. A binding is a
|
|
790
|
+
value, never source text: `print` throws (`OqxError`, stage `"print"`) on one,
|
|
791
|
+
and `printTemplate(node)` returns `{ strings, count, indices }` — the fragments
|
|
792
|
+
around each binding and the binding each gap stands for (the canonical clause
|
|
793
|
+
order can move one past another), to re-run with `parseTemplate(strings,
|
|
794
|
+
count)` and the values permuted by `indices`.
|
|
795
|
+
|
|
796
|
+
`build.*` constructs nodes with the empty span `[0, 0]` and the materialized
|
|
797
|
+
defaults (`build.query(source, { select, where, … })`, `build.op(receiver,
|
|
798
|
+
"collect", build.subquery({ … }))`, `build.field`, `build.collect`,
|
|
799
|
+
`build.follow`, `build.ident`, `build.lit`, `build.binary`, `build.and`, …), so
|
|
800
|
+
a tree you assemble prints with `print` and has the shape of a parsed one.
|
|
801
|
+
|
|
672
802
|
## Data context: string queries and named roots
|
|
673
803
|
|
|
674
804
|
When you don't need interpolation, `execute` runs a plain string query against a
|
|
@@ -687,7 +817,8 @@ the full discriminated result (`{ consumer, … }`).
|
|
|
687
817
|
## Architecture: adapting to other storage & query systems
|
|
688
818
|
|
|
689
819
|
OQX is layered so it can be the front-end for query systems far beyond in-memory
|
|
690
|
-
objects. The parsed `Query` AST is the host-agnostic
|
|
820
|
+
objects. The parsed `Query` AST is the host-agnostic contract (above); execution
|
|
821
|
+
is pluggable.
|
|
691
822
|
|
|
692
823
|
```
|
|
693
824
|
Query AST ─┬─ InMemoryEngine(DataContext) tier 1/2 — drive any data model
|
|
@@ -705,8 +836,10 @@ than approximate it. The conformance suite verifies this.
|
|
|
705
836
|
|
|
706
837
|
The engine never touches host objects directly; it asks a `DataContext` to
|
|
707
838
|
resolve named roots, read properties/relations, coerce results to rows, and
|
|
708
|
-
compute identity
|
|
709
|
-
|
|
839
|
+
compute identity (and, optionally, to supply a `rootObject()` — the root
|
|
840
|
+
scope's row, what `$it` is at the root and `^$it` from a top-level row; without
|
|
841
|
+
one the root row is absent). Implement it to query an ORM graph, a remote API,
|
|
842
|
+
or lazily loaded relations — the query *semantics* stay in OQX. Name resolution is simple
|
|
710
843
|
for a context: a bare `field`, a `.field` segment, and a `^field` outer
|
|
711
844
|
reference each become one `get(row, key)` against exactly the row of the scope
|
|
712
845
|
they name, so a computed relation only needs `get` to know about it:
|
|
@@ -908,7 +1041,8 @@ adapter lives on the `@omgbase/oqx/sqlite` subpath.
|
|
|
908
1041
|
consumer-shaped value: an array for `collect`, a boolean for `exists` / `none`,
|
|
909
1042
|
a number for `count`, a record or `null` for `first` / `single`.
|
|
910
1043
|
- `parse(source)` — a query string → reusable `Query` AST (an `OqxError` with
|
|
911
|
-
`stage: "lex" | "parse"` on bad input)
|
|
1044
|
+
`stage: "lex" | "parse"` on bad input); `parseTemplate(strings, count)` the
|
|
1045
|
+
tagged-template form, with `binding` nodes for the `${…}` slots.
|
|
912
1046
|
- `execute(source, roots?)` — parse and run a string against named roots
|
|
913
1047
|
(`{ people }`), returning the consumer-shaped value.
|
|
914
1048
|
- `run(query, opts?)` — run a parsed `Query`, returning the full `OqxResult`.
|
|
@@ -1032,11 +1166,30 @@ adapter lives on the `@omgbase/oqx/sqlite` subpath.
|
|
|
1032
1166
|
- `map?: (raw) => row` — a custom raw-SQL-row → query-row mapper (overrides
|
|
1033
1167
|
`jsonColumns`).
|
|
1034
1168
|
|
|
1169
|
+
**The AST** (see [above](#the-ast-reflecting-on-a-query); `spec/oqx/AST.md` is normative)
|
|
1170
|
+
|
|
1171
|
+
- `visit(root, { enter?, leave? })` / `transform(root, f)` — the generic walks
|
|
1172
|
+
(`Visitor`, `VisitContext`, `Clause` are the types); `CHILDREN` — the
|
|
1173
|
+
child-key table per node kind that drives them.
|
|
1174
|
+
- `print(node)` / `printTemplate(node)` — the canonical printer (`Template` is
|
|
1175
|
+
`{ strings, count, indices }`).
|
|
1176
|
+
- `resolveAliases(query)` — the `select`-alias substitution the entry points
|
|
1177
|
+
apply before evaluation (SEMANTICS §14).
|
|
1178
|
+
- `toJSON(query)` — the tree stamped with `oqx: LANGUAGE_VERSION`; `stripSpans(node)`
|
|
1179
|
+
— the tree without spans (`Stripped<T>`); `toUtf16(span, source)` and
|
|
1180
|
+
`codePointLength(s)` — code-point ↔ UTF-16 offsets; `rawSource(strings)` —
|
|
1181
|
+
the display source of a template.
|
|
1182
|
+
- `build` — the node builders (`build.query`, `build.op`, `build.subquery`,
|
|
1183
|
+
`build.field`, `build.ident`, …).
|
|
1184
|
+
- `LANGUAGE_VERSION` — the `spec/oqx/VERSION` this implementation conforms to (`"0.17"`).
|
|
1185
|
+
|
|
1035
1186
|
**Types**
|
|
1036
1187
|
|
|
1037
1188
|
- Every AST node type from `src/ast.ts` is re-exported (`Query`, `Subquery`,
|
|
1038
|
-
`Where`, `Expr`, `OpNode`, `SelectItem`, `OrderSpec`, `Follow`,
|
|
1039
|
-
|
|
1189
|
+
`Where`, `Expr`, `OpNode`, `SelectItem`, `OrderSpec`, `Follow`,
|
|
1190
|
+
`AstNode`, `AstKind`, `Span`, …) for tools that reflect on queries and
|
|
1191
|
+
planners that walk the tree; `isExpr(node)` tells an expression from the
|
|
1192
|
+
structural nodes.
|
|
1040
1193
|
|
|
1041
1194
|
## Requirements
|
|
1042
1195
|
|
package/dist/ast.d.ts
CHANGED
|
@@ -1,91 +1,123 @@
|
|
|
1
|
+
/** A half-open source range `[start, end)` in Unicode code points over the raw
|
|
2
|
+
* source (`rawSource` for a template, where a binding occupies its `${n}`
|
|
3
|
+
* marker). The empty span `[0, 0]` marks a node that did not come from source. */
|
|
4
|
+
export type Span = [start: number, end: number];
|
|
1
5
|
/** Query consumers — how a (sub)query's row set is shaped. `none` is the
|
|
2
6
|
* zero-cardinality complement of `exists` (true iff the block yields no rows). */
|
|
3
7
|
export type Consumer = "collect" | "exists" | "none" | "count" | "first" | "single";
|
|
4
8
|
/** Comparison operators usable in a `count { … } <op> <int>` test. */
|
|
5
9
|
export type RelOp = "==" | "!=" | "<" | "<=" | ">" | ">=";
|
|
10
|
+
/** The identity operators (SEMANTICS §5b, since 0.17). */
|
|
11
|
+
export type IdentityOp = "is" | "is not";
|
|
12
|
+
/** The operator of a `binary` expression: a comparison, an identity test, or arithmetic. */
|
|
13
|
+
export type BinaryOp = RelOp | IdentityOp | "+" | "-" | "*" | "/" | "%";
|
|
14
|
+
/** The operator of a `unary` expression. */
|
|
15
|
+
export type UnaryOp = "!" | "-";
|
|
16
|
+
/** The operator of a `logical` expression. */
|
|
17
|
+
export type LogicalOp = "&&" | "||";
|
|
6
18
|
/** Scalar value/predicate expression, evaluated against a row scope + bindings. */
|
|
7
19
|
export type Expr = {
|
|
8
20
|
kind: "lit";
|
|
21
|
+
span: Span;
|
|
9
22
|
value: string | number | boolean | null;
|
|
10
23
|
} | {
|
|
11
24
|
kind: "ident";
|
|
25
|
+
span: Span;
|
|
12
26
|
name: string;
|
|
13
27
|
} | {
|
|
14
28
|
kind: "outer";
|
|
29
|
+
span: Span;
|
|
15
30
|
levels: number;
|
|
16
31
|
name: string;
|
|
17
32
|
} | {
|
|
18
33
|
kind: "binding";
|
|
34
|
+
span: Span;
|
|
19
35
|
index: number;
|
|
20
36
|
} | {
|
|
21
37
|
kind: "member";
|
|
38
|
+
span: Span;
|
|
22
39
|
recv: Expr;
|
|
23
40
|
name: string;
|
|
24
|
-
} | {
|
|
25
|
-
kind: "index";
|
|
26
|
-
recv: Expr;
|
|
27
|
-
index: Expr;
|
|
28
41
|
} | {
|
|
29
42
|
kind: "call";
|
|
43
|
+
span: Span;
|
|
30
44
|
recv: Expr | null;
|
|
31
45
|
name: string;
|
|
32
46
|
args: Expr[];
|
|
33
47
|
} | {
|
|
34
48
|
kind: "unary";
|
|
35
|
-
|
|
49
|
+
span: Span;
|
|
50
|
+
op: UnaryOp;
|
|
51
|
+
expr: Expr;
|
|
52
|
+
} | {
|
|
53
|
+
kind: "required";
|
|
54
|
+
span: Span;
|
|
36
55
|
expr: Expr;
|
|
37
56
|
} | {
|
|
38
57
|
kind: "binary";
|
|
39
|
-
|
|
58
|
+
span: Span;
|
|
59
|
+
op: BinaryOp;
|
|
40
60
|
left: Expr;
|
|
41
61
|
right: Expr;
|
|
42
62
|
} | {
|
|
43
63
|
kind: "logical";
|
|
44
|
-
|
|
64
|
+
span: Span;
|
|
65
|
+
op: LogicalOp;
|
|
45
66
|
left: Expr;
|
|
46
67
|
right: Expr;
|
|
47
68
|
} | {
|
|
48
69
|
kind: "in";
|
|
70
|
+
span: Span;
|
|
49
71
|
left: Expr;
|
|
50
72
|
right: Expr;
|
|
51
73
|
} | {
|
|
52
74
|
kind: "range";
|
|
75
|
+
span: Span;
|
|
53
76
|
lo: Expr | null;
|
|
54
77
|
hi: Expr | null;
|
|
55
78
|
exclusiveEnd: boolean;
|
|
56
|
-
};
|
|
79
|
+
} | OpNode;
|
|
80
|
+
/** One `order by` term. */
|
|
57
81
|
export interface OrderSpec {
|
|
82
|
+
kind: "order";
|
|
83
|
+
span: Span;
|
|
58
84
|
expr: Expr;
|
|
59
85
|
desc: boolean;
|
|
60
86
|
}
|
|
61
87
|
/** One projection item: a named scalar/navigation value, or a named nested
|
|
62
|
-
* collection consumer. `lift` marks a `^name`
|
|
88
|
+
* collection consumer. `lift` marks a `^name` lift. */
|
|
63
89
|
export type SelectItem = {
|
|
64
90
|
kind: "field";
|
|
91
|
+
span: Span;
|
|
65
92
|
name: string;
|
|
66
93
|
expr: Expr;
|
|
67
94
|
lift: number;
|
|
68
95
|
} | {
|
|
69
96
|
kind: "collect";
|
|
97
|
+
span: Span;
|
|
70
98
|
name: string;
|
|
71
99
|
op: OpNode;
|
|
72
100
|
};
|
|
101
|
+
/** The `count { … } <op> <int>` test attached to an `OpNode` — an attribute of
|
|
102
|
+
* the directive, not a node of its own. */
|
|
103
|
+
export interface CountCmp {
|
|
104
|
+
op: RelOp;
|
|
105
|
+
value: number;
|
|
106
|
+
}
|
|
73
107
|
/** A postfix consumer directive over a receiver collection:
|
|
74
108
|
* `<receiver> <op> { <sub> }`, optionally `count { … } <relop> <int>`. */
|
|
75
109
|
export interface OpNode {
|
|
76
110
|
kind: "op";
|
|
111
|
+
span: Span;
|
|
77
112
|
receiver: Expr;
|
|
78
113
|
op: Consumer;
|
|
79
114
|
sub: Subquery;
|
|
80
|
-
countCmp
|
|
81
|
-
op: RelOp;
|
|
82
|
-
value: number;
|
|
83
|
-
};
|
|
115
|
+
countCmp: CountCmp | null;
|
|
84
116
|
/** `distinct` — dedup the rows this directive consumes by their projected
|
|
85
117
|
* value (the `select`), so `count distinct { … }` counts distinct projections
|
|
86
118
|
* and `collect distinct { … }` yields distinct rows. Empty select ⇒ dedup by
|
|
87
119
|
* row identity. Spellable as `<op> distinct { … }` or `{ select distinct … }`. */
|
|
88
|
-
distinct
|
|
120
|
+
distinct: boolean;
|
|
89
121
|
}
|
|
90
122
|
/** One `follow` destination: a relation of the current row (an `Expr` receiver), or a
|
|
91
123
|
* destination block — a select-position directive (`collect`/`first`/`single`) re-evaluated
|
|
@@ -93,6 +125,8 @@ export interface OpNode {
|
|
|
93
125
|
export type FollowDestination = Expr | OpNode;
|
|
94
126
|
/** The recursive `follow` clause. */
|
|
95
127
|
export interface Follow {
|
|
128
|
+
kind: "follow";
|
|
129
|
+
span: Span;
|
|
96
130
|
destinations: FollowDestination[];
|
|
97
131
|
distinct: boolean;
|
|
98
132
|
where: Expr | null;
|
|
@@ -100,8 +134,10 @@ export interface Follow {
|
|
|
100
134
|
depth: number | null;
|
|
101
135
|
by: Expr | null;
|
|
102
136
|
}
|
|
103
|
-
/** A nested (sub)query body. */
|
|
137
|
+
/** A nested (sub)query body — the `{ … }` of a directive. */
|
|
104
138
|
export interface Subquery {
|
|
139
|
+
kind: "subquery";
|
|
140
|
+
span: Span;
|
|
105
141
|
from: Expr[];
|
|
106
142
|
where: Where | null;
|
|
107
143
|
select: SelectItem[];
|
|
@@ -110,33 +146,39 @@ export interface Subquery {
|
|
|
110
146
|
/** `values` — scalar projection mode: the (single) projected expression is the
|
|
111
147
|
* row's result itself rather than being wrapped in a `{ name: value }` record,
|
|
112
148
|
* so `name values` yields `["Bob", …]` and `$it values` yields the rows. */
|
|
113
|
-
values
|
|
149
|
+
values: boolean;
|
|
114
150
|
/** `limit N` / `offset N` — bound the row set AFTER where/order/distinct and
|
|
115
151
|
* BEFORE the consumer reduces it, so `count { … limit 5 }` is at most 5 and
|
|
116
152
|
* `first { … offset 1 }` is the second row. Each is a value expression
|
|
117
153
|
* (a literal, a `${…}` binding, or an outer reference) read as part of the
|
|
118
154
|
* block — `^n` is the enclosing row's field, as everywhere inside `{ … }` —
|
|
119
155
|
* and must yield a non-negative integer. */
|
|
120
|
-
limit
|
|
121
|
-
offset
|
|
156
|
+
limit: Expr | null;
|
|
157
|
+
offset: Expr | null;
|
|
122
158
|
}
|
|
123
159
|
/** The where-clause boolean tree: OQX owns &&/||/!/grouping so consumer ops
|
|
124
160
|
* (invisible to the scalar evaluator) compose with scalar predicates. */
|
|
125
161
|
export type Where = {
|
|
126
162
|
kind: "and";
|
|
163
|
+
span: Span;
|
|
127
164
|
parts: Where[];
|
|
128
165
|
} | {
|
|
129
166
|
kind: "or";
|
|
167
|
+
span: Span;
|
|
130
168
|
parts: Where[];
|
|
131
169
|
} | {
|
|
132
170
|
kind: "not";
|
|
171
|
+
span: Span;
|
|
133
172
|
expr: Where;
|
|
134
173
|
} | {
|
|
135
174
|
kind: "scalar";
|
|
175
|
+
span: Span;
|
|
136
176
|
expr: Expr;
|
|
137
177
|
} | OpNode;
|
|
138
178
|
/** A top-level OQX query. */
|
|
139
179
|
export interface Query {
|
|
180
|
+
kind: "query";
|
|
181
|
+
span: Span;
|
|
140
182
|
source: Expr;
|
|
141
183
|
from: Expr[];
|
|
142
184
|
where: Where | null;
|
|
@@ -145,11 +187,23 @@ export interface Query {
|
|
|
145
187
|
consumer: Consumer;
|
|
146
188
|
follow: Follow | null;
|
|
147
189
|
/** `distinct` — dedup the result rows by their projected value (see OpNode). */
|
|
148
|
-
distinct
|
|
190
|
+
distinct: boolean;
|
|
149
191
|
/** `values` — scalar projection mode (see Subquery). */
|
|
150
|
-
values
|
|
192
|
+
values: boolean;
|
|
151
193
|
/** `limit N` / `offset N` (see Subquery); evaluated at the root scope. */
|
|
152
|
-
limit
|
|
153
|
-
offset
|
|
194
|
+
limit: Expr | null;
|
|
195
|
+
offset: Expr | null;
|
|
154
196
|
}
|
|
197
|
+
/** Every node of the tree. `AstNode["kind"]` is the closed set of node kinds. */
|
|
198
|
+
export type AstNode = Query | Subquery | OpNode | Follow | OrderSpec | SelectItem | Where | Expr;
|
|
199
|
+
/** The closed set of node kinds. */
|
|
200
|
+
export type AstKind = AstNode["kind"];
|
|
201
|
+
/** Whether a node is a scalar `Expr` (an `op` in expression position is not: it
|
|
202
|
+
* is an expression only by position, see AST.md §1). */
|
|
203
|
+
export declare function isExpr(node: AstNode): node is Expr;
|
|
204
|
+
/** The parsed query's JSON form (`toJSON`): the tree with the language version
|
|
205
|
+
* it was produced under. */
|
|
206
|
+
export type QueryJson = Query & {
|
|
207
|
+
oqx: string;
|
|
208
|
+
};
|
|
155
209
|
//# sourceMappingURL=ast.d.ts.map
|
package/dist/ast.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../src/ast.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../src/ast.ts"],"names":[],"mappings":"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"}
|
package/dist/ast.js
CHANGED
|
@@ -1,7 +1,22 @@
|
|
|
1
|
-
// OQX
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
|
|
1
|
+
// The OQX AST — a first-class, language-level contract (spec/oqx/AST.md, since
|
|
2
|
+
// language 0.16). The parser produces it, the interpreter walks it, and tools
|
|
3
|
+
// reflect on it without re-parsing: every node carries `kind` and `span`, every
|
|
4
|
+
// optional field is materialized (`null` / `false`), and the in-memory objects
|
|
5
|
+
// ARE the JSON shape both implementations serialize (plain data, no parent
|
|
6
|
+
// pointers, no methods). There is no separate lowering/IR pass: a "relation" is
|
|
7
|
+
// just an expression evaluated against the current row.
|
|
8
|
+
//
|
|
9
|
+
// `where` keeps its surface form: a `select` alias referenced in `where` stays a
|
|
10
|
+
// bare identifier here; `resolveAliases` (resolve.ts) performs the substitution
|
|
11
|
+
// before evaluation. Nodes built by a tool rather than the parser carry the
|
|
12
|
+
// empty span `[0, 0]` (see `build.ts`).
|
|
13
|
+
/** The expression kinds (`Expr["kind"]`), for `isExpr`. */
|
|
14
|
+
const EXPR_KINDS = new Set([
|
|
15
|
+
"lit", "ident", "outer", "binding", "member", "call", "unary", "required", "binary", "logical", "in", "range",
|
|
16
|
+
]);
|
|
17
|
+
/** Whether a node is a scalar `Expr` (an `op` in expression position is not: it
|
|
18
|
+
* is an expression only by position, see AST.md §1). */
|
|
19
|
+
export function isExpr(node) {
|
|
20
|
+
return EXPR_KINDS.has(node.kind);
|
|
21
|
+
}
|
|
7
22
|
//# sourceMappingURL=ast.js.map
|
package/dist/ast.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ast.js","sourceRoot":"","sources":["../src/ast.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,
|
|
1
|
+
{"version":3,"file":"ast.js","sourceRoot":"","sources":["../src/ast.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,8EAA8E;AAC9E,gFAAgF;AAChF,+EAA+E;AAC/E,2EAA2E;AAC3E,gFAAgF;AAChF,wDAAwD;AACxD,EAAE;AACF,iFAAiF;AACjF,gFAAgF;AAChF,4EAA4E;AAC5E,wCAAwC;AAqKxC,2DAA2D;AAC3D,MAAM,UAAU,GAAwB,IAAI,GAAG,CAAC;IAC9C,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO;CAC9G,CAAC,CAAC;AAEH;wDACwD;AACxD,MAAM,UAAU,MAAM,CAAC,IAAa;IAClC,OAAO,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACnC,CAAC"}
|