@omgbase/oqx 0.7.0 → 0.9.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/README.md CHANGED
@@ -101,8 +101,51 @@ oqx`label: name, decade: age / 10 from ${people} where name == "Bob"`;
101
101
  // [{ label: "Bob", decade: 4.1 }]
102
102
  ```
103
103
 
104
+ An item that is not a plain navigation — a call, arithmetic, a comparison — has
105
+ no natural key, so it must be aliased (`n: size(jobs)`), unless the projection is
106
+ in `values` mode (next).
107
+
104
108
  The `select` keyword is optional and works in any position — `select name from …`
105
- is identical to `name from …`. (It's the hook for a future `select distinct`.)
109
+ is identical to `name from …` (and hosts `select distinct`, §6).
110
+
111
+ **`values` — scalar projection.** Ordinarily every row projects to a record. Add
112
+ `values` after a projection of exactly **one** item to get the value itself:
113
+
114
+ ```js
115
+ oqx`name values from ${people}`; // ["Bob", "Alice", "Carol"]
116
+ oqx`name.upper() values from ${people} where age < 30`; // ["CAROL"] (no alias needed)
117
+ oqx`${people} first { name values where age > 50 }`; // "Alice"
118
+ ```
119
+
120
+ `values` is a result-shape mode, not a consumer: it works in the top-level
121
+ projection and inside any `collect` / `first` / `single` block, and composes with
122
+ `distinct` (`select distinct employer values from ${jobs}` is the distinct set of
123
+ employers as strings, not `{ employer }` records). An alias, if present, is
124
+ ignored; a lift (`^name:`) cannot be combined with it.
125
+
126
+ **`$value` — the current item.** Every scope has a current value; `$value` is
127
+ that exact value, whatever its type (an object row or a plain scalar). Bare names
128
+ still navigate it (`name` ≡ `$value.name`), so `$value` matters exactly where
129
+ there is nothing to navigate: collections of numbers or strings, or handing the
130
+ whole row somewhere. Together with `values` this makes scalar collections
131
+ first-class:
132
+
133
+ ```js
134
+ const scores = [10, 60, 70, 45];
135
+ oqx`$value values from ${scores} where $value > 50`; // [60, 70]
136
+ oqx`$value values from ${scores} order by $value desc`; // [70, 60, 45, 10]
137
+
138
+ const players = [{ name: "Ann", scores: [10, 60, 70] }, { name: "Ben", scores: [45] }];
139
+ oqx`name, big: scores collect { $value values where $value > 50 } from ${players}`;
140
+ // [{ name: "Ann", big: [60, 70] }, { name: "Ben", big: [] }]
141
+
142
+ oqx`employee: $value from ${people} where name == "Bob"`; // [{ employee: <Bob> }]
143
+ ```
144
+
145
+ Inside a nested block `$value` is the inner item; the enclosing row is `^$value`
146
+ (§3). At the root scope (before any row) it is absent. Plain objects are **not**
147
+ iterable through `$value` — a record stays a record; converting one to a
148
+ collection of entries is a separate, explicit step (`entries()`, planned).
106
149
 
107
150
  ### 3. Predicates (where)
108
151
 
@@ -223,12 +266,13 @@ oqx`name from ${people} where title.lower() == "director"`; // → Alice
223
266
 
224
267
  ### 5. Consumers
225
268
 
226
- A consumer shapes a result set. There are five:
269
+ A consumer shapes a result set. There are six:
227
270
 
228
271
  | Consumer | Returns |
229
272
  | --------- | ------------------------------ |
230
273
  | `collect` | an array (the default) |
231
- | `exists` | a boolean |
274
+ | `exists` | a boolean — one or more rows |
275
+ | `none` | a boolean — zero rows (the complement of `exists`) |
232
276
  | `count` | a number |
233
277
  | `first` | one record, or `null` |
234
278
  | `single` | one record, or `null`; throws if more than one matches |
@@ -258,8 +302,14 @@ cardinality:
258
302
  ```js
259
303
  oqx`name from ${people} where jobs exists { !end }`; // has a current job → Bob, Carol
260
304
  oqx`name from ${people} where jobs count {} >= 2`; // ≥2 jobs → Bob, Alice
305
+ oqx`name from ${people} where jobs none { where end }`; // no past job → Carol
261
306
  ```
262
307
 
308
+ `none { … }` is exactly `!… exists { … }`, kept as its own word because the
309
+ cardinality is the point. It is also how you say "every": *all* members active is
310
+ `members none { where !active }` — there is deliberately no `all { … }`, whose
311
+ block would have to mean something different from every other consumer's.
312
+
263
313
  In a projection, `collect` yields a nested array; `first` / `single` yield one
264
314
  nested record:
265
315
 
@@ -340,6 +390,26 @@ oqx`name from ${people} where city == "NYC" order by age desc`;
340
390
  // [{ name: "Bob" }, { name: "Carol" }]
341
391
  ```
