@bjornpagen/bumbledb 0.10.0 → 0.12.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/COOKBOOK.md +155 -136
- package/README.md +5 -9
- package/dist/db.d.ts +77 -109
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +121 -339
- package/dist/db.js.map +1 -1
- package/dist/index.d.ts +12 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -11
- package/dist/index.js.map +1 -1
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +8 -1
- package/dist/lower.js.map +1 -1
- package/dist/native.d.ts +77 -51
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js.map +1 -1
- package/dist/query/atom.d.ts +94 -112
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +8 -17
- package/dist/query/atom.js.map +1 -1
- package/dist/query/find.d.ts +18 -35
- package/dist/query/find.d.ts.map +1 -1
- package/dist/query/find.js +13 -32
- package/dist/query/find.js.map +1 -1
- package/dist/query/lower.d.ts +128 -124
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +366 -258
- package/dist/query/lower.js.map +1 -1
- package/dist/query/parse-ir.d.ts +12 -0
- package/dist/query/parse-ir.d.ts.map +1 -0
- package/dist/query/parse-ir.js +71 -0
- package/dist/query/parse-ir.js.map +1 -0
- package/dist/query/run.d.ts +2 -2
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +2 -13
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +4 -16
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +1 -6
- package/dist/query/scope.js.map +1 -1
- package/dist/schema.js +2 -2
- package/dist/schema.js.map +1 -1
- package/dist/statements.d.ts +8 -4
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +7 -9
- package/dist/statements.js.map +1 -1
- package/package.json +2 -2
- package/src/db.ts +182 -443
- package/src/index.ts +11 -24
- package/src/lower.ts +8 -1
- package/src/native.ts +79 -43
- package/src/query/atom.ts +105 -138
- package/src/query/find.ts +39 -80
- package/src/query/lower.ts +626 -434
- package/src/query/parse-ir.ts +82 -0
- package/src/query/run.ts +2 -14
- package/src/query/scope.ts +3 -21
- package/src/schema.ts +2 -2
- package/src/statements.ts +17 -13
- package/dist/order.d.ts +0 -87
- package/dist/order.d.ts.map +0 -1
- package/dist/order.js +0 -153
- package/dist/order.js.map +0 -1
- package/dist/query/predicate.d.ts +0 -91
- package/dist/query/predicate.d.ts.map +0 -1
- package/dist/query/predicate.js +0 -156
- package/dist/query/predicate.js.map +0 -1
- package/src/order.ts +0 -234
- package/src/query/predicate.ts +0 -269
package/dist/query/lower.js
CHANGED
|
@@ -18,11 +18,8 @@
|
|
|
18
18
|
* `Params` inferred to be EXACTLY the params the rules use (params are typed
|
|
19
19
|
* BY USE; a param no rule uses never registers). Variable IDENTITY is the
|
|
20
20
|
* object reference: reusing one value across binding positions IS the join,
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* equality, that alone makes every cross-binding join transitively
|
|
24
|
-
* class-equal. Lowering is a pure function of the query value down to the
|
|
25
|
-
* bridge's `ProgramIr` (`bumbledb/crates/bumbledb/src/ir.rs`): relations by
|
|
21
|
+
* lowering is a pure function of the query value down to the
|
|
22
|
+
* bridge's `QueryIr` (`bumbledb/crates/bumbledb/src/ir.rs`): relations by
|
|
26
23
|
* declaration ordinal, variables by dense per-rule first-occurrence ids
|
|
27
24
|
* (keyed on the object REFERENCE — the discipline is unchanged, only the map
|
|
28
25
|
* key moved from name to reference), params by first-use order. Lowering is
|
|
@@ -30,20 +27,20 @@
|
|
|
30
27
|
* two identically-written queries (fresh mints each) lower identically.
|
|
31
28
|
* Construction validates negation safety and boundness (typed by the var's
|
|
32
29
|
* label — object identity is invisible to the type tier, so these are
|
|
33
|
-
* construction-time walls); everything else (
|
|
30
|
+
* construction-time walls); everything else (types, aggregate
|
|
34
31
|
* rosters, rule caps) is the ENGINE's judge, surfacing at prepare.
|
|
35
32
|
*/
|
|
36
33
|
import * as errors from "@superbuilders/errors";
|
|
37
34
|
import { sealedFieldsOf } from "#closed.ts";
|
|
38
35
|
import { assertDeclarationOrderKey, isIntervalValue, literalShapeError, rosterOf } from "#fields.ts";
|
|
39
36
|
import { allen, and, eq, ge, gt, le, lt, ne, not, or, pointIn } from "#query/atom.ts";
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
37
|
+
import { count, max, min, pack, sum } from "#query/find.ts";
|
|
38
|
+
import { parseQueryIr } from "#query/parse-ir.ts";
|
|
39
|
+
import { fieldJoins, inferred, isTerm, makeDuration, makeParam, makeSetParam, renderFieldKind, term } from "#query/scope.ts";
|
|
42
40
|
/** The frozen constructor vocabulary every rule builder spreads. */
|
|
43
41
|
const termOps = Object.freeze({
|
|
44
42
|
param: makeParam,
|
|
45
43
|
inSet: makeSetParam,
|
|
46
|
-
maskParam: makeMaskParam,
|
|
47
44
|
duration: makeDuration,
|
|
48
45
|
eq,
|
|
49
46
|
ne,
|
|
@@ -57,12 +54,9 @@ const termOps = Object.freeze({
|
|
|
57
54
|
or,
|
|
58
55
|
not,
|
|
59
56
|
count,
|
|
60
|
-
countDistinct,
|
|
61
57
|
sum,
|
|
62
58
|
min,
|
|
63
59
|
max,
|
|
64
|
-
argMax,
|
|
65
|
-
argMin,
|
|
66
60
|
pack
|
|
67
61
|
});
|
|
68
62
|
/** The empty rule state. */
|
|
@@ -177,8 +171,6 @@ function resolveBindings(context, label, relation, bindings) {
|
|
|
177
171
|
}));
|
|
178
172
|
break;
|
|
179
173
|
}
|
|
180
|
-
case "maskParam":
|
|
181
|
-
throw errors.new(`${label}.${fieldName}: an Allen-mask param is not a field-typed value — masks live in allen() conditions only`);
|
|
182
174
|
case "duration":
|
|
183
175
|
throw errors.new(`${label}.${fieldName}: the measure is not a field-typed value — it lives in comparisons and find entries`);
|
|
184
176
|
}
|
|
@@ -215,7 +207,7 @@ function advanceMatch(context, state, relation, bindings) {
|
|
|
215
207
|
});
|
|
216
208
|
}
|
|
217
209
|
/** Resolves one comparison side to its runtime term (variables and the measure ride by reference). */
|
|
218
|
-
function cmpTermDataOf(
|
|
210
|
+
function cmpTermDataOf(value) {
|
|
219
211
|
if (isTerm(value)) {
|
|
220
212
|
switch (value[term]) {
|
|
221
213
|
case "var":
|
|
@@ -226,8 +218,6 @@ function cmpTermDataOf(op, value) {
|
|
|
226
218
|
return Object.freeze({ kind: "setParam", name: value.name });
|
|
227
219
|
case "duration":
|
|
228
220
|
return Object.freeze({ kind: "measure", ref: value.over });
|
|
229
|
-
case "maskParam":
|
|
230
|
-
throw errors.new(`${op}: an Allen-mask param is not a comparison term — masks live in allen()'s mask position`);
|
|
231
221
|
}
|
|
232
222
|
}
|
|
233
223
|
return Object.freeze({ kind: "literal", value });
|
|
@@ -262,31 +252,23 @@ function sideUses(op, side, sibling, uses) {
|
|
|
262
252
|
/** Lowers one condition VALUE to its runtime data, recording param uses. */
|
|
263
253
|
function condDataOf(cond, uses) {
|
|
264
254
|
if (cond.cond === "cmp") {
|
|
265
|
-
const lhs = cmpTermDataOf(cond.
|
|
266
|
-
const rhs = cmpTermDataOf(cond.
|
|
255
|
+
const lhs = cmpTermDataOf(cond.lhs);
|
|
256
|
+
const rhs = cmpTermDataOf(cond.rhs);
|
|
267
257
|
sideUses(cond.op, lhs, rhs, uses);
|
|
268
258
|
sideUses(cond.op, rhs, lhs, uses);
|
|
269
|
-
let mask;
|
|
270
259
|
if (cond.op === "allen") {
|
|
271
260
|
const maskValue = cond.mask;
|
|
272
|
-
if (typeof maskValue
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
else if (isTerm(maskValue) && maskValue[term] === "maskParam") {
|
|
276
|
-
mask = Object.freeze({ kind: "param", name: maskValue.name });
|
|
277
|
-
uses.push(Object.freeze({
|
|
278
|
-
name: maskValue.name,
|
|
279
|
-
shape: "mask",
|
|
280
|
-
anchor: undefined,
|
|
281
|
-
op: "allen",
|
|
282
|
-
members: undefined
|
|
283
|
-
}));
|
|
284
|
-
}
|
|
285
|
-
else {
|
|
286
|
-
throw errors.new("allen: the mask position takes a 13-bit mask number or a maskParam");
|
|
261
|
+
if (typeof maskValue !== "number") {
|
|
262
|
+
throw errors.new("allen: the mask position takes a 13-bit mask number built from the ALLEN constants");
|
|
287
263
|
}
|
|
264
|
+
return Object.freeze({
|
|
265
|
+
kind: "cmp",
|
|
266
|
+
op: { kind: "allen", mask: maskValue },
|
|
267
|
+
lhs,
|
|
268
|
+
rhs
|
|
269
|
+
});
|
|
288
270
|
}
|
|
289
|
-
return Object.freeze({ kind: "cmp", op: cond.op
|
|
271
|
+
return Object.freeze({ kind: "cmp", op: { kind: cond.op }, lhs, rhs });
|
|
290
272
|
}
|
|
291
273
|
if (cond.cond === "tree") {
|
|
292
274
|
const children = cond.children.map(function lowerChild(child) {
|
|
@@ -301,11 +283,11 @@ function advanceWhere(context, state, cond) {
|
|
|
301
283
|
if (typeof cond !== "object" || cond === null || !("cond" in cond)) {
|
|
302
284
|
throw errors.new("where() takes a comparison, an and()/or() tree, or a negated atom");
|
|
303
285
|
}
|
|
304
|
-
if (cond.cond === "
|
|
286
|
+
if (cond.cond === "notInterior") {
|
|
305
287
|
const bindings = Object.fromEntries(Object.entries(cond.bindings ?? {}).filter(function defined([, value]) {
|
|
306
288
|
return value !== undefined;
|
|
307
289
|
}));
|
|
308
|
-
return
|
|
290
|
+
return notInteriorAdvance(context, state, cond.name, bindings);
|
|
309
291
|
}
|
|
310
292
|
if (cond.cond === "not") {
|
|
311
293
|
const relation = cond.relation;
|
|
@@ -328,36 +310,33 @@ function advanceWhere(context, state, cond) {
|
|
|
328
310
|
});
|
|
329
311
|
}
|
|
330
312
|
/**
|
|
331
|
-
* Extends a rule state with one
|
|
332
|
-
* vars validated at completion). A POSITIVE
|
|
333
|
-
* occurrence exactly as the engine represents it (`check_atoms` walks
|
|
313
|
+
* Extends a rule state with one interior atom (a named record over head keys;
|
|
314
|
+
* vars validated at completion). A POSITIVE interior atom is a positive
|
|
315
|
+
* occurrence exactly as the engine represents it (`check_atoms` walks Interior
|
|
334
316
|
* and Edb in one loop), so its variables GROUND: they enter the rule's
|
|
335
317
|
* boundness set, may ride the head, and satisfy negation safety — the
|
|
336
|
-
*
|
|
318
|
+
* interior-only identity projection of a finished table is spellable with no
|
|
337
319
|
* re-grounding join. A NEGATED one binds nothing, only rejects.
|
|
338
320
|
*/
|
|
339
|
-
function
|
|
321
|
+
function advanceInterior(state, target, bindings, kind) {
|
|
340
322
|
const resolved = [];
|
|
341
323
|
for (const [key, value] of Object.entries(bindings)) {
|
|
342
324
|
if (value === undefined) {
|
|
343
325
|
continue;
|
|
344
326
|
}
|
|
345
327
|
if (!isTerm(value) || value[term] !== "var") {
|
|
346
|
-
throw errors.new(`
|
|
328
|
+
throw errors.new(`interior ${target.name}: position ${key} takes a variable — bind literals and params through where()/match()`);
|
|
347
329
|
}
|
|
348
330
|
resolved.push(Object.freeze({ key, ref: value }));
|
|
349
331
|
}
|
|
350
332
|
const bound = new Set(state.bound);
|
|
351
|
-
if (
|
|
333
|
+
if (kind === "interior") {
|
|
352
334
|
for (const binding of resolved) {
|
|
353
335
|
bound.add(binding.ref);
|
|
354
336
|
}
|
|
355
337
|
}
|
|
356
338
|
return Object.freeze({
|
|
357
|
-
items: Object.freeze([
|
|
358
|
-
...state.items,
|
|
359
|
-
Object.freeze({ kind: "idb", rec, bindings: Object.freeze(resolved), negated })
|
|
360
|
-
]),
|
|
339
|
+
items: Object.freeze([...state.items, Object.freeze({ kind, target, bindings: Object.freeze(resolved) })]),
|
|
361
340
|
bound,
|
|
362
341
|
paramUses: state.paramUses
|
|
363
342
|
});
|
|
@@ -379,8 +358,6 @@ function aggDataOf(name, entry) {
|
|
|
379
358
|
switch (entry.agg) {
|
|
380
359
|
case "count":
|
|
381
360
|
return Object.freeze({ op: "count" });
|
|
382
|
-
case "countDistinct":
|
|
383
|
-
return Object.freeze({ op: "countDistinct", over: asVarTerm(`find ${name} (countDistinct)`, over) });
|
|
384
361
|
case "sum":
|
|
385
362
|
case "min":
|
|
386
363
|
case "max": {
|
|
@@ -392,14 +369,6 @@ function aggDataOf(name, entry) {
|
|
|
392
369
|
}
|
|
393
370
|
throw errors.new(`find ${name} (${entry.agg}): takes a variable or r.duration(v)`);
|
|
394
371
|
}
|
|
395
|
-
case "argMax":
|
|
396
|
-
case "argMin":
|
|
397
|
-
return Object.freeze({
|
|
398
|
-
op: "arg",
|
|
399
|
-
direction: entry.agg,
|
|
400
|
-
over: asVarTerm(`find ${name} (${entry.agg})`, over),
|
|
401
|
-
key: asVarTerm(`find ${name} (${entry.agg} key)`, entry.key)
|
|
402
|
-
});
|
|
403
372
|
case "pack":
|
|
404
373
|
return Object.freeze({ op: "pack", over: asVarTerm(`find ${name} (pack)`, over) });
|
|
405
374
|
default:
|
|
@@ -473,17 +442,14 @@ function assertNotClosed(where, position, ref) {
|
|
|
473
442
|
}
|
|
474
443
|
/**
|
|
475
444
|
* The classed mint slot one answer column's VALUES flow from: a projected
|
|
476
|
-
* variable's mint slot
|
|
477
|
-
*
|
|
445
|
+
* variable's mint slot. Counts, folds, `pack` and the measure derive
|
|
446
|
+
* numbers/intervals, so they resolve no slot.
|
|
478
447
|
*/
|
|
479
448
|
function findColumnSlotOf(context, column) {
|
|
480
449
|
const entry = column.entry;
|
|
481
450
|
if (entry.kind === "var") {
|
|
482
451
|
return mintSlotOf(context, entry.over);
|
|
483
452
|
}
|
|
484
|
-
if (entry.kind === "aggregate" && entry.agg.op === "arg") {
|
|
485
|
-
return mintSlotOf(context, entry.agg.over);
|
|
486
|
-
}
|
|
487
453
|
return undefined;
|
|
488
454
|
}
|
|
489
455
|
/** Validates one find column's variable references (boundness + the orderable/interval walls, off the var's own field). */
|
|
@@ -503,9 +469,6 @@ function validateColumn(context, bound, column) {
|
|
|
503
469
|
switch (agg.op) {
|
|
504
470
|
case "count":
|
|
505
471
|
return;
|
|
506
|
-
case "countDistinct":
|
|
507
|
-
assertBound(where, bound, agg.over);
|
|
508
|
-
return;
|
|
509
472
|
case "fold": {
|
|
510
473
|
if ("duration" in agg.over) {
|
|
511
474
|
assertBound(where, bound, agg.over.duration);
|
|
@@ -516,12 +479,6 @@ function validateColumn(context, bound, column) {
|
|
|
516
479
|
assertNotClosed(where, `the ${agg.fold} input`, agg.over);
|
|
517
480
|
return;
|
|
518
481
|
}
|
|
519
|
-
case "arg": {
|
|
520
|
-
assertBound(where, bound, agg.over);
|
|
521
|
-
assertBound(where, bound, agg.key);
|
|
522
|
-
assertNotClosed(where, `the ${agg.direction} key`, agg.key);
|
|
523
|
-
return;
|
|
524
|
-
}
|
|
525
482
|
case "pack":
|
|
526
483
|
assertBound(where, bound, agg.over);
|
|
527
484
|
assertInterval(where, agg.over);
|
|
@@ -540,8 +497,8 @@ function validateCond(context, bound, cond) {
|
|
|
540
497
|
if (side.kind === "var") {
|
|
541
498
|
assertBound(label, bound, side.ref);
|
|
542
499
|
const roster = rosterOf(side.ref.field);
|
|
543
|
-
if (isOrderOp(cond.op) && roster !== undefined) {
|
|
544
|
-
throw closedOrderError(label, `the ${cond.op} side ${side.ref.label}`, roster.name);
|
|
500
|
+
if (isOrderOp(cond.op.kind) && roster !== undefined) {
|
|
501
|
+
throw closedOrderError(label, `the ${cond.op.kind} side ${side.ref.label}`, roster.name);
|
|
545
502
|
}
|
|
546
503
|
}
|
|
547
504
|
if (side.kind === "measure") {
|
|
@@ -549,13 +506,13 @@ function validateCond(context, bound, cond) {
|
|
|
549
506
|
assertInterval(label, side.ref);
|
|
550
507
|
}
|
|
551
508
|
}
|
|
552
|
-
if ((cond.op === "eq" || cond.op === "ne") && cond.lhs.kind === "var" && cond.rhs.kind === "var") {
|
|
509
|
+
if ((cond.op.kind === "eq" || cond.op.kind === "ne") && cond.lhs.kind === "var" && cond.rhs.kind === "var") {
|
|
553
510
|
assertBound(label, bound, cond.lhs.ref);
|
|
554
511
|
assertBound(label, bound, cond.rhs.ref);
|
|
555
512
|
const lhs = mintSlotOf(context, cond.lhs.ref);
|
|
556
513
|
const rhs = mintSlotOf(context, cond.rhs.ref);
|
|
557
514
|
if (!fieldJoins(lhs, rhs)) {
|
|
558
|
-
throw errors.new(`${label}: ${cond.op}(${cond.lhs.ref.label}, ${cond.rhs.ref.label}) unifies domain-unequal fields — ${cond.lhs.ref.label} bound at ${renderFieldKind(lhs)}, ${cond.rhs.ref.label} at ${renderFieldKind(rhs)} (a var joins only class-equal slots; bare pairs only with bare)`);
|
|
515
|
+
throw errors.new(`${label}: ${cond.op.kind}(${cond.lhs.ref.label}, ${cond.rhs.ref.label}) unifies domain-unequal fields — ${cond.lhs.ref.label} bound at ${renderFieldKind(lhs)}, ${cond.rhs.ref.label} at ${renderFieldKind(rhs)} (a var joins only class-equal slots; bare pairs only with bare)`);
|
|
559
516
|
}
|
|
560
517
|
}
|
|
561
518
|
return;
|
|
@@ -565,20 +522,18 @@ function validateCond(context, bound, cond) {
|
|
|
565
522
|
}
|
|
566
523
|
}
|
|
567
524
|
/**
|
|
568
|
-
* Validates one
|
|
525
|
+
* Validates one interior item: every head column of the table is bound exactly
|
|
569
526
|
* once (a missing or extra key is a pointed error) and each variable joins
|
|
570
|
-
* its head column's classed slot. A POSITIVE
|
|
527
|
+
* its head column's classed slot. A POSITIVE interior atom GROUNDS its
|
|
571
528
|
* variables (a positive occurrence, exactly the engine's representation),
|
|
572
529
|
* so no boundness precondition exists; a NEGATED one binds nothing — its
|
|
573
530
|
* variables must be positively bound elsewhere in the rule, the same
|
|
574
|
-
* safety rule as EDB negation. When the
|
|
575
|
-
* (`
|
|
576
|
-
* head.
|
|
531
|
+
* safety rule as EDB negation. When the table's own first rule is in flight
|
|
532
|
+
* (`finds` empty), the completing rule's OWN find columns ARE the head.
|
|
577
533
|
*/
|
|
578
|
-
function
|
|
534
|
+
function validateInterior(context, bound, item, columns) {
|
|
579
535
|
const label = contextLabel(context);
|
|
580
|
-
const
|
|
581
|
-
const headColumns = head !== undefined ? head.finds : columns;
|
|
536
|
+
const headColumns = item.target.finds.length > 0 ? item.target.finds : columns;
|
|
582
537
|
const headNames = headColumns.map(function nameOf(column) {
|
|
583
538
|
return column.name;
|
|
584
539
|
});
|
|
@@ -587,17 +542,17 @@ function validateIdb(context, bound, item, columns) {
|
|
|
587
542
|
});
|
|
588
543
|
for (const key of keys) {
|
|
589
544
|
if (!headNames.includes(key)) {
|
|
590
|
-
throw errors.new(`${label}:
|
|
545
|
+
throw errors.new(`${label}: interior ${item.target.name} binds ${key}, not a head column of ${item.target.name} (head columns: ${headNames.join(", ")})`);
|
|
591
546
|
}
|
|
592
547
|
}
|
|
593
548
|
for (const name of headNames) {
|
|
594
549
|
if (!keys.includes(name)) {
|
|
595
|
-
throw errors.new(`${label}:
|
|
550
|
+
throw errors.new(`${label}: interior ${item.target.name} omits the head column ${name} — an interior join binds every head column of ${item.target.name}`);
|
|
596
551
|
}
|
|
597
552
|
}
|
|
598
553
|
for (const binding of item.bindings) {
|
|
599
|
-
if (item.
|
|
600
|
-
throw errors.new(`${label}: negated
|
|
554
|
+
if (item.kind === "negatedInterior" && !bound.has(binding.ref)) {
|
|
555
|
+
throw errors.new(`${label}: negated interior ${item.target.name} names the variable ${binding.ref.label}, but no positive atom of the rule binds it — a negated atom binds nothing, only rejects (the safety rule)`);
|
|
601
556
|
}
|
|
602
557
|
const headColumn = headColumns.find(function byName(column) {
|
|
603
558
|
return column.name === binding.key;
|
|
@@ -607,14 +562,14 @@ function validateIdb(context, bound, item, columns) {
|
|
|
607
562
|
}
|
|
608
563
|
const mint = mintSlotOf(context, binding.ref);
|
|
609
564
|
if (!fieldJoins(headColumn.slot, mint)) {
|
|
610
|
-
throw errors.new(`${label}:
|
|
565
|
+
throw errors.new(`${label}: interior ${item.target.name} joins the variable ${binding.ref.label} (${renderFieldKind(mint)}) at head column ${binding.key} (${renderFieldKind(headColumn.slot)}) — a var joins only class-equal slots; bare pairs only with bare`);
|
|
611
566
|
}
|
|
612
567
|
}
|
|
613
568
|
}
|
|
614
569
|
/**
|
|
615
570
|
* Completes one rule: enriches the find columns (declaration-order-safe
|
|
616
571
|
* keys, boundness validated, each column's classed slot and closed slice
|
|
617
|
-
* resolved), then walks the body walls — negated-atom boundness safety,
|
|
572
|
+
* resolved), then walks the body walls — negated-atom boundness safety, interior
|
|
618
573
|
* head pairing, and condition validation.
|
|
619
574
|
*/
|
|
620
575
|
function completeRule(context, state, rawColumns) {
|
|
@@ -636,8 +591,8 @@ function completeRule(context, state, rawColumns) {
|
|
|
636
591
|
}
|
|
637
592
|
}
|
|
638
593
|
}
|
|
639
|
-
if (item.kind === "
|
|
640
|
-
|
|
594
|
+
if (item.kind === "interior" || item.kind === "negatedInterior") {
|
|
595
|
+
validateInterior(context, state.bound, item, columns);
|
|
641
596
|
}
|
|
642
597
|
if (item.kind === "cond") {
|
|
643
598
|
validateCond(context, state.bound, item.cond);
|
|
@@ -654,56 +609,70 @@ function contextLabel(context) {
|
|
|
654
609
|
switch (context.kind) {
|
|
655
610
|
case "query":
|
|
656
611
|
return "query rule";
|
|
657
|
-
case "
|
|
658
|
-
return `
|
|
659
|
-
case "
|
|
660
|
-
return
|
|
612
|
+
case "interior":
|
|
613
|
+
return `interior ${context.self} rule`;
|
|
614
|
+
case "rec-base":
|
|
615
|
+
return `rec ${context.self.name} base`;
|
|
616
|
+
case "rec-arm":
|
|
617
|
+
return `rec ${context.self.name} rec`;
|
|
661
618
|
}
|
|
662
619
|
}
|
|
663
|
-
/**
|
|
664
|
-
function
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
620
|
+
/** RecHandle is name-only staging; RecHead/RecData carry the sealed finds. */
|
|
621
|
+
function isRecHead(rec) {
|
|
622
|
+
return Array.isArray(rec.finds);
|
|
623
|
+
}
|
|
624
|
+
/** Resolves a derived-table name against the context's visible tables. */
|
|
625
|
+
function lookupDerived(context, name) {
|
|
626
|
+
const interior = context.interiors.find(function byName(candidate) {
|
|
627
|
+
return candidate.name === name;
|
|
628
|
+
});
|
|
629
|
+
if (interior !== undefined) {
|
|
630
|
+
if (context.kind === "interior" && name === context.self) {
|
|
631
|
+
throw errors.new(`interior ${name}: an interior does not read itself — declaration order is topological (a self-read is InteriorNotPrior)`);
|
|
671
632
|
}
|
|
672
|
-
return
|
|
633
|
+
return interior;
|
|
673
634
|
}
|
|
674
|
-
|
|
675
|
-
|
|
635
|
+
const rec = "rec" in context ? context.rec : undefined;
|
|
636
|
+
if (rec !== undefined && rec.name === name) {
|
|
637
|
+
if (context.kind === "interior") {
|
|
638
|
+
throw errors.new(`interior ${context.self}: interiors cannot read the rec — this cut's interiors are a prefix`);
|
|
639
|
+
}
|
|
640
|
+
if (context.kind === "rec-base") {
|
|
641
|
+
throw errors.new(`rec ${rec.name}: a base arm does not read the rec — self-atoms belong on rec arms`);
|
|
642
|
+
}
|
|
643
|
+
if (!isRecHead(rec)) {
|
|
644
|
+
throw errors.new(`rec ${rec.name}: rec arms resolve the rec head after base arms seal it`);
|
|
645
|
+
}
|
|
646
|
+
return rec;
|
|
676
647
|
}
|
|
677
|
-
|
|
648
|
+
throw errors.new(`${contextLabel(context)}: no derived table named ${name} is in scope`);
|
|
649
|
+
}
|
|
650
|
+
/** Validates and records one interior atom per the context's cut. */
|
|
651
|
+
function interiorAdvance(context, state, name, bindings) {
|
|
652
|
+
return advanceInterior(state, lookupDerived(context, name), bindings, "interior");
|
|
678
653
|
}
|
|
679
654
|
/**
|
|
680
|
-
* Validates and records one NEGATED finished-
|
|
681
|
-
*
|
|
682
|
-
*
|
|
683
|
-
* only negation *through* a cycle, which the rec-context refusal here
|
|
684
|
-
* makes unwritable).
|
|
655
|
+
* Validates and records one NEGATED finished-table atom — main and interior
|
|
656
|
+
* rules: a finished set is a set. Rec bodies refuse every negation
|
|
657
|
+
* (`NegationInRec` — self is the wall; EDB / earlier-interior is this-cut).
|
|
685
658
|
*/
|
|
686
|
-
function
|
|
687
|
-
if (context.kind === "
|
|
688
|
-
throw errors.new(
|
|
659
|
+
function notInteriorAdvance(context, state, name, bindings) {
|
|
660
|
+
if (context.kind === "rec-base" || context.kind === "rec-arm") {
|
|
661
|
+
throw errors.new(`rec ${context.self.name}: a rec rule negates no table — self-negation is negation through the cycle (a finished set is what keeps the operator monotone), and a finished table's fold belongs in the main rules`);
|
|
689
662
|
}
|
|
690
|
-
|
|
691
|
-
throw errors.new(`rec ${context.self.name}: a recursive rule negates no stratum — self-negation is negation through the cycle (a finished set is what keeps the operator monotone), and a finished stratum's fold belongs in the output rules`);
|
|
692
|
-
}
|
|
693
|
-
if (!context.program.recs.includes(target.data)) {
|
|
694
|
-
throw errors.new(`idb ${target.name}: the rec was declared by a different program — rec identity is the membership rule`);
|
|
695
|
-
}
|
|
696
|
-
return advanceIdb(state, target.data, bindings, true);
|
|
663
|
+
return advanceInterior(state, lookupDerived(context, name), bindings, "negatedInterior");
|
|
697
664
|
}
|
|
698
|
-
/** Classifies one find record per the context (
|
|
665
|
+
/** Classifies one find record per the context (interior and rec heads project bound variables only). */
|
|
699
666
|
function findColumns(context, entries) {
|
|
700
667
|
const columns = [];
|
|
668
|
+
const derivedHead = context.kind !== "query";
|
|
701
669
|
for (const [name, entry] of Object.entries(entries)) {
|
|
702
670
|
if (entry === undefined) {
|
|
703
671
|
continue;
|
|
704
672
|
}
|
|
705
|
-
if (
|
|
706
|
-
|
|
673
|
+
if (derivedHead && !(isTerm(entry) && entry[term] === "var")) {
|
|
674
|
+
const who = context.kind === "interior" ? `interior ${context.self}` : `rec ${context.self.name}`;
|
|
675
|
+
throw errors.new(`${who}: a rec head projects bound variables only — aggregates and the measure read finished sets (unwritable here)`);
|
|
707
676
|
}
|
|
708
677
|
columns.push(findColumnOf(name, entry));
|
|
709
678
|
}
|
|
@@ -718,8 +687,8 @@ function makeRawChain(context, state) {
|
|
|
718
687
|
where(cond) {
|
|
719
688
|
return makeRawChain(context, advanceWhere(context, state, cond));
|
|
720
689
|
},
|
|
721
|
-
|
|
722
|
-
return makeRawChain(context,
|
|
690
|
+
interior(name, bindings) {
|
|
691
|
+
return makeRawChain(context, interiorAdvance(context, state, name, bindings));
|
|
723
692
|
},
|
|
724
693
|
find(entries) {
|
|
725
694
|
return makeRuleValue(completeRule(context, state, findColumns(context, entries)));
|
|
@@ -735,8 +704,8 @@ function makeRawScope(context) {
|
|
|
735
704
|
match(relation, bindings) {
|
|
736
705
|
return makeRawChain(context, advanceMatch(context, EMPTY_RULE, relation, bindings));
|
|
737
706
|
},
|
|
738
|
-
|
|
739
|
-
return makeRawChain(context,
|
|
707
|
+
interior(name, bindings) {
|
|
708
|
+
return makeRawChain(context, interiorAdvance(context, EMPTY_RULE, name, bindings));
|
|
740
709
|
}
|
|
741
710
|
};
|
|
742
711
|
Object.freeze(scope);
|
|
@@ -755,18 +724,39 @@ function isTypedScope(scope) {
|
|
|
755
724
|
return typeof scope.match === "function";
|
|
756
725
|
}
|
|
757
726
|
/** Builds one query-rule builder (the typed face of the raw builder). */
|
|
758
|
-
function makeQueryRuleScope(theory) {
|
|
759
|
-
const raw = makeRawScope({ kind: "query", classes: theory.classes, theory });
|
|
727
|
+
function makeQueryRuleScope(theory, env) {
|
|
728
|
+
const raw = makeRawScope({ kind: "query", classes: theory.classes, theory, ...env });
|
|
760
729
|
if (!isTypedScope(raw)) {
|
|
761
730
|
throw errors.new("query rule builder construction incomplete");
|
|
762
731
|
}
|
|
763
732
|
return raw;
|
|
764
733
|
}
|
|
765
|
-
/** Builds one
|
|
766
|
-
function
|
|
767
|
-
const raw = makeRawScope({ kind: "
|
|
734
|
+
/** Builds one interior-rule builder. */
|
|
735
|
+
function makeInteriorRuleScope(theory, env, self) {
|
|
736
|
+
const raw = makeRawScope({ kind: "interior", self, classes: theory.classes, theory, ...env });
|
|
737
|
+
if (!isTypedScope(raw)) {
|
|
738
|
+
throw errors.new("interior rule builder construction incomplete");
|
|
739
|
+
}
|
|
740
|
+
return raw;
|
|
741
|
+
}
|
|
742
|
+
function makeRecRuleScope(theory, env, self, kind) {
|
|
743
|
+
const raw = kind === "rec-base"
|
|
744
|
+
? makeRawScope({
|
|
745
|
+
kind: "rec-base",
|
|
746
|
+
self: self,
|
|
747
|
+
classes: theory.classes,
|
|
748
|
+
theory,
|
|
749
|
+
...env
|
|
750
|
+
})
|
|
751
|
+
: makeRawScope({
|
|
752
|
+
kind: "rec-arm",
|
|
753
|
+
self: self,
|
|
754
|
+
classes: theory.classes,
|
|
755
|
+
theory,
|
|
756
|
+
...env
|
|
757
|
+
});
|
|
768
758
|
if (!isTypedScope(raw)) {
|
|
769
|
-
throw errors.new("
|
|
759
|
+
throw errors.new("rec rule builder construction incomplete");
|
|
770
760
|
}
|
|
771
761
|
return raw;
|
|
772
762
|
}
|
|
@@ -784,9 +774,6 @@ function headSignature(column) {
|
|
|
784
774
|
if (agg.op === "fold") {
|
|
785
775
|
return `${column.name}:${agg.fold}`;
|
|
786
776
|
}
|
|
787
|
-
if (agg.op === "arg") {
|
|
788
|
-
return `${column.name}:${agg.direction}`;
|
|
789
|
-
}
|
|
790
777
|
return `${column.name}:${agg.op}`;
|
|
791
778
|
}
|
|
792
779
|
/** The roster a param anchor carries: present exactly on a closed-reference field anchor. */
|
|
@@ -798,17 +785,18 @@ function renderParamAnchor(roster) {
|
|
|
798
785
|
return roster === undefined ? "a non-closed position" : `a ${roster.name} reference`;
|
|
799
786
|
}
|
|
800
787
|
/**
|
|
801
|
-
* Folds every rule's param uses (
|
|
802
|
-
*
|
|
803
|
-
* use mints the dense `ParamId`, the first
|
|
804
|
-
* wire, and one name keeps one shape AND one
|
|
805
|
-
* ban needs no registry arm: an order use always
|
|
806
|
-
* domain (the no-variable-side spelling is refused
|
|
807
|
-
* constructor), so a closed-anchored param under an
|
|
808
|
-
* one-domain wall here — and an order use whose
|
|
809
|
-
* closed-bound dies at the comparison's own var-side
|
|
788
|
+
* Folds every rule's param uses (interiors in declaration order, then rec
|
|
789
|
+
* base, then rec arms, then main — exactly the lowering walk) into the
|
|
790
|
+
* query's registry: first use mints the dense `ParamId`, the first
|
|
791
|
+
* FIELD-ANCHORED use types the wire, and one name keeps one shape AND one
|
|
792
|
+
* closedness. The orderable ban needs no registry arm: an order use always
|
|
793
|
+
* anchors its SIBLING's domain (the no-variable-side spelling is refused
|
|
794
|
+
* at the comparison constructor), so a closed-anchored param under an
|
|
795
|
+
* order op dies at the one-domain wall here — and an order use whose
|
|
796
|
+
* sibling is itself closed-bound dies at the comparison's own var-side
|
|
797
|
+
* wall first.
|
|
810
798
|
*/
|
|
811
|
-
function paramRegistryOf(
|
|
799
|
+
function paramRegistryOf(interiors, rec, rules) {
|
|
812
800
|
const order = [];
|
|
813
801
|
const byName = new Map();
|
|
814
802
|
function fold(uses) {
|
|
@@ -843,8 +831,16 @@ function paramRegistryOf(recs, rules) {
|
|
|
843
831
|
}
|
|
844
832
|
}
|
|
845
833
|
}
|
|
846
|
-
for (const
|
|
847
|
-
for (const rule of
|
|
834
|
+
for (const interior of interiors) {
|
|
835
|
+
for (const rule of interior.rules) {
|
|
836
|
+
fold(rule.paramUses);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
if (rec !== undefined) {
|
|
840
|
+
for (const rule of rec.base) {
|
|
841
|
+
fold(rule.paramUses);
|
|
842
|
+
}
|
|
843
|
+
for (const rule of rec.rec) {
|
|
848
844
|
fold(rule.paramUses);
|
|
849
845
|
}
|
|
850
846
|
}
|
|
@@ -880,93 +876,204 @@ function paramRegistryOf(recs, rules) {
|
|
|
880
876
|
return Object.freeze({ name, shape: entry.shape, anchor: entry.anchor, op: entry.op, membership });
|
|
881
877
|
}));
|
|
882
878
|
}
|
|
883
|
-
/**
|
|
884
|
-
|
|
885
|
-
* derive the SAME head (name and aggregate shape, position for position —
|
|
886
|
-
* the decode labels and the engine's alignment rule agree), and the param
|
|
887
|
-
* registry folds in program-walk order.
|
|
888
|
-
*/
|
|
889
|
-
function makeRawQuery(theory, recs, rules) {
|
|
879
|
+
/** Asserts every rule in a list derives the same head (name, aggregate shape, closed slice, class). */
|
|
880
|
+
function assertAlignedHeads(label, rules) {
|
|
890
881
|
const first = rules[0];
|
|
891
882
|
if (first === undefined) {
|
|
892
|
-
throw errors.new(
|
|
883
|
+
throw errors.new(`${label} needs at least one rule`);
|
|
893
884
|
}
|
|
894
885
|
const signature = first.finds.map(headSignature).join(", ");
|
|
895
886
|
rules.forEach(function verifyHead(rule, index) {
|
|
896
887
|
const candidate = rule.finds.map(headSignature).join(", ");
|
|
897
888
|
if (candidate !== signature) {
|
|
898
|
-
throw errors.new(`every rule of
|
|
889
|
+
throw errors.new(`every rule of ${label} derives the same head — rule 0 finds (${signature}), rule ${index} finds (${candidate})`);
|
|
899
890
|
}
|
|
900
|
-
// The closed slice is part of the head too: one answer column decodes
|
|
901
|
-
// through one roster, so a union whose rules bind a column at different
|
|
902
|
-
// vocabularies (or one closed, one bare) is refused pointed.
|
|
903
891
|
rule.finds.forEach(function verifyClosedSlice(column, position) {
|
|
904
892
|
const lead = first.finds[position];
|
|
905
893
|
if (lead !== undefined && column.closed !== lead.closed) {
|
|
906
|
-
throw errors.new(`every rule of
|
|
894
|
+
throw errors.new(`every rule of ${label} derives the same head — the head column ${lead.name} is ${renderClosedSlice(lead.closed)} in rule 0 but ${renderClosedSlice(column.closed)} in rule ${index} (one column decodes through one roster)`);
|
|
907
895
|
}
|
|
908
|
-
// The law-class wall on the union head: one answer column is one
|
|
909
|
-
// value space, so the classed mint slot each rule binds the column
|
|
910
|
-
// at must join across rules — the SAME fieldJoins judgment every
|
|
911
|
-
// join/eq/negated-atom position enforces (the SDK holds it because
|
|
912
|
-
// the wire IR carries no domains).
|
|
913
896
|
if (lead === undefined) {
|
|
914
897
|
return;
|
|
915
898
|
}
|
|
916
899
|
if (lead.slot !== undefined && column.slot !== undefined && !fieldJoins(lead.slot, column.slot)) {
|
|
917
|
-
throw errors.new(`every rule of
|
|
900
|
+
throw errors.new(`every rule of ${label} derives the same head — the head column ${lead.name} is bound at ${renderFieldKind(lead.slot)} in rule 0 but at ${renderFieldKind(column.slot)} in rule ${index} (a head column joins only class-equal slots; bare pairs only with bare)`);
|
|
918
901
|
}
|
|
919
902
|
});
|
|
920
903
|
});
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
904
|
+
}
|
|
905
|
+
function afterMainError(what) {
|
|
906
|
+
return errors.new(`query: ${what} after a main rule is unwritable — declaration order is interiors, then rec, then main`);
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Assembles the runtime query value over completed rules: every rule must
|
|
910
|
+
* derive the SAME head (name and aggregate shape, position for position —
|
|
911
|
+
* the decode labels and the engine's alignment rule agree), and the param
|
|
912
|
+
* registry folds in query-walk order. CQ lowering does not mention rec;
|
|
913
|
+
* Reach carries RecData by value.
|
|
914
|
+
*/
|
|
915
|
+
function makeRawQuery(theory, interiors, rec, rules) {
|
|
916
|
+
assertAlignedHeads("a query", rules);
|
|
917
|
+
const first = rules[0];
|
|
918
|
+
if (first === undefined) {
|
|
919
|
+
throw errors.new("a query needs at least one rule");
|
|
920
|
+
}
|
|
921
|
+
const env = rec === undefined ? { interiors } : { interiors, rec };
|
|
922
|
+
const frozenInteriors = Object.freeze([...interiors]);
|
|
923
|
+
const frozenRules = Object.freeze([...rules]);
|
|
924
|
+
const params = paramRegistryOf(interiors, rec, rules);
|
|
925
|
+
const data = rec === undefined
|
|
926
|
+
? Object.freeze({
|
|
927
|
+
kind: "cq",
|
|
928
|
+
interiors: frozenInteriors,
|
|
929
|
+
rules: frozenRules,
|
|
930
|
+
finds: first.finds,
|
|
931
|
+
params
|
|
932
|
+
})
|
|
933
|
+
: Object.freeze({
|
|
934
|
+
kind: "reach",
|
|
935
|
+
interiors: frozenInteriors,
|
|
936
|
+
rec,
|
|
937
|
+
rules: frozenRules,
|
|
938
|
+
finds: first.finds,
|
|
939
|
+
params
|
|
940
|
+
});
|
|
927
941
|
const value = {
|
|
928
942
|
schema: theory,
|
|
929
943
|
data,
|
|
930
944
|
rule(build) {
|
|
931
|
-
const built = build(makeRawScope({ kind: "query", classes: theory.classes, theory }));
|
|
932
|
-
return makeRawQuery(theory,
|
|
945
|
+
const built = build(makeRawScope({ kind: "query", classes: theory.classes, theory, ...env }));
|
|
946
|
+
return makeRawQuery(theory, interiors, rec, [...rules, built.rule]);
|
|
947
|
+
},
|
|
948
|
+
interior() {
|
|
949
|
+
throw afterMainError("interior");
|
|
950
|
+
},
|
|
951
|
+
reach() {
|
|
952
|
+
throw afterMainError("reach");
|
|
933
953
|
}
|
|
934
954
|
};
|
|
935
955
|
Object.freeze(value);
|
|
936
956
|
return value;
|
|
937
957
|
}
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
* the checkable fact — the value was assembled over the identical theory —
|
|
941
|
-
* is verified before the raw value is admitted at its typed face.
|
|
942
|
-
*/
|
|
943
|
-
function isQueryValue(theory, value) {
|
|
944
|
-
return value.schema === theory;
|
|
958
|
+
function makeQuery(theory, interiors, rec, rules) {
|
|
959
|
+
return makeRawQuery(theory, interiors, rec, rules);
|
|
945
960
|
}
|
|
946
|
-
/**
|
|
947
|
-
function
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
throw errors.new("query value construction incomplete");
|
|
961
|
+
/** Collects one Interior from its builders. */
|
|
962
|
+
function collectInterior(theory, env, name, builds) {
|
|
963
|
+
if (builds.length === 0) {
|
|
964
|
+
throw errors.new(`query: interior ${name} needs at least one rule`);
|
|
951
965
|
}
|
|
952
|
-
|
|
966
|
+
const rules = builds.map(function buildRule(buildOne) {
|
|
967
|
+
return buildOne(makeInteriorRuleScope(theory, env, name)).rule;
|
|
968
|
+
});
|
|
969
|
+
assertAlignedHeads(`interior ${name}`, rules);
|
|
970
|
+
const first = rules[0];
|
|
971
|
+
if (first === undefined) {
|
|
972
|
+
throw errors.new(`query: interior ${name} needs at least one rule`);
|
|
973
|
+
}
|
|
974
|
+
return Object.freeze({ name, finds: first.finds, rules: Object.freeze(rules) });
|
|
953
975
|
}
|
|
954
|
-
/**
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
976
|
+
/** Collects the Rec from tagged base/rec builder arrays. */
|
|
977
|
+
function collectRec(theory, interiors, name, baseBuilds, recBuilds) {
|
|
978
|
+
if (baseBuilds.length === 0) {
|
|
979
|
+
throw errors.new(`query: rec ${name} has no base arms`);
|
|
980
|
+
}
|
|
981
|
+
if (recBuilds.length === 0) {
|
|
982
|
+
throw errors.new(`query: rec ${name} has no rec arms`);
|
|
983
|
+
}
|
|
984
|
+
const handle = Object.freeze({ name });
|
|
985
|
+
const baseEnv = { interiors, rec: handle };
|
|
986
|
+
const base = baseBuilds.map(function buildBase(buildOne) {
|
|
987
|
+
return buildOne(makeRecRuleScope(theory, baseEnv, handle, "rec-base")).rule;
|
|
988
|
+
});
|
|
989
|
+
assertAlignedHeads(`rec ${name}`, base);
|
|
990
|
+
const first = base[0];
|
|
991
|
+
if (first === undefined) {
|
|
992
|
+
throw errors.new(`query: rec ${name} has no base arms`);
|
|
993
|
+
}
|
|
994
|
+
const firstFind = first.finds[0];
|
|
995
|
+
if (firstFind === undefined) {
|
|
996
|
+
throw errors.new(`query: rec ${name} has no head`);
|
|
997
|
+
}
|
|
998
|
+
const finds = [firstFind, ...first.finds.slice(1)];
|
|
999
|
+
const head = Object.freeze({ name, finds });
|
|
1000
|
+
const recEnv = { interiors, rec: head };
|
|
1001
|
+
const rec = recBuilds.map(function buildRec(buildOne) {
|
|
1002
|
+
return buildOne(makeRecRuleScope(theory, recEnv, head, "rec-arm")).rule;
|
|
1003
|
+
});
|
|
1004
|
+
assertAlignedHeads(`rec ${name}`, [...base, ...rec]);
|
|
1005
|
+
const firstRec = rec[0];
|
|
1006
|
+
if (firstRec === undefined) {
|
|
1007
|
+
throw errors.new(`query: rec ${name} has no rec arms`);
|
|
1008
|
+
}
|
|
1009
|
+
const sealedBase = [first, ...base.slice(1)];
|
|
1010
|
+
const sealedRec = [firstRec, ...rec.slice(1)];
|
|
1011
|
+
const recData = Object.freeze({
|
|
1012
|
+
name,
|
|
1013
|
+
finds,
|
|
1014
|
+
base: sealedBase,
|
|
1015
|
+
rec: sealedRec
|
|
1016
|
+
});
|
|
1017
|
+
return recData;
|
|
1018
|
+
}
|
|
1019
|
+
/** Builds the CQ query start (interiors, then reach or the first main rule). */
|
|
1020
|
+
function makeQueryStart(theory, interiors) {
|
|
1021
|
+
const env = { interiors };
|
|
1022
|
+
const start = {
|
|
1023
|
+
interior(name, ...builds) {
|
|
1024
|
+
if (interiors.some(function sameName(interior) {
|
|
1025
|
+
return interior.name === name;
|
|
1026
|
+
})) {
|
|
1027
|
+
throw errors.new(`query: interior ${name} is already declared — names are unique`);
|
|
1028
|
+
}
|
|
1029
|
+
if (name.length === 0) {
|
|
1030
|
+
throw errors.new("query: an interior needs a name");
|
|
1031
|
+
}
|
|
1032
|
+
const data = collectInterior(theory, env, name, builds);
|
|
1033
|
+
return makeQueryStart(theory, [...interiors, data]);
|
|
1034
|
+
},
|
|
1035
|
+
reach(name, arms) {
|
|
1036
|
+
if (interiors.some(function sameName(interior) {
|
|
1037
|
+
return interior.name === name;
|
|
1038
|
+
})) {
|
|
1039
|
+
throw errors.new(`query: interior and rec share the name ${name}`);
|
|
1040
|
+
}
|
|
1041
|
+
if (name.length === 0) {
|
|
1042
|
+
throw errors.new("query: reach needs a name");
|
|
1043
|
+
}
|
|
1044
|
+
const data = collectRec(theory, interiors, name, arms.base, arms.rec);
|
|
1045
|
+
return makeQueryReachStart(theory, interiors, data);
|
|
1046
|
+
},
|
|
1047
|
+
rule(build) {
|
|
1048
|
+
const built = build(makeQueryRuleScope(theory, env));
|
|
1049
|
+
return makeQuery(theory, interiors, undefined, [built.rule]);
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
Object.freeze(start);
|
|
1053
|
+
return start;
|
|
1054
|
+
}
|
|
1055
|
+
/** Builds the Reach query start (rec sealed; only the first main rule remains). */
|
|
1056
|
+
function makeQueryReachStart(theory, interiors, rec) {
|
|
1057
|
+
const env = { interiors, rec };
|
|
961
1058
|
const start = {
|
|
962
1059
|
rule(build) {
|
|
963
|
-
const built = build(makeQueryRuleScope(theory));
|
|
964
|
-
return makeQuery(theory,
|
|
1060
|
+
const built = build(makeQueryRuleScope(theory, env));
|
|
1061
|
+
return makeQuery(theory, interiors, rec, [built.rule]);
|
|
965
1062
|
}
|
|
966
1063
|
};
|
|
967
1064
|
Object.freeze(start);
|
|
968
1065
|
return start;
|
|
969
1066
|
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Opens a query over a schema: `query(S).rule(r => ...)`, optionally with
|
|
1069
|
+
* `interior` / `reach` first. Each `.rule` adds one conjunctive rule;
|
|
1070
|
+
* multiple rules are the set union. The schema's law-computed class map and
|
|
1071
|
+
* theory value ride into every rule builder — the join walls compare
|
|
1072
|
+
* against the mint slots off it.
|
|
1073
|
+
*/
|
|
1074
|
+
function query(theory) {
|
|
1075
|
+
return makeQueryStart(theory, []);
|
|
1076
|
+
}
|
|
970
1077
|
/**
|
|
971
1078
|
* Tags one closed-reference literal: the handle NAME, verified against the
|
|
972
1079
|
* roster and translated to its declaration-order row id, tagged u64. THE
|
|
@@ -1141,30 +1248,29 @@ function lowerBindingTerm(ctx, context, binding, ids) {
|
|
|
1141
1248
|
}
|
|
1142
1249
|
}
|
|
1143
1250
|
/**
|
|
1144
|
-
* Lowers one
|
|
1145
|
-
* head position i. Every head column of the
|
|
1251
|
+
* Lowers one interior atom: named bindings placed by HEAD order, `FieldId(i)` =
|
|
1252
|
+
* head position i. Every head column of the table must be bound (a missing key
|
|
1146
1253
|
* is refused pointed); the var-id assignment order is head order, so the
|
|
1147
1254
|
* first-use numbering matches the name-keyed edition exactly.
|
|
1148
1255
|
*/
|
|
1149
|
-
function
|
|
1150
|
-
const
|
|
1151
|
-
if (
|
|
1152
|
-
throw errors.new(`query lowering:
|
|
1256
|
+
function lowerInteriorAtom(ctx, target, bindings, ids) {
|
|
1257
|
+
const interior = ctx.interiorIds.get(target.name);
|
|
1258
|
+
if (interior === undefined) {
|
|
1259
|
+
throw errors.new(`query lowering: derived table ${target.name} was not declared on this query`);
|
|
1153
1260
|
}
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
throw errors.new(`query lowering: rec ${rec.name} has no rules`);
|
|
1261
|
+
if (target.finds.length === 0) {
|
|
1262
|
+
throw errors.new(`query lowering: derived table ${target.name} has no head`);
|
|
1157
1263
|
}
|
|
1158
|
-
const irBindings =
|
|
1264
|
+
const irBindings = target.finds.map(function lowerPosition(column, position) {
|
|
1159
1265
|
const binding = bindings.find(function byKey(candidate) {
|
|
1160
1266
|
return candidate.key === column.name;
|
|
1161
1267
|
});
|
|
1162
1268
|
if (binding === undefined) {
|
|
1163
|
-
throw errors.new(`query lowering:
|
|
1269
|
+
throw errors.new(`query lowering: interior ${target.name} omits head column ${column.name}`);
|
|
1164
1270
|
}
|
|
1165
1271
|
return [position, { kind: "var", var: ids.of(binding.ref) }];
|
|
1166
1272
|
});
|
|
1167
|
-
return { source: { kind: "
|
|
1273
|
+
return { source: { kind: "interior", interior }, bindings: irBindings };
|
|
1168
1274
|
}
|
|
1169
1275
|
/** Lowers one comparison side; literals tag by the sibling's anchor (op-aware at `pointIn`). */
|
|
1170
1276
|
function lowerCmpTerm(ctx, side, sibling, ids, op) {
|
|
@@ -1201,24 +1307,17 @@ function cmpAnchorOf(ctx, sibling) {
|
|
|
1201
1307
|
}
|
|
1202
1308
|
/** Lowers one comparison. */
|
|
1203
1309
|
function lowerComparison(ctx, cmp, ids) {
|
|
1204
|
-
if (cmp.op === "allen") {
|
|
1205
|
-
const maskData = cmp.mask;
|
|
1206
|
-
if (maskData === undefined) {
|
|
1207
|
-
throw errors.new("query lowering: an allen comparison lost its mask");
|
|
1208
|
-
}
|
|
1209
|
-
const mask = maskData.kind === "literal"
|
|
1210
|
-
? { kind: "literal", mask: maskData.mask }
|
|
1211
|
-
: { kind: "param", param: paramIdOf(ctx, maskData.name) };
|
|
1310
|
+
if (cmp.op.kind === "allen") {
|
|
1212
1311
|
return {
|
|
1213
|
-
op: { kind: "allen", mask },
|
|
1312
|
+
op: { kind: "allen", mask: cmp.op.mask },
|
|
1214
1313
|
lhs: lowerCmpTerm(ctx, cmp.lhs, cmp.rhs, ids, "allen"),
|
|
1215
1314
|
rhs: lowerCmpTerm(ctx, cmp.rhs, cmp.lhs, ids, "allen")
|
|
1216
1315
|
};
|
|
1217
1316
|
}
|
|
1218
1317
|
return {
|
|
1219
|
-
op: { kind: cmp.op },
|
|
1220
|
-
lhs: lowerCmpTerm(ctx, cmp.lhs, cmp.rhs, ids, cmp.op),
|
|
1221
|
-
rhs: lowerCmpTerm(ctx, cmp.rhs, cmp.lhs, ids, cmp.op)
|
|
1318
|
+
op: { kind: cmp.op.kind },
|
|
1319
|
+
lhs: lowerCmpTerm(ctx, cmp.lhs, cmp.rhs, ids, cmp.op.kind),
|
|
1320
|
+
rhs: lowerCmpTerm(ctx, cmp.rhs, cmp.lhs, ids, cmp.op.kind)
|
|
1222
1321
|
};
|
|
1223
1322
|
}
|
|
1224
1323
|
/** Lowers one condition node (comparison leaf or and/or tree). */
|
|
@@ -1245,16 +1344,12 @@ function lowerFind(entry, ids) {
|
|
|
1245
1344
|
switch (agg.op) {
|
|
1246
1345
|
case "count":
|
|
1247
1346
|
return { kind: "aggregate", op: { kind: "count" } };
|
|
1248
|
-
case "countDistinct":
|
|
1249
|
-
return { kind: "aggregate", op: { kind: "countDistinct" }, over: ids.of(agg.over) };
|
|
1250
1347
|
case "fold": {
|
|
1251
1348
|
if ("duration" in agg.over) {
|
|
1252
1349
|
return { kind: "aggregateMeasure", op: { kind: agg.fold }, over: ids.of(agg.over.duration) };
|
|
1253
1350
|
}
|
|
1254
1351
|
return { kind: "aggregate", op: { kind: agg.fold }, over: ids.of(agg.over) };
|
|
1255
1352
|
}
|
|
1256
|
-
case "arg":
|
|
1257
|
-
return { kind: "aggregate", op: { kind: agg.direction, key: ids.of(agg.key) }, over: ids.of(agg.over) };
|
|
1258
1353
|
case "pack":
|
|
1259
1354
|
return { kind: "aggregate", op: { kind: "pack" }, over: ids.of(agg.over) };
|
|
1260
1355
|
}
|
|
@@ -1264,12 +1359,8 @@ function headOpOf(agg) {
|
|
|
1264
1359
|
switch (agg.op) {
|
|
1265
1360
|
case "count":
|
|
1266
1361
|
return "count";
|
|
1267
|
-
case "countDistinct":
|
|
1268
|
-
return "countDistinct";
|
|
1269
1362
|
case "fold":
|
|
1270
1363
|
return agg.fold;
|
|
1271
|
-
case "arg":
|
|
1272
|
-
return agg.direction;
|
|
1273
1364
|
case "pack":
|
|
1274
1365
|
return "pack";
|
|
1275
1366
|
}
|
|
@@ -1298,9 +1389,12 @@ function lowerRule(ctx, rule) {
|
|
|
1298
1389
|
negated.push(lowerAtom(ctx, item.atom, ids));
|
|
1299
1390
|
break;
|
|
1300
1391
|
}
|
|
1301
|
-
case "
|
|
1302
|
-
|
|
1303
|
-
|
|
1392
|
+
case "interior": {
|
|
1393
|
+
atoms.push(lowerInteriorAtom(ctx, item.target, item.bindings, ids));
|
|
1394
|
+
break;
|
|
1395
|
+
}
|
|
1396
|
+
case "negatedInterior": {
|
|
1397
|
+
negated.push(lowerInteriorAtom(ctx, item.target, item.bindings, ids));
|
|
1304
1398
|
break;
|
|
1305
1399
|
}
|
|
1306
1400
|
case "cond": {
|
|
@@ -1319,9 +1413,9 @@ function lowerRule(ctx, rule) {
|
|
|
1319
1413
|
};
|
|
1320
1414
|
}
|
|
1321
1415
|
/**
|
|
1322
|
-
* Lowers a query value to the bridge's `
|
|
1323
|
-
*
|
|
1324
|
-
*
|
|
1416
|
+
* Lowers a query value to the bridge's `QueryIr` — pure and stable:
|
|
1417
|
+
* interiors in declaration order, then CQ or Reach, then main. Every
|
|
1418
|
+
* registered param must carry a field anchor by now.
|
|
1325
1419
|
*/
|
|
1326
1420
|
function lowerQuery(q) {
|
|
1327
1421
|
const theory = q.schema;
|
|
@@ -1329,39 +1423,53 @@ function lowerQuery(q) {
|
|
|
1329
1423
|
Object.keys(theory.relations).forEach(function assignOrdinal(name, index) {
|
|
1330
1424
|
relationIds.set(name, index);
|
|
1331
1425
|
});
|
|
1332
|
-
const
|
|
1333
|
-
q.data.
|
|
1334
|
-
|
|
1426
|
+
const interiorIds = new Map();
|
|
1427
|
+
q.data.interiors.forEach(function assignInteriorId(interior, index) {
|
|
1428
|
+
interiorIds.set(interior.name, index);
|
|
1335
1429
|
});
|
|
1430
|
+
if (q.data.kind === "reach") {
|
|
1431
|
+
interiorIds.set(q.data.rec.name, q.data.interiors.length);
|
|
1432
|
+
}
|
|
1336
1433
|
const paramIds = new Map();
|
|
1337
1434
|
const params = new Map();
|
|
1338
1435
|
q.data.params.forEach(function assignParamId(entry, index) {
|
|
1339
|
-
if (entry.anchor === undefined
|
|
1436
|
+
if (entry.anchor === undefined) {
|
|
1340
1437
|
throw errors.new(`query param ${entry.name} has no field-anchored use — bind it in an atom or compare it against a bound variable`);
|
|
1341
1438
|
}
|
|
1342
1439
|
paramIds.set(entry.name, index);
|
|
1343
1440
|
params.set(entry.name, entry);
|
|
1344
1441
|
});
|
|
1345
|
-
const ctx = { theory, relationIds,
|
|
1346
|
-
const
|
|
1347
|
-
const head = rec.rules[0];
|
|
1348
|
-
if (head === undefined) {
|
|
1349
|
-
throw errors.new(`query lowering: rec ${rec.name} has no rules`);
|
|
1350
|
-
}
|
|
1442
|
+
const ctx = { theory, relationIds, interiorIds, paramIds, params };
|
|
1443
|
+
const interiors = q.data.interiors.map(function lowerInterior(interior) {
|
|
1351
1444
|
return {
|
|
1352
|
-
head:
|
|
1353
|
-
rules:
|
|
1445
|
+
head: interior.finds.map(headTermOf),
|
|
1446
|
+
rules: interior.rules.map(function lowerInteriorRule(rule) {
|
|
1354
1447
|
return lowerRule(ctx, rule);
|
|
1355
1448
|
})
|
|
1356
1449
|
};
|
|
1357
1450
|
});
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1451
|
+
const head = q.data.finds.map(headTermOf);
|
|
1452
|
+
const rules = q.data.rules.map(function lowerMainRule(rule) {
|
|
1453
|
+
return lowerRule(ctx, rule);
|
|
1454
|
+
});
|
|
1455
|
+
if (q.data.kind === "cq") {
|
|
1456
|
+
return parseQueryIr({ kind: "cq", interiors, head, rules });
|
|
1457
|
+
}
|
|
1458
|
+
return parseQueryIr({
|
|
1459
|
+
kind: "reach",
|
|
1460
|
+
interiors,
|
|
1461
|
+
rec: {
|
|
1462
|
+
head: q.data.rec.finds.map(headTermOf),
|
|
1463
|
+
base: q.data.rec.base.map(function lowerBase(rule) {
|
|
1464
|
+
return lowerRule(ctx, rule);
|
|
1465
|
+
}),
|
|
1466
|
+
rec: q.data.rec.rec.map(function lowerRecArm(rule) {
|
|
1467
|
+
return lowerRule(ctx, rule);
|
|
1468
|
+
})
|
|
1469
|
+
},
|
|
1470
|
+
head,
|
|
1471
|
+
rules
|
|
1363
1472
|
});
|
|
1364
|
-
return { predicates, output: q.data.recs.length };
|
|
1365
1473
|
}
|
|
1366
|
-
export { lowerQuery,
|
|
1474
|
+
export { lowerQuery, makeRawScope, query, taggedCmpLiteral, taggedLiteral };
|
|
1367
1475
|
//# sourceMappingURL=lower.js.map
|