@omgbase/oqx 0.13.0 → 0.14.1
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 +94 -0
- package/README.md +184 -17
- package/dist/adapters/indexed.d.ts +13 -1
- package/dist/adapters/indexed.d.ts.map +1 -1
- package/dist/adapters/indexed.js +55 -24
- package/dist/adapters/indexed.js.map +1 -1
- package/dist/ast.d.ts +5 -1
- package/dist/ast.d.ts.map +1 -1
- package/dist/context.d.ts +24 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js.map +1 -1
- package/dist/engine.d.ts +55 -2
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +295 -37
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/optimize/analysis.d.ts +45 -0
- package/dist/optimize/analysis.d.ts.map +1 -0
- package/dist/optimize/analysis.js +194 -0
- package/dist/optimize/analysis.js.map +1 -0
- package/dist/optimize/driver.d.ts +8 -0
- package/dist/optimize/driver.d.ts.map +1 -0
- package/dist/optimize/driver.js +35 -0
- package/dist/optimize/driver.js.map +1 -0
- package/dist/optimize/hash-index.d.ts +34 -0
- package/dist/optimize/hash-index.d.ts.map +1 -0
- package/dist/optimize/hash-index.js +79 -0
- package/dist/optimize/hash-index.js.map +1 -0
- package/dist/optimize/index.d.ts +8 -0
- package/dist/optimize/index.d.ts.map +1 -0
- package/dist/optimize/index.js +11 -0
- package/dist/optimize/index.js.map +1 -0
- package/dist/optimize/ir.d.ts +84 -0
- package/dist/optimize/ir.d.ts.map +1 -0
- package/dist/optimize/ir.js +60 -0
- package/dist/optimize/ir.js.map +1 -0
- package/dist/optimize/rules.d.ts +8 -0
- package/dist/optimize/rules.d.ts.map +1 -0
- package/dist/optimize/rules.js +136 -0
- package/dist/optimize/rules.js.map +1 -0
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +59 -5
- package/dist/parser.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/indexed.ts +60 -24
- package/src/ast.ts +6 -1
- package/src/context.ts +24 -0
- package/src/engine.ts +329 -35
- package/src/index.ts +7 -2
- package/src/optimize/analysis.ts +183 -0
- package/src/optimize/driver.ts +37 -0
- package/src/optimize/hash-index.ts +91 -0
- package/src/optimize/index.ts +16 -0
- package/src/optimize/ir.ts +128 -0
- package/src/optimize/rules.ts +136 -0
- package/src/parser.ts +54 -6
package/src/parser.ts
CHANGED
|
@@ -12,9 +12,15 @@
|
|
|
12
12
|
// Clause order is FIXED (ADR-020). Within one clause body — the top level or a
|
|
13
13
|
// consumer block — each clause appears at most once, in exactly this order:
|
|
14
14
|
//
|
|
15
|
-
// select <projection> from <source> where <predicate> follow <
|
|
15
|
+
// select <projection> from <source> where <predicate> follow <dest>, … {…}
|
|
16
16
|
// order by … limit N offset N
|
|
17
17
|
//
|
|
18
|
+
// A `follow` destination is a relation of the current row (a receiver, never
|
|
19
|
+
// an outer reference) or a destination block — a receiver (which MAY be an
|
|
20
|
+
// outer reference, `^people`) immediately followed by `collect`/`first`/
|
|
21
|
+
// `single` [`distinct`] `{ body }`, exactly the select-position directive
|
|
22
|
+
// `tryOp(false)` recognizes, re-evaluated per frontier row (language 0.14).
|
|
23
|
+
//
|
|
18
24
|
// Every clause is optional except that a top-level body needs `from` (the
|
|
19
25
|
// receiver-plus-consumer form `<receiver> <consumer> { block }` supplies the
|
|
20
26
|
// source itself, so its block's `from` is an optional re-projection). Only
|
|
@@ -39,7 +45,7 @@ import type { Token, TokType } from "./lexer.ts";
|
|
|
39
45
|
import { lexTemplate, lexString } from "./lexer.ts";
|
|
40
46
|
import { OqxError } from "./errors.ts";
|
|
41
47
|
import type {
|
|
42
|
-
Query, Where, Expr, OpNode, Subquery, SelectItem, OrderSpec, Follow, Consumer, RelOp,
|
|
48
|
+
Query, Where, Expr, OpNode, Subquery, SelectItem, OrderSpec, Follow, FollowDestination, Consumer, RelOp,
|
|
43
49
|
} from "./ast.ts";
|
|
44
50
|
|
|
45
51
|
const CONSUMERS = new Set<string>(["collect", "exists", "none", "count", "first", "single"]);
|
|
@@ -393,11 +399,15 @@ class Parser {
|
|
|
393
399
|
this.fail("expected a relation after `follow distinct` (`follow distinct <relation>`)");
|
|
394
400
|
}
|
|
395
401
|
}
|
|
396
|
-
|
|
397
|
-
|
|
402
|
+
const destinations: FollowDestination[] = [this.parseFollowDestination()];
|
|
403
|
+
while (this.at("comma")) {
|
|
404
|
+
this.next();
|
|
405
|
+
if (!this.at("ident") && !this.at("binding") && !this.at("caret")) {
|
|
406
|
+
this.fail("expected a relation after ',' (`follow <relation>, <relation>`)");
|
|
407
|
+
}
|
|
408
|
+
destinations.push(this.parseFollowDestination());
|
|
398
409
|
}
|
|
399
|
-
const
|
|
400
|
-
const follow: Follow = { receiver, distinct, where: null, frontier: null, depth: null, by: null };
|
|
410
|
+
const follow: Follow = { destinations, distinct, where: null, frontier: null, depth: null, by: null };
|
|
401
411
|
if (!this.at("lbrace")) return follow;
|
|
402
412
|
this.next(); // '{'
|
|
403
413
|
while (!this.at("eof") && !this.at("rbrace")) {
|
|
@@ -429,6 +439,44 @@ class Parser {
|
|
|
429
439
|
return follow;
|
|
430
440
|
}
|
|
431
441
|
|
|
442
|
+
// One `follow` destination. The lookahead mirrors `tryOp`: a receiver (binding,
|
|
443
|
+
// or carets + a dotted name) followed by a consumer word and `{` / `distinct {`
|
|
444
|
+
// is a destination block, parsed by `tryOp(false)` itself — but only the
|
|
445
|
+
// select-position consumers (collect/first/single) yield rows to walk; the
|
|
446
|
+
// where-position tests are rejected by name before their body is read. With
|
|
447
|
+
// no directive the tokens are re-read as a plain relation of the current row,
|
|
448
|
+
// where an outer reference is still an error (a `^name` head only makes sense
|
|
449
|
+
// as the receiver of a block, since the block is what varies per row).
|
|
450
|
+
private parseFollowDestination(): FollowDestination {
|
|
451
|
+
const start = this.pos;
|
|
452
|
+
let isReceiver = false;
|
|
453
|
+
if (this.at("binding")) { this.next(); isReceiver = true; }
|
|
454
|
+
else if (this.at("ident") || this.at("caret")) {
|
|
455
|
+
this.parseCarets();
|
|
456
|
+
if (this.at("ident")) { this.parseNavFrom(this.next()); isReceiver = true; }
|
|
457
|
+
}
|
|
458
|
+
if (isReceiver && this.at("ident") && CONSUMERS.has(this.peek().value)) {
|
|
459
|
+
const op = this.peek().value;
|
|
460
|
+
const after = this.peekAt(1);
|
|
461
|
+
const opThenBrace = after?.type === "lbrace";
|
|
462
|
+
const opDistinctBrace = after?.type === "ident" && after.value === "distinct" && this.peekAt(2)?.type === "lbrace";
|
|
463
|
+
if (opThenBrace || opDistinctBrace) {
|
|
464
|
+
if (op !== "collect" && op !== "first" && op !== "single") {
|
|
465
|
+
this.fail(`a follow destination must use collect/first/single, not \`${op}\` (exists/none/count are where-position tests)`);
|
|
466
|
+
}
|
|
467
|
+
this.pos = start;
|
|
468
|
+
const block = this.tryOp(false);
|
|
469
|
+
if (!block) this.fail("expected a destination block (`<receiver> collect { … }`) after `follow`");
|
|
470
|
+
return block;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
this.pos = start;
|
|
474
|
+
if (this.at("caret")) {
|
|
475
|
+
this.fail("`follow` takes a relation of the current row (`follow <relation>`); an outer reference `^name` is not allowed there — it may only head a destination block (`follow ^name collect { … }`)");
|
|
476
|
+
}
|
|
477
|
+
return this.parseReceiver();
|
|
478
|
+
}
|
|
479
|
+
|
|
432
480
|
// A receiver/source: a `${…}` binding, or a dotted identifier navigation chain
|
|
433
481
|
// whose head may be an outer reference (`^rel`, `^^root.rel`) — since a bare
|
|
434
482
|
// name is the current row's own property, an enclosing row's relation or a
|