342
392
 
393
+ ### 8b. Bounding: `limit` / `offset`
394
+
395
+ `limit N` and `offset N` bound the row set **after** `where`, `order by`, and
396
+ `distinct`, and **before** the consumer reduces it — so they mean the same thing
397
+ under every consumer: `count { … limit 5 }` is at most 5, `first { … offset 1 }`
398
+ is the second row, `exists { offset 2 }` asks for a third. They work at the top
399
+ level and inside any block, and `N` may be a literal, a `${…}` binding, or an
400
+ outer reference — `limit ^n` reads the enclosing row's `n`, as `^` does everywhere
401
+ inside `{ … }`; it must be a non-negative integer.
402
+
403
+ ```js
404
+ oqx`name values from ${people} order by age desc limit 2`; // ["Alice", "Bob"]
405
+ oqx`name values from ${people} order by age desc offset 1 limit 1`; // ["Bob"]
406
+ oqx`name, latest: jobs collect { employer values order by start desc limit 1 } from ${people}`;
407
+ oqx`name from ${people} where jobs exists { offset 1 }`; // has a second job
408
+ ```
409
+
410
+ A storage adapter that pushes the whole query may translate them to SQL
411
+ `LIMIT`/`OFFSET`; the shipped `SqliteTable` leaves them to the residual.
412
+
343
413
  ### 9. Recursion: `follow`
344
414
 
345
415
  `follow <relation>` turns a query into a bounded recursive traversal: the `where`
@@ -392,15 +462,19 @@ your relation returns fresh objects rather than shared references.
392
462
  name, alias: expr, nested: rel collect { … } projection (select optional)
393
463
  from ${source} source collection
394
464
  where a == b && rel exists { … } || !c predicate tree + nested ops
465
+ where rel none { … } zero rows (≡ !rel exists { … }; "all" = none over the complement)
395
466
  where x in lo..hi / lo...hi / ..hi / lo.. range membership (incl. / excl. / open-ended)
396
467
  where x in range(field) coerce a string field to a range, then test coverage
397
468
  name the CURRENT row's field only (never climbs)
398
- ^name / ^^name read an enclosing row's field (exactly N scopes out)
469
+ $value the current item itself (a scalar row, or the whole object)
470
+ <expr> values scalar projection: the value, not a { name: value } record
471
+ ^name / ^^name read an enclosing row's field (exactly N scopes out); ^$value = the enclosing row
399
472
  ^name: expr / ^^name: expr lift/export a value N scopes out (flatten-append)
400
473
  ^rel collect { … } / ^^root exists { … } nested consumer over an enclosing row's relation / a named root
401
474
  order by expr desc, expr2 ordering
475
+ limit n / offset n bound the row set (after where/order/distinct, before the consumer)
402
476
  follow rel { where … frontier … depth n by … } recursion ($depth/$stop/$leaf/$frontier)
403
- ${source} <collect|exists|count|first|single> { … } whole-query consumer
477
+ ${source} <collect|exists|none|count|first|single> { … } whole-query consumer
404
478
  ```
405
479
 
406
480
  ## Data context: string queries and named roots
@@ -482,7 +556,7 @@ new PlannedEngine(planner).run(parse('name from emp where dept == "eng" && level
482
556
 
483
557
  This is the seam an omgbase adapter uses: its existing OQX→SQL compiler (docs/
484
558
  blocks/nodes, the relations table, `$` intrinsics, `WITH RECURSIVE` for `follow`)
485
- becomes a `QueryPlanner`, while oqx-js contributes the parser, IR, semantics
559
+ becomes a `QueryPlanner`, while oqx contributes the parser, IR, semantics
486
560
  contract, and residual executor.
487
561
 
488
562
  ## Requirements
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/adapters/sqlite.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,KAAK,EAAQ,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAGxD,MAAM,WAAW,kBAAkB;IACjC,qEAAqE;IACrE,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,6EAA6E;IAC7E,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,+EAA+E;IAC/E,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;CACjD;AAOD,qBAAa,WAAY,YAAW,YAAY;IAC9C,OAAO,CAAC,EAAE,CAAe;IACzB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,IAAI,CAAqB;gBAErB,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB;IAOrE,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI,GAAG,IAAI;IAsB3D,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,SAAS;IAkBjB,OAAO,CAAC,MAAM;CAWf"}
1
+ {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/adapters/sqlite.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,KAAK,EAAQ,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAGxD,MAAM,WAAW,kBAAkB;IACjC,qEAAqE;IACrE,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,6EAA6E;IAC7E,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,+EAA+E;IAC/E,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;CACjD;AAOD,qBAAa,WAAY,YAAW,YAAY;IAC9C,OAAO,CAAC,EAAE,CAAe;IACzB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,IAAI,CAAqB;gBAErB,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB;IAOrE,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI,GAAG,IAAI;IAwB3D,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,SAAS;IAkBjB,OAAO,CAAC,MAAM;CAWf"}
@@ -36,10 +36,12 @@ export class SqliteTable {
36
36
  const { pushed, residual } = partitionPushable(query.where, (e) => this.translatable(e));
37
37
  const sqlParams = [];
38
38
  const whereSql = pushed.map((e) => this.translate(e, params, sqlParams)).join(" AND ");
39
- // A LIMIT is only safe when nothing is left to filter in-memory and the
40
- // result is unordered (first/single are "some row" without an order by).
39
+ // A LIMIT is only safe when nothing is left to filter in-memory, the result
40
+ // is unordered (first/single are "some row" without an order by), and the
41
+ // query carries no limit/offset of its own (the residual applies those, so
42
+ // a SQL LIMIT underneath would starve them).
41
43
  let tail = "";
42
- if (!residual && !query.orderBy) {
44
+ if (!residual && !query.orderBy && !query.limit && !query.offset) {
43
45
  if (query.consumer === "first")
44
46
  tail = " LIMIT 1";
45
47
  else if (query.consumer === "single")
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../src/adapters/sqlite.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../src/adapters/sqlite.ts"],"names":[],"mappings":"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"}
package/dist/ast.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- /** Query consumers — how a (sub)query's row set is shaped. */
2
- export type Consumer = "collect" | "exists" | "count" | "first" | "single";
1
+ /** Query consumers — how a (sub)query's row set is shaped. `none` is the
2
+ * zero-cardinality complement of `exists` (true iff the block yields no rows). */
3
+ export type Consumer = "collect" | "exists" | "none" | "count" | "first" | "single";
3
4
  /** Comparison operators usable in a `count { … } <op> <int>` test. */
4
5
  export type RelOp = "==" | "!=" | "<" | "<=" | ">" | ">=";
5
6
  /** Scalar value/predicate expression, evaluated against a row scope + bindings. */
@@ -102,6 +103,18 @@ export interface Subquery {
102
103
  select: SelectItem[];
103
104
  orderBy: OrderSpec[] | null;
104
105
  follow: Follow | null;
106
+ /** `values` — scalar projection mode: the (single) projected expression is the
107
+ * row's result itself rather than being wrapped in a `{ name: value }` record,
108
+ * so `name values` yields `["Bob", …]` and `$value values` yields the rows. */
109
+ values?: boolean;
110
+ /** `limit N` / `offset N` — bound the row set AFTER where/order/distinct and
111
+ * BEFORE the consumer reduces it, so `count { … limit 5 }` is at most 5 and
112
+ * `first { … offset 1 }` is the second row. Each is a value expression
113
+ * (a literal, a `${…}` binding, or an outer reference) read as part of the
114
+ * block — `^n` is the enclosing row's field, as everywhere inside `{ … }` —
115
+ * and must yield a non-negative integer. */
116
+ limit?: Expr;
117
+ offset?: Expr;
105
118
  }
106
119
  /** The where-clause boolean tree: OQX owns &&/||/!/grouping so consumer ops
107
120
  * (invisible to the scalar evaluator) compose with scalar predicates. */
@@ -129,5 +142,10 @@ export interface Query {
129
142
  follow: Follow | null;
130
143
  /** `distinct` — dedup the result rows by their projected value (see OpNode). */
131
144
  distinct?: boolean;
145
+ /** `values` — scalar projection mode (see Subquery). */
146
+ values?: boolean;
147
+ /** `limit N` / `offset N` (see Subquery); evaluated at the root scope. */
148
+ limit?: Expr;
149
+ offset?: Expr;
132
150
  }
133
151
  //# 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":"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"}
1
+ {"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../src/ast.ts"],"names":[],"mappings":"AAMA;kFACkF;AAClF,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEpF,sEAAsE;AACtE,MAAM,MAAM,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC;AAE1D,mFAAmF;AACnF,MAAM,MAAM,IAAI,GACZ;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAA;CAAE,GACxD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC5C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,IAAI,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,IAAI,EAAE,CAAA;CAAE,GAC/D;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,GAAG,GAAG,GAAG,CAAC;IAAC,IAAI,EAAE,IAAI,CAAA;CAAE,GAC5C;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,IAAI,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,EAAE,EAAE,IAAI,GAAG,IAAI,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,IAAI,CAAA;CAAE,GAC7D;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,IAAI,CAAA;CAAE,GAIvC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,IAAI,GAAG,IAAI,CAAC;IAAC,EAAE,EAAE,IAAI,GAAG,IAAI,CAAC;IAAC,YAAY,EAAE,OAAO,CAAA;CAAE,CAAC;AAE/E,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,IAAI,CAAC;IACX,IAAI,EAAE,OAAO,CAAC;CACf;AAED;iEACiE;AACjE,MAAM,MAAM,UAAU,GAGlB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACzD;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAElD;0EAC0E;AAC1E,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,EAAE,IAAI,CAAC;IACf,EAAE,EAAE,QAAQ,CAAC;IACb,GAAG,EAAE,QAAQ,CAAC;IACd,QAAQ,CAAC,EAAE;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC;;;sFAGkF;IAClF,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,qCAAqC;AACrC,MAAM,WAAW,MAAM;IACrB,QAAQ,EAAE,IAAI,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,EAAE,EAAE,IAAI,GAAG,IAAI,CAAC;CACjB;AAED,gCAAgC;AAChC,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,IAAI,EAAE,CAAC;IACb,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,OAAO,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB;;mFAE+E;IAC/E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;gDAK4C;IAC5C,KAAK,CAAC,EAAE,IAAI,CAAC;IACb,MAAM,CAAC,EAAE,IAAI,CAAC;CACf;AAED;yEACyE;AACzE,MAAM,MAAM,KAAK,GACb;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,KAAK,EAAE,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,KAAK,EAAE,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,KAAK,CAAA;CAAE,GAC5B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,IAAI,CAAA;CAAE,GAC9B,MAAM,CAAC;AAEX,6BAA6B;AAC7B,MAAM,WAAW,KAAK;IACpB,MAAM,EAAE,IAAI,CAAC;IACb,IAAI,EAAE,IAAI,EAAE,CAAC;IACb,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,OAAO,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IAC5B,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,gFAAgF;IAChF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wDAAwD;IACxD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,IAAI,CAAC;IACb,MAAM,CAAC,EAAE,IAAI,CAAC;CACf"}
package/dist/engine.d.ts CHANGED
@@ -7,6 +7,9 @@ export type OqxResult = {
7
7
  } | {
8
8
  consumer: "exists";
9
9
  exists: boolean;
10
+ } | {
11
+ consumer: "none";
12
+ none: boolean;
10
13
  } | {
11
14
  consumer: "count";
12
15
  count: number;
@@ -25,6 +28,7 @@ export declare class InMemoryEngine implements Engine {
25
28
  private ctx;
26
29
  constructor(context?: DataContext);
27
30
  run(query: Query, bindings?: readonly unknown[]): OqxResult;
31
+ private boundOf;
28
32
  private matches;
29
33
  private rowsOf;
30
34
  private child;
@@ -33,8 +37,10 @@ export declare class InMemoryEngine implements Engine {
33
37
  private followWalk;
34
38
  private shape;
35
39
  private projectRow;
40
+ private itemValue;
36
41
  private evalWhere;
37
42
  private evalWhereOp;
43
+ private opRows;
38
44
  private anyMatch;
39
45
  private iterMatchRows;
40
46
  private matchRows;
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EACV,KAAK,EACN,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAOhD,yEAAyE;AACzE,MAAM,MAAM,SAAS,GACjB;IAAE,QAAQ,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,EAAE,CAAA;CAAE,GACxC;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GACvC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAA;CAAE,GAC1C;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAA;CAAE,CAAC;AAEhD,6EAA6E;AAC7E,MAAM,WAAW,MAAM;IACrB,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,CAAC;CAC5D;AAkBD,qBAAa,cAAe,YAAW,MAAM;IAC3C,OAAO,CAAC,GAAG,CAAc;gBAEb,OAAO,GAAE,WAAkC;IAIvD,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,GAAE,SAAS,OAAO,EAAO,GAAG,SAAS;IAsC/D,OAAO,CAAC,OAAO;IAKf,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,KAAK;IAMb,OAAO,CAAC,SAAS;IAcjB,OAAO,CAAC,iBAAiB;IA2BzB,OAAO,CAAC,UAAU;IA6DlB,OAAO,CAAC,KAAK;IAYb,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,SAAS;IAcjB,OAAO,CAAC,WAAW;IA6BnB,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAE,aAAa;IAStB,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,gBAAgB;IAyBxB,OAAO,CAAC,UAAU;IAwBlB,OAAO,CAAC,QAAQ;IAqDhB,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,QAAQ;CAYjB;AA+ED,8EAA8E;AAC9E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,GAAG,SAAS,CAG9F"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EACV,KAAK,EACN,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAOhD,yEAAyE;AACzE,MAAM,MAAM,SAAS,GACjB;IAAE,QAAQ,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,EAAE,CAAA;CAAE,GACxC;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GACvC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GACnC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAA;CAAE,GAC1C;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAA;CAAE,CAAC;AAEhD,6EAA6E;AAC7E,MAAM,WAAW,MAAM;IACrB,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,CAAC;CAC5D;AA0BD,qBAAa,cAAe,YAAW,MAAM;IAC3C,OAAO,CAAC,GAAG,CAAc;gBAEb,OAAO,GAAE,WAAkC;IAIvD,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,GAAE,SAAS,OAAO,EAAO,GAAG,SAAS;IAmD/D,OAAO,CAAC,OAAO;IAef,OAAO,CAAC,OAAO;IAKf,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,KAAK;IAMb,OAAO,CAAC,SAAS;IAejB,OAAO,CAAC,iBAAiB;IA2BzB,OAAO,CAAC,UAAU;IA6DlB,OAAO,CAAC,KAAK;IAeb,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,SAAS;IAcjB,OAAO,CAAC,WAAW;IAiCnB,OAAO,CAAC,MAAM;IAQd,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAE,aAAa;IAStB,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,gBAAgB;IA2BxB,OAAO,CAAC,UAAU;IAwBlB,OAAO,CAAC,QAAQ;IAuDhB,OAAO,CAAC,SAAS;IAQjB,OAAO,CAAC,QAAQ;CAYjB;AAyFD,8EAA8E;AAC9E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,GAAG,SAAS,CAG9F"}
package/dist/engine.js CHANGED
@@ -16,6 +16,7 @@ import { OqxError } from "./errors.js";
16
16
  import { equals, relate, arith, membership, truthy, toNumber, compareForSort, makeRange, } from "./semantics.js";
17
17
  const RECUR = new Set(["$depth", "$stop", "$leaf", "$frontier", "$ordinal"]);
18
18
  const HARD_DEPTH_CAP = 8;
19
+ const UNBOUNDED = { offset: 0, limit: null };
19
20
  export class InMemoryEngine {
20
21
  ctx;
21
22
  constructor(context = new DefaultContext()) {
@@ -27,26 +28,36 @@ export class InMemoryEngine {
27
28
  for (const proj of query.from) {
28
29
  rows = rows.flatMap((r) => this.rowsOf(this.evalExpr(proj, this.child(r, root))));
29
30
  }
31
+ const bound = this.boundOf(query, root);
30
32
  if (query.follow)
31
- return this.runFollow(query, rows, root);
33
+ return this.runFollow(query, rows, root, bound);
32
34
  // Consumer-directed short-circuits (skipped under `distinct`, which must
33
- // materialize + dedup by projection before reducing).
34
- if (!query.distinct && query.consumer === "exists") {
35
- for (const r of rows)
36
- if (this.matches(query.where, r, root))
37
- return { consumer: "exists", exists: true };
38
- return { consumer: "exists", exists: false };
39
- }
40
- if (!query.distinct && query.consumer === "count") {
41
- let count = 0;
42
- for (const r of rows)
43
- if (this.matches(query.where, r, root))
44
- count++;
45
- return { consumer: "count", count };
35
+ // materialize + dedup by projection before reducing). Counting never
36
+ // materializes rows; exists/none stop as soon as the bound is known to be
37
+ // non-empty (the (offset+1)th match — or the first, when unbounded).
38
+ if (!query.distinct && (query.consumer === "exists" || query.consumer === "none" || query.consumer === "count")) {
39
+ const need = query.consumer === "count" ? Infinity : bound.offset + 1;
40
+ let n = 0;
41
+ for (const r of rows) {
42
+ if (this.matches(query.where, r, root)) {
43
+ n++;
44
+ if (n >= need)
45
+ break;
46
+ }
47
+ }
48
+ const m = boundedCount(n, bound);
49
+ if (query.consumer === "count")
50
+ return { consumer: "count", count: m };
51
+ if (query.consumer === "exists")
52
+ return { consumer: "exists", exists: m > 0 };
53
+ return { consumer: "none", none: m === 0 };
46
54
  }
47
- const cap = !query.orderBy && !query.distinct && query.consumer === "first" ? 1
48
- : !query.orderBy && !query.distinct && query.consumer === "single" ? 2
49
- : Infinity;
55
+ // first/single over an unordered, non-distinct set need only the rows up to
56
+ // the bound: offset + 1 (first) / offset + 2 (single, to detect a second).
57
+ const want = query.consumer === "first" ? 1 : query.consumer === "single" ? 2 : Infinity;
58
+ const cap = !query.orderBy && !query.distinct && want !== Infinity
59
+ ? bound.offset + Math.min(want, bound.limit ?? want)
60
+ : Infinity;
50
61
  let kept = [];
51
62
  for (const r of rows) {
52
63
  const s = { row: r, parent: root, bindings, lifts: {} };
@@ -58,8 +69,29 @@ export class InMemoryEngine {
58
69
  }
59
70
  this.sortScopes(kept, query.orderBy);
60
71
  if (query.distinct)
61
- kept = this.dedupByProjection(kept, query.select);
62
- return this.shape(query.consumer, kept, query.select);
72
+ kept = this.dedupByProjection(kept, query);
73
+ kept = sliceBound(kept, bound);
74
+ return this.shape(query.consumer, kept, query);
75
+ }
76
+ // Evaluate a block's `limit`/`offset`. The bound is part of the block, so it
77
+ // is read in a row-less scope INSIDE it: a bare name is absent (there is no
78
+ // current item yet), `^name` is the enclosing row — exactly as in the block's
79
+ // body — and literals/bindings are themselves. For a top-level query `scope`
80
+ // is the root. Each must be a non-negative integer.
81
+ boundOf(b, enclosing) {
82
+ if (!b.limit && !b.offset)
83
+ return UNBOUNDED;
84
+ const scope = enclosing.parent === null ? enclosing : { row: undefined, parent: enclosing, bindings: enclosing.bindings };
85
+ const read = (e, word) => {
86
+ if (!e)
87
+ return null;
88
+ const v = this.evalExpr(e, scope);
89
+ if (typeof v !== "number" || !Number.isInteger(v) || v < 0) {
90
+ throw new OqxError(`${word} must be a non-negative integer (got ${JSON.stringify(v ?? null)})`, "eval");
91
+ }
92
+ return v;
93
+ };
94
+ return { offset: read(b.offset, "offset") ?? 0, limit: read(b.limit, "limit") };
63
95
  }
64
96
  // A where match that needs no lift capture (exists/count fast paths).
65
97
  matches(where, row, parent) {
@@ -74,7 +106,7 @@ export class InMemoryEngine {
74
106
  return { row, parent, bindings: parent.bindings };
75
107
  }
76
108
  // ---- follow ---------------------------------------------------------------
77
- runFollow(query, rows, root) {
109
+ runFollow(query, rows, root, bound) {
78
110
  const { seed, post } = query.where ? partitionRecur(query.where) : { seed: null, post: null };
79
111
  const seeds = seed ? rows.filter((r) => this.matches(seed, r, root)) : rows;
80
112
  const occ = this.followWalk(seeds, query.follow, root);
@@ -83,19 +115,20 @@ export class InMemoryEngine {
83
115
  scopes = scopes.filter((s) => this.evalWhere(post, s));
84
116
  this.sortScopes(scopes, query.orderBy);
85
117
  if (query.distinct)
86
- scopes = this.dedupByProjection(scopes, query.select);
87
- return this.shape(query.consumer, scopes, query.select);
118
+ scopes = this.dedupByProjection(scopes, query);
119
+ scopes = sliceBound(scopes, bound);
120
+ return this.shape(query.consumer, scopes, query);
88
121
  }
89
122
  // Dedup scopes by their PROJECTED value (`distinct`): keep the first scope per
90
123
  // distinct projection, preserving order. An empty projection dedups by row
91
124
  // identity (so `count distinct { }` counts distinct rows).
92
- dedupByProjection(scopes, select) {
125
+ dedupByProjection(scopes, proj) {
93
126
  const seen = new Set();
94
127
  const out = [];
95
128
  for (const s of scopes) {
96
- const key = select.length === 0
129
+ const key = proj.select.length === 0
97
130
  ? `i:${String(this.ctx.identity(s.row))}`
98
- : `p:${stableStringify(this.projectRow(select, s))}`;
131
+ : `p:${stableStringify(this.projectRow(proj, s))}`;
99
132
  if (seen.has(key))
100
133
  continue;
101
134
  seen.add(key);
@@ -176,27 +209,35 @@ export class InMemoryEngine {
176
209
  }));
177
210
  }
178
211
  // ---- consumer shaping -----------------------------------------------------
179
- shape(consumer, scopes, select) {
212
+ shape(consumer, scopes, proj) {
180
213
  switch (consumer) {
181
214
  case "exists": return { consumer, exists: scopes.length > 0 };
215
+ case "none": return { consumer, none: scopes.length === 0 };
182
216
  case "count": return { consumer, count: scopes.length };
183
- case "collect": return { consumer, rows: scopes.map((s) => this.projectRow(select, s)) };
184
- case "first": return { consumer, row: scopes.length > 0 ? this.projectRow(select, scopes[0]) : null };
217
+ case "collect": return { consumer, rows: scopes.map((s) => this.projectRow(proj, s)) };
218
+ case "first": return { consumer, row: scopes.length > 0 ? this.projectRow(proj, scopes[0]) : null };
185
219
  case "single":
186
220
  if (scopes.length > 1)
187
221
  throw new OqxError(`single { … } matched ${scopes.length} rows; use first { … } for zero-or-one`, "eval");
188
- return { consumer, row: scopes.length > 0 ? this.projectRow(select, scopes[0]) : null };
222
+ return { consumer, row: scopes.length > 0 ? this.projectRow(proj, scopes[0]) : null };
189
223
  }
190
224
  }
191
- projectRow(select, scope) {
225
+ // The per-row result: the raw row (empty projection), the single item's value
226
+ // itself (`values` mode), or a `{ name: value }` record.
227
+ projectRow(proj, scope) {
228
+ const { select } = proj;
192
229
  if (select.length === 0)
193
230
  return scope.row;
231
+ if (proj.values)
232
+ return this.itemValue(select[0], scope);
194
233
  const out = {};
195
- for (const item of select) {
196
- out[item.name] = item.kind === "field" ? this.evalExpr(item.expr, scope) : this.evalCollectValue(item.op, scope);
197
- }
234
+ for (const item of select)
235
+ out[item.name] = this.itemValue(item, scope);
198
236
  return out;
199
237
  }
238
+ itemValue(item, scope) {
239
+ return item.kind === "field" ? this.evalExpr(item.expr, scope) : this.evalCollectValue(item.op, scope);
240
+ }
200
241
  // ---- where evaluation -----------------------------------------------------
201
242
  evalWhere(w, scope) {
202
243
  switch (w.kind) {
@@ -216,10 +257,15 @@ export class InMemoryEngine {
216
257
  evalWhereOp(op, scope) {
217
258
  if (op.sub.follow)
218
259
  throw new OqxError("`follow` is only valid on a select-position collect { … }, not a where op", "eval");
219
- if (op.op === "exists")
220
- return this.anyMatch(op, scope);
260
+ const bound = this.boundOf(op.sub, scope);
261
+ if (op.op === "exists" || op.op === "none") {
262
+ // Unbounded: stop at the first match (dedup cannot change emptiness).
263
+ // Bounded: the offset/limit decide emptiness, so materialize the set.
264
+ const any = bound === UNBOUNDED ? this.anyMatch(op, scope) : this.opRows(op, scope, bound).length > 0;
265
+ return op.op === "exists" ? any : !any;
266
+ }
221
267
  if (op.op === "collect") {
222
- const matched = this.matchRows(op, scope);
268
+ const matched = this.opRows(op, scope, bound);
223
269
  for (const item of op.sub.select) {
224
270
  if (item.kind !== "field")
225
271
  continue;
@@ -237,13 +283,18 @@ export class InMemoryEngine {
237
283
  return matched.length > 0;
238
284
  }
239
285
  if (op.op === "count") {
240
- let rows = this.matchRows(op, scope);
241
- if (op.distinct)
242
- rows = this.dedupByProjection(rows, op.sub.select);
243
- const n = rows.length;
286
+ const n = this.opRows(op, scope, bound).length;
244
287
  return op.countCmp ? compareCount(n, op.countCmp) : n > 0;
245
288
  }
246
- return this.matchRows(op, scope).length > 0;
289
+ return this.opRows(op, scope, bound).length > 0;
290
+ }
291
+ // The rows a consumer op reduces: matched → ordered → distinct → bounded.
292
+ opRows(op, scope, bound) {
293
+ let scopes = this.matchRows(op, scope);
294
+ this.sortScopes(scopes, op.sub.orderBy);
295
+ if (op.distinct)
296
+ scopes = this.dedupByProjection(scopes, op.sub);
297
+ return sliceBound(scopes, bound);
247
298
  }
248
299
  // Short-circuiting existence check over a consumer receiver.
249
300
  anyMatch(op, scope) {
@@ -269,6 +320,7 @@ export class InMemoryEngine {
269
320
  // A select-position collect/first/single, optionally recursive via `follow`.
270
321
  evalCollectValue(op, scope) {
271
322
  const sub = op.sub;
323
+ const bound = this.boundOf(sub, scope);
272
324
  let scopes;
273
325
  if (sub.follow) {
274
326
  let rows = this.rowsOf(this.evalExpr(op.receiver, scope));
@@ -276,20 +328,21 @@ export class InMemoryEngine {
276
328
  rows = rows.flatMap((r) => this.rowsOf(this.evalExpr(proj, this.child(r, scope))));
277
329
  const seeds = sub.where ? rows.filter((r) => this.evalWhere(sub.where, { row: r, parent: scope, bindings: scope.bindings, lifts: {} })) : rows;
278
330
  scopes = this.followWalk(seeds, sub.follow, scope).map((o) => ({ row: o.row, parent: scope, bindings: scope.bindings, meta: o.meta }));
331
+ this.sortScopes(scopes, sub.orderBy);
332
+ if (op.distinct)
333
+ scopes = this.dedupByProjection(scopes, sub);
334
+ scopes = sliceBound(scopes, bound);
279
335
  }
280
336
  else {
281
- scopes = this.matchRows(op, scope);
337
+ scopes = this.opRows(op, scope, bound);
282
338
  }
283
- this.sortScopes(scopes, sub.orderBy);
284
- if (op.distinct)
285
- scopes = this.dedupByProjection(scopes, sub.select);
286
339
  switch (op.op) {
287
- case "collect": return scopes.map((s) => this.projectRow(sub.select, s));
288
- case "first": return scopes.length > 0 ? this.projectRow(sub.select, scopes[0]) : null;
340
+ case "collect": return scopes.map((s) => this.projectRow(sub, s));
341
+ case "first": return scopes.length > 0 ? this.projectRow(sub, scopes[0]) : null;
289
342
  case "single":
290
343
  if (scopes.length > 1)
291
344
  throw new OqxError(`single { … } for '${describeReceiver(op.receiver)}' matched ${scopes.length} rows`, "eval");
292
- return scopes.length > 0 ? this.projectRow(sub.select, scopes[0]) : null;
345
+ return scopes.length > 0 ? this.projectRow(sub, scopes[0]) : null;
293
346
  default: throw new OqxError(`${op.op} { … } is not valid in select position`, "eval");
294
347
  }
295
348
  }
@@ -362,9 +415,11 @@ export class InMemoryEngine {
362
415
  }
363
416
  }
364
417
  // Resolve a name against ONE scope — never its ancestors. A scope provides,
365
- // in order: the recursion intrinsics (`$depth`, …) when it is a follow
366
- // occurrence; values lifted into it by `^name:` items; then either the row's
367
- // own property or, for the root scope (no row), the context's named roots.
418
+ // in order: `$value` (the scope's row itself the current item, whatever its
419
+ // type, so scalar collections are queryable; absent at the root, which has no
420
+ // row); the recursion intrinsics (`$depth`, ) when it is a follow occurrence;
421
+ // values lifted into it by `^name:` items; then either the row's own property
422
+ // or, for the root scope (no row), the context's named roots.
368
423
  //
369
424
  // A name the scope lacks is simply absent (undefined). It does NOT fall
370
425
  // through to an enclosing scope, so a query's meaning never depends on which
@@ -373,6 +428,8 @@ export class InMemoryEngine {
373
428
  // always spelled explicitly as `^name`. Present-but-falsy values (null, false,
374
429
  // 0, "") need no special case — there is no "absent, so look outward" rule.
375
430
  resolveIn(name, scope) {
431
+ if (name === "$value")
432
+ return scope.parent === null ? undefined : scope.row;
376
433
  if (RECUR.has(name))
377
434
  return scope.meta ? scope.meta[name] : undefined;
378
435
  if (scope.lifts && name in scope.lifts)
@@ -417,6 +474,16 @@ function stableStringify(v) {
417
474
  function compareCount(n, cmp) {
418
475
  return relate(cmp.op, n, cmp.value);
419
476
  }
477
+ // Apply a bound to an ordered row set / to a match count.
478
+ function sliceBound(rows, b) {
479
+ if (b === UNBOUNDED)
480
+ return rows;
481
+ return rows.slice(b.offset, b.limit == null ? undefined : b.offset + b.limit);
482
+ }
483
+ function boundedCount(n, b) {
484
+ const rest = Math.max(0, n - b.offset);
485
+ return b.limit == null ? rest : Math.min(rest, b.limit);
486
+ }
420
487
  // Order `&&` conjuncts so cheap scalar leaves run before consumer-op leaves.
421
488
  function orderByCost(parts) {
422
489
  return [...parts].sort((a, b) => whereCost(a) - whereCost(b));