@bjornpagen/bumbledb 0.3.0 → 0.5.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 +246 -92
- package/README.md +31 -16
- package/dist/closed.d.ts +80 -75
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +102 -127
- package/dist/closed.js.map +1 -1
- package/dist/db.d.ts +37 -7
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +95 -55
- package/dist/db.js.map +1 -1
- package/dist/exhume.d.ts.map +1 -1
- package/dist/exhume.js +1 -14
- package/dist/exhume.js.map +1 -1
- package/dist/face.d.ts +40 -40
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +9 -17
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +46 -15
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +58 -29
- package/dist/fields.js.map +1 -1
- package/dist/index.d.ts +13 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -5
- package/dist/index.js.map +1 -1
- package/dist/law.d.ts +2 -1
- package/dist/law.d.ts.map +1 -1
- package/dist/law.js +15 -14
- package/dist/law.js.map +1 -1
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +1 -7
- package/dist/lower.js.map +1 -1
- package/dist/marshal.d.ts +33 -6
- package/dist/marshal.d.ts.map +1 -1
- package/dist/marshal.js +75 -26
- package/dist/marshal.js.map +1 -1
- package/dist/native.d.ts +21 -2
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +20 -3
- package/dist/native.js.map +1 -1
- package/dist/order.d.ts +36 -0
- package/dist/order.d.ts.map +1 -0
- package/dist/order.js +135 -0
- package/dist/order.js.map +1 -0
- package/dist/query/atom.d.ts +76 -28
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +12 -16
- package/dist/query/atom.js.map +1 -1
- package/dist/query/lower.d.ts +5 -8
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +308 -72
- package/dist/query/lower.js.map +1 -1
- package/dist/query/predicate.d.ts.map +1 -1
- package/dist/query/predicate.js +34 -2
- package/dist/query/predicate.js.map +1 -1
- package/dist/query/run.d.ts +15 -5
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +27 -8
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +37 -29
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +19 -47
- package/dist/query/scope.js.map +1 -1
- package/dist/relation.d.ts +17 -29
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +41 -38
- package/dist/relation.js.map +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +7 -31
- package/dist/schema.js.map +1 -1
- package/dist/spec.d.ts +3 -2
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js.map +1 -1
- package/dist/statements.d.ts +13 -4
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +75 -8
- package/dist/statements.js.map +1 -1
- package/package.json +2 -5
- package/src/closed.ts +144 -206
- package/src/db.ts +143 -68
- package/src/exhume.ts +1 -15
- package/src/face.ts +38 -48
- package/src/fields.ts +103 -49
- package/src/index.ts +11 -10
- package/src/law.ts +15 -14
- package/src/lower.ts +2 -9
- package/src/marshal.ts +82 -31
- package/src/native.ts +22 -4
- package/src/order.ts +156 -0
- package/src/query/atom.ts +70 -35
- package/src/query/lower.ts +354 -82
- package/src/query/predicate.ts +39 -4
- package/src/query/run.ts +27 -9
- package/src/query/scope.ts +52 -70
- package/src/relation.ts +54 -68
- package/src/schema.ts +7 -33
- package/src/spec.ts +3 -2
- package/src/statements.ts +82 -8
package/dist/query/lower.js
CHANGED
|
@@ -25,14 +25,14 @@
|
|
|
25
25
|
* pre-checked here.
|
|
26
26
|
*/
|
|
27
27
|
import * as errors from "@superbuilders/errors";
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
28
|
+
import { sealedFieldsOf } from "#closed.ts";
|
|
29
|
+
import { assertDeclarationOrderKey, isIntervalValue, literalShapeError, rosterOf } from "#fields.ts";
|
|
30
|
+
import { allen, and, eq, ge, gt, le, lt, ne, not, or, pointIn } from "#query/atom.ts";
|
|
31
|
+
import { fieldJoins, inferred, isTerm, makeDuration, makeMaskParam, makeParam, makeSetParam, makeVar, renderFieldKind, term } from "#query/scope.ts";
|
|
31
32
|
import { argMax, argMin, count, countDistinct, max, min, pack, sum } from "#query/select.ts";
|
|
32
33
|
/** The frozen constructor vocabulary every rule builder spreads. */
|
|
33
34
|
const termOps = Object.freeze({
|
|
34
35
|
var: makeVar,
|
|
35
|
-
vars: makeVars,
|
|
36
36
|
param: makeParam,
|
|
37
37
|
inSet: makeSetParam,
|
|
38
38
|
maskParam: makeMaskParam,
|
|
@@ -44,7 +44,6 @@ const termOps = Object.freeze({
|
|
|
44
44
|
gt,
|
|
45
45
|
ge,
|
|
46
46
|
pointIn,
|
|
47
|
-
covers,
|
|
48
47
|
allen,
|
|
49
48
|
and,
|
|
50
49
|
or,
|
|
@@ -65,19 +64,44 @@ const EMPTY_RULE = Object.freeze({
|
|
|
65
64
|
paramUses: Object.freeze([])
|
|
66
65
|
});
|
|
67
66
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
67
|
+
* Judges one membership ARRAY at a binding position — legal exactly at a
|
|
68
|
+
* CLOSED-reference field (the owner ruling: ordinary u64/str membership is
|
|
69
|
+
* spelled through `r.inSet` params; literal arrays are the closed
|
|
70
|
+
* vocabulary's spelling), holding ≥ 2 DISTINCT handle names (the
|
|
71
|
+
* degenerate sets are refusals: empty selects nothing, one element is the
|
|
72
|
+
* bare literal respelled, and a duplicate member is the same respelling in
|
|
73
|
+
* disguise — write each member once). The returned name is
|
|
74
|
+
* CONTENT-ADDRESSED (vocabulary + the member SET — the key sorts a copy,
|
|
75
|
+
* so two spellings of one set, reordered or not, share one dense
|
|
76
|
+
* `ParamId`); the members are shape-checked strings here and
|
|
77
|
+
* roster-verified at the one verification point (`taggedHandleId`) when
|
|
78
|
+
* the SDK supplies the set at execute — the same moment a bound `r.inSet`
|
|
79
|
+
* param's members are judged.
|
|
75
80
|
*/
|
|
76
|
-
function
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
function membershipSet(context, field, value) {
|
|
82
|
+
const roster = rosterOf(field);
|
|
83
|
+
if (roster === undefined) {
|
|
84
|
+
throw errors.new(`${context}: a membership array is the closed-reference spelling — ordinary field membership is a bound ∈-set param (r.inSet)`);
|
|
79
85
|
}
|
|
80
|
-
|
|
86
|
+
if (value.length === 0) {
|
|
87
|
+
throw errors.new(`${context}: an empty membership array selects nothing — write the query you mean`);
|
|
88
|
+
}
|
|
89
|
+
if (value.length === 1) {
|
|
90
|
+
throw errors.new(`${context}: a one-element membership array is the bare literal respelled — write the literal (the canonical-utterance law: one meaning, one spelling)`);
|
|
91
|
+
}
|
|
92
|
+
const seen = new Set();
|
|
93
|
+
const members = value.map(function memberName(member) {
|
|
94
|
+
if (typeof member !== "string") {
|
|
95
|
+
throw literalShapeError(context, `a ${roster.name} handle name (string)`, member);
|
|
96
|
+
}
|
|
97
|
+
if (seen.has(member)) {
|
|
98
|
+
throw errors.new(`${context}: the membership array spells ${member} twice — write it once (the canonical-utterance law: one meaning, one spelling)`);
|
|
99
|
+
}
|
|
100
|
+
seen.add(member);
|
|
101
|
+
return member;
|
|
102
|
+
});
|
|
103
|
+
const key = [...members].sort();
|
|
104
|
+
return { name: `∈ ${roster.name} ${JSON.stringify(key)}`, members: Object.freeze(members) };
|
|
81
105
|
}
|
|
82
106
|
/**
|
|
83
107
|
* Resolves a bindings record against an atom owner's matchable fields (a
|
|
@@ -93,7 +117,7 @@ function resolveBindings(context, relation, bindings, classes) {
|
|
|
93
117
|
const vars = [];
|
|
94
118
|
const uses = [];
|
|
95
119
|
const relationClasses = classes[relation.name];
|
|
96
|
-
const ordered =
|
|
120
|
+
const ordered = sealedFieldsOf(relation);
|
|
97
121
|
for (const [fieldName, value] of Object.entries(bindings)) {
|
|
98
122
|
if (value === undefined) {
|
|
99
123
|
continue;
|
|
@@ -115,12 +139,24 @@ function resolveBindings(context, relation, bindings, classes) {
|
|
|
115
139
|
}
|
|
116
140
|
case "param": {
|
|
117
141
|
bound = Object.freeze({ kind: "param", name: value.name });
|
|
118
|
-
uses.push(Object.freeze({
|
|
142
|
+
uses.push(Object.freeze({
|
|
143
|
+
name: value.name,
|
|
144
|
+
shape: "value",
|
|
145
|
+
anchor: declared.field,
|
|
146
|
+
op: "binding",
|
|
147
|
+
members: undefined
|
|
148
|
+
}));
|
|
119
149
|
break;
|
|
120
150
|
}
|
|
121
151
|
case "setParam": {
|
|
122
152
|
bound = Object.freeze({ kind: "setParam", name: value.name });
|
|
123
|
-
uses.push(Object.freeze({
|
|
153
|
+
uses.push(Object.freeze({
|
|
154
|
+
name: value.name,
|
|
155
|
+
shape: "set",
|
|
156
|
+
anchor: declared.field,
|
|
157
|
+
op: "binding",
|
|
158
|
+
members: undefined
|
|
159
|
+
}));
|
|
124
160
|
break;
|
|
125
161
|
}
|
|
126
162
|
case "maskParam":
|
|
@@ -129,6 +165,17 @@ function resolveBindings(context, relation, bindings, classes) {
|
|
|
129
165
|
throw errors.new(`${context}.${fieldName}: the measure is not a field-typed value — it lives in comparisons and select entries`);
|
|
130
166
|
}
|
|
131
167
|
}
|
|
168
|
+
else if (Array.isArray(value)) {
|
|
169
|
+
const set = membershipSet(`${context}.${fieldName}`, declared.field, value);
|
|
170
|
+
bound = Object.freeze({ kind: "literalSet", name: set.name, members: set.members });
|
|
171
|
+
uses.push(Object.freeze({
|
|
172
|
+
name: set.name,
|
|
173
|
+
shape: "set",
|
|
174
|
+
anchor: declared.field,
|
|
175
|
+
op: "binding",
|
|
176
|
+
members: set.members
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
132
179
|
else {
|
|
133
180
|
bound = Object.freeze({ kind: "literal", value });
|
|
134
181
|
}
|
|
@@ -207,7 +254,8 @@ function sideUses(op, side, sibling, varFields, uses) {
|
|
|
207
254
|
name: side.name,
|
|
208
255
|
shape: side.kind === "param" ? "value" : "set",
|
|
209
256
|
anchor,
|
|
210
|
-
op
|
|
257
|
+
op,
|
|
258
|
+
members: undefined
|
|
211
259
|
}));
|
|
212
260
|
}
|
|
213
261
|
/** Lowers one condition VALUE to its runtime data, recording param uses. */
|
|
@@ -225,7 +273,13 @@ function condDataOf(cond, varFields, uses) {
|
|
|
225
273
|
}
|
|
226
274
|
else if (isTerm(maskValue) && maskValue[term] === "maskParam") {
|
|
227
275
|
mask = Object.freeze({ kind: "param", name: maskValue.name });
|
|
228
|
-
uses.push(Object.freeze({
|
|
276
|
+
uses.push(Object.freeze({
|
|
277
|
+
name: maskValue.name,
|
|
278
|
+
shape: "mask",
|
|
279
|
+
anchor: undefined,
|
|
280
|
+
op: "allen",
|
|
281
|
+
members: undefined
|
|
282
|
+
}));
|
|
229
283
|
}
|
|
230
284
|
else {
|
|
231
285
|
throw errors.new("allen: the mask position takes a 13-bit mask number or a maskParam");
|
|
@@ -286,14 +340,27 @@ function advanceIdb(state, rec, vars) {
|
|
|
286
340
|
function isAggregateEntry(value) {
|
|
287
341
|
return typeof value === "object" && value !== null && "agg" in value;
|
|
288
342
|
}
|
|
289
|
-
/**
|
|
343
|
+
/**
|
|
344
|
+
* Classifies one select entry into its named answer column. The `closed`
|
|
345
|
+
* slice is resolved LATER, at rule completion (`completeRule`), where the
|
|
346
|
+
* rule's `varFields` are in hand — until then every column is provisionally
|
|
347
|
+
* bare.
|
|
348
|
+
*/
|
|
290
349
|
function selectColumnOf(entry) {
|
|
291
350
|
if (typeof entry === "string") {
|
|
292
|
-
return Object.freeze({
|
|
351
|
+
return Object.freeze({
|
|
352
|
+
name: entry,
|
|
353
|
+
entry: Object.freeze({ kind: "var", over: entry }),
|
|
354
|
+
closed: undefined
|
|
355
|
+
});
|
|
293
356
|
}
|
|
294
357
|
if (isTerm(entry)) {
|
|
295
358
|
if (entry[term] === "duration") {
|
|
296
|
-
return Object.freeze({
|
|
359
|
+
return Object.freeze({
|
|
360
|
+
name: entry.name,
|
|
361
|
+
entry: Object.freeze({ kind: "measure", over: entry.name }),
|
|
362
|
+
closed: undefined
|
|
363
|
+
});
|
|
297
364
|
}
|
|
298
365
|
throw errors.new(`query select: a ${entry[term]} is not projectable — select takes variable names, duration(v), or aggregates`);
|
|
299
366
|
}
|
|
@@ -305,7 +372,11 @@ function selectColumnOf(entry) {
|
|
|
305
372
|
/** Classifies one aggregate select entry. */
|
|
306
373
|
function aggregateColumnOf(entry) {
|
|
307
374
|
function column(name, agg) {
|
|
308
|
-
return Object.freeze({
|
|
375
|
+
return Object.freeze({
|
|
376
|
+
name,
|
|
377
|
+
entry: Object.freeze({ kind: "aggregate", agg: Object.freeze(agg) }),
|
|
378
|
+
closed: undefined
|
|
379
|
+
});
|
|
309
380
|
}
|
|
310
381
|
const over = entry.over;
|
|
311
382
|
switch (entry.agg) {
|
|
@@ -345,6 +416,22 @@ function aggregateColumnOf(entry) {
|
|
|
345
416
|
throw errors.new(`unknown aggregate ${entry.agg}`);
|
|
346
417
|
}
|
|
347
418
|
}
|
|
419
|
+
/**
|
|
420
|
+
* The orderable ban's pointed refusal (`docs/architecture/10-data-model.md`
|
|
421
|
+
* § orderability): a closed reference is equality-and-membership only —
|
|
422
|
+
* its declaration-id order is an encoding accident, so every
|
|
423
|
+
* order-comparison and fold position refuses it. The construction-time
|
|
424
|
+
* twin of the type tier's `OrderVarOk` exclusion, so the wall holds for
|
|
425
|
+
* untyped callers too (the engine cannot backstop this one: the wire IR
|
|
426
|
+
* carries plain u64s, no rosters).
|
|
427
|
+
*/
|
|
428
|
+
function closedOrderError(context, position, vocabulary) {
|
|
429
|
+
return errors.new(`${context}: ${position} is a ${vocabulary} reference — declaration order is an accident, not semantics: vocabularies do not order (docs/architecture/10-data-model.md; equality, membership, and counting remain)`);
|
|
430
|
+
}
|
|
431
|
+
/** The comparison ops under the orderable ban (order roster + point membership — every order-comparison position). */
|
|
432
|
+
function isOrderOp(op) {
|
|
433
|
+
return op === "lt" || op === "le" || op === "gt" || op === "ge" || op === "pointIn";
|
|
434
|
+
}
|
|
348
435
|
/** Requires a var name to be bound by a relation atom of the rule. */
|
|
349
436
|
function assertBound(context, varFields, name) {
|
|
350
437
|
const slot = varFields[name];
|
|
@@ -373,7 +460,11 @@ function validateCond(context, varFields, cond) {
|
|
|
373
460
|
if (cond.kind === "cmp") {
|
|
374
461
|
for (const side of [cond.lhs, cond.rhs]) {
|
|
375
462
|
if (side.kind === "var") {
|
|
376
|
-
assertBound(context, varFields, side.name);
|
|
463
|
+
const slot = assertBound(context, varFields, side.name);
|
|
464
|
+
const roster = rosterOf(slot.field);
|
|
465
|
+
if (isOrderOp(cond.op) && roster !== undefined) {
|
|
466
|
+
throw closedOrderError(context, `the ${cond.op} side ${side.name}`, roster.name);
|
|
467
|
+
}
|
|
377
468
|
}
|
|
378
469
|
if (side.kind === "measure") {
|
|
379
470
|
assertIntervalBound(context, varFields, side.name);
|
|
@@ -412,21 +503,56 @@ function validateColumn(context, varFields, column) {
|
|
|
412
503
|
return;
|
|
413
504
|
case "fold": {
|
|
414
505
|
if (typeof agg.over === "string") {
|
|
415
|
-
assertBound(`${context} select ${column.name}`, varFields, agg.over);
|
|
506
|
+
const slot = assertBound(`${context} select ${column.name}`, varFields, agg.over);
|
|
507
|
+
const roster = rosterOf(slot.field);
|
|
508
|
+
if (roster !== undefined) {
|
|
509
|
+
throw closedOrderError(`${context} select ${column.name}`, `the ${agg.fold} input ${agg.over}`, roster.name);
|
|
510
|
+
}
|
|
416
511
|
return;
|
|
417
512
|
}
|
|
418
513
|
assertIntervalBound(`${context} select ${column.name}`, varFields, agg.over.duration);
|
|
419
514
|
return;
|
|
420
515
|
}
|
|
421
|
-
case "arg":
|
|
516
|
+
case "arg": {
|
|
422
517
|
assertBound(`${context} select ${column.name}`, varFields, agg.over);
|
|
423
|
-
assertBound(`${context} select ${column.name}`, varFields, agg.key);
|
|
518
|
+
const key = assertBound(`${context} select ${column.name}`, varFields, agg.key);
|
|
519
|
+
const keyRoster = rosterOf(key.field);
|
|
520
|
+
if (keyRoster !== undefined) {
|
|
521
|
+
throw closedOrderError(`${context} select ${column.name}`, `the ${agg.direction} key ${agg.key}`, keyRoster.name);
|
|
522
|
+
}
|
|
424
523
|
return;
|
|
524
|
+
}
|
|
425
525
|
case "pack":
|
|
426
526
|
assertIntervalBound(`${context} select ${column.name}`, varFields, agg.over);
|
|
427
527
|
return;
|
|
428
528
|
}
|
|
429
529
|
}
|
|
530
|
+
/**
|
|
531
|
+
* Resolves the roster one select column decodes through: a projected var,
|
|
532
|
+
* or an Arg-carried payload, bound at a closed-referencing field carries
|
|
533
|
+
* that field's roster (read off `varFields` — the same slot the domain
|
|
534
|
+
* machinery reads), and `decodeAnswers` lifts the column's row ids back to
|
|
535
|
+
* handle NAMES through it — the runtime twin of the row type's `Infer`
|
|
536
|
+
* claim. Every other entry decodes bare: counts are counts, the measure
|
|
537
|
+
* and `pack` are never closed, and a closed FOLD is banned outright
|
|
538
|
+
* ({@link closedOrderError}) before this resolution runs.
|
|
539
|
+
*/
|
|
540
|
+
function selectClosedOf(varFields, entry) {
|
|
541
|
+
let over;
|
|
542
|
+
if (entry.kind === "var") {
|
|
543
|
+
over = entry.over;
|
|
544
|
+
}
|
|
545
|
+
else if (entry.kind === "aggregate" && entry.agg.op === "arg") {
|
|
546
|
+
over = entry.agg.over;
|
|
547
|
+
}
|
|
548
|
+
else {
|
|
549
|
+
over = undefined;
|
|
550
|
+
}
|
|
551
|
+
if (over === undefined) {
|
|
552
|
+
return undefined;
|
|
553
|
+
}
|
|
554
|
+
return rosterOf(varFields[over]?.field);
|
|
555
|
+
}
|
|
430
556
|
/**
|
|
431
557
|
* Completes one rule: classifies the select record (written order = answer
|
|
432
558
|
* column order, names must be declaration-order-safe keys), and validates
|
|
@@ -485,7 +611,13 @@ function completeRule(context, state, columns) {
|
|
|
485
611
|
}
|
|
486
612
|
return Object.freeze({
|
|
487
613
|
items: state.items,
|
|
488
|
-
select: Object.freeze(
|
|
614
|
+
select: Object.freeze(columns.map(function enrichColumn(column) {
|
|
615
|
+
return Object.freeze({
|
|
616
|
+
name: column.name,
|
|
617
|
+
entry: column.entry,
|
|
618
|
+
closed: selectClosedOf(state.varFields, column.entry)
|
|
619
|
+
});
|
|
620
|
+
})),
|
|
489
621
|
varFields: state.varFields,
|
|
490
622
|
paramUses: state.paramUses
|
|
491
623
|
});
|
|
@@ -561,10 +693,12 @@ function makeRawScope(context) {
|
|
|
561
693
|
return scope;
|
|
562
694
|
}
|
|
563
695
|
/**
|
|
564
|
-
* The
|
|
565
|
-
*
|
|
566
|
-
*
|
|
567
|
-
*
|
|
696
|
+
* The rule builders' trusted admission seam — THE home of the
|
|
697
|
+
* trusted-admission-seam pattern the other mint guards cite (the face,
|
|
698
|
+
* class-map, axiom-readback, rec-handle, and query-value seams): the raw
|
|
699
|
+
* builder is one runtime shape for every context, and this guard verifies
|
|
700
|
+
* the checkable fact — the builder verbs exist — before the value is
|
|
701
|
+
* admitted at its TYPED face. The type-level
|
|
568
702
|
* judgments (domain-equal joins, boundness, the recursion cut) live in the
|
|
569
703
|
* interfaces themselves; the runtime twin of every one of them is a
|
|
570
704
|
* construction-time validation in this module.
|
|
@@ -588,6 +722,10 @@ function makeOutputRuleScope(program) {
|
|
|
588
722
|
}
|
|
589
723
|
return raw;
|
|
590
724
|
}
|
|
725
|
+
/** Renders one head column's closed slice for the rule-alignment check's diagnostics. */
|
|
726
|
+
function renderClosedSlice(closed) {
|
|
727
|
+
return closed === undefined ? "a bare value" : `a ${closed.name} reference`;
|
|
728
|
+
}
|
|
591
729
|
/** Renders one head column's signature for the rule-alignment check. */
|
|
592
730
|
function headSignature(column) {
|
|
593
731
|
const entry = column.entry;
|
|
@@ -603,11 +741,48 @@ function headSignature(column) {
|
|
|
603
741
|
}
|
|
604
742
|
return `${column.name}:${agg.op}`;
|
|
605
743
|
}
|
|
744
|
+
/**
|
|
745
|
+
* The classed slot one answer column's VALUES flow from, resolved through
|
|
746
|
+
* the rule's own binding environment: a projected var's first-binding slot,
|
|
747
|
+
* or an Arg-carried payload's (`argMax`/`argMin` carry `over` verbatim —
|
|
748
|
+
* the same two shapes the closed slice lifts). Counts, folds, `pack` and
|
|
749
|
+
* the measure derive numbers/intervals rather than carrying a slot's ids,
|
|
750
|
+
* so they resolve no slot (`undefined`).
|
|
751
|
+
*/
|
|
752
|
+
function headSlotOf(rule, column) {
|
|
753
|
+
const entry = column.entry;
|
|
754
|
+
if (entry.kind === "var") {
|
|
755
|
+
return rule.varFields[entry.over];
|
|
756
|
+
}
|
|
757
|
+
if (entry.kind === "aggregate" && entry.agg.op === "arg") {
|
|
758
|
+
return rule.varFields[entry.agg.over];
|
|
759
|
+
}
|
|
760
|
+
return undefined;
|
|
761
|
+
}
|
|
762
|
+
/** The roster a param anchor carries: present exactly on a closed-reference field anchor (rides THE one `rosterOf` reader). */
|
|
763
|
+
function anchorRosterOf(anchor) {
|
|
764
|
+
return anchor === "measure" ? undefined : rosterOf(anchor);
|
|
765
|
+
}
|
|
766
|
+
/** Renders one param anchor's closedness for the registry's coherence diagnostics. */
|
|
767
|
+
function renderParamAnchor(roster) {
|
|
768
|
+
return roster === undefined ? "a non-closed position" : `a ${roster.name} reference`;
|
|
769
|
+
}
|
|
606
770
|
/**
|
|
607
771
|
* Folds every rule's param uses (recs in declaration order first, output
|
|
608
772
|
* rules last — exactly the lowering walk) into the query's registry:
|
|
609
773
|
* first use mints the dense `ParamId`, the first FIELD-ANCHORED use types
|
|
610
|
-
* the wire, and one name must keep one shape
|
|
774
|
+
* the wire, and one name must keep one shape AND one closedness — every
|
|
775
|
+
* anchored use of one name must agree on the roster (value identity), so a
|
|
776
|
+
* param anchored at a closed reference is GUARANTEED to ride the one
|
|
777
|
+
* roster-verification point (`taggedHandleId`) at execute; a name anchored
|
|
778
|
+
* both at a closed reference and at a non-closed position (or at two
|
|
779
|
+
* vocabularies) is refused here, because the wire would translate only the
|
|
780
|
+
* first anchor's reading (the type tier intersects the uses to `never`;
|
|
781
|
+
* this is its runtime twin for untyped callers). A param whose anchor is a
|
|
782
|
+
* CLOSED reference must never sit in an order-comparison position — the
|
|
783
|
+
* anchor types its value a handle name and the engine would order the
|
|
784
|
+
* translated row ids, so the pairing is refused here too (the registry is
|
|
785
|
+
* the one place a name's every use and its anchoring field meet).
|
|
611
786
|
*/
|
|
612
787
|
function paramRegistryOf(recs, rules) {
|
|
613
788
|
const order = [];
|
|
@@ -617,16 +792,35 @@ function paramRegistryOf(recs, rules) {
|
|
|
617
792
|
const existing = byName.get(use.name);
|
|
618
793
|
if (existing === undefined) {
|
|
619
794
|
order.push(use.name);
|
|
620
|
-
byName.set(use.name, {
|
|
795
|
+
byName.set(use.name, {
|
|
796
|
+
shape: use.shape,
|
|
797
|
+
anchor: use.anchor,
|
|
798
|
+
op: use.op,
|
|
799
|
+
members: use.members,
|
|
800
|
+
orderOp: isOrderOp(use.op) ? use.op : undefined
|
|
801
|
+
});
|
|
621
802
|
continue;
|
|
622
803
|
}
|
|
804
|
+
if ((existing.members === undefined) !== (use.members === undefined)) {
|
|
805
|
+
throw errors.new(`query param ${use.name} collides with a membership array's registry entry — name the param differently`);
|
|
806
|
+
}
|
|
623
807
|
if (existing.shape !== use.shape) {
|
|
624
808
|
throw errors.new(`query param ${use.name} is used both as a ${existing.shape} param and a ${use.shape} param — one name, one shape`);
|
|
625
809
|
}
|
|
810
|
+
if (existing.anchor !== undefined && use.anchor !== undefined) {
|
|
811
|
+
const registered = anchorRosterOf(existing.anchor);
|
|
812
|
+
const anchored = anchorRosterOf(use.anchor);
|
|
813
|
+
if (registered !== anchored) {
|
|
814
|
+
throw errors.new(`query param ${use.name} is anchored at ${renderParamAnchor(registered)} and at ${renderParamAnchor(anchored)} — a closed-anchored param translates handle names through ONE roster (one name, one domain); name the params differently`);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
626
817
|
if (existing.anchor === undefined && use.anchor !== undefined) {
|
|
627
818
|
existing.anchor = use.anchor;
|
|
628
819
|
existing.op = use.op;
|
|
629
820
|
}
|
|
821
|
+
if (existing.orderOp === undefined && isOrderOp(use.op)) {
|
|
822
|
+
existing.orderOp = use.op;
|
|
823
|
+
}
|
|
630
824
|
}
|
|
631
825
|
}
|
|
632
826
|
for (const rec of recs) {
|
|
@@ -642,7 +836,11 @@ function paramRegistryOf(recs, rules) {
|
|
|
642
836
|
if (entry === undefined) {
|
|
643
837
|
throw errors.new(`query param ${name} lost its registry entry`);
|
|
644
838
|
}
|
|
645
|
-
|
|
839
|
+
const anchorRoster = anchorRosterOf(entry.anchor);
|
|
840
|
+
if (entry.orderOp !== undefined && anchorRoster !== undefined) {
|
|
841
|
+
throw closedOrderError(`query param ${name}`, `its ${entry.orderOp} use's anchor`, anchorRoster.name);
|
|
842
|
+
}
|
|
843
|
+
return Object.freeze({ name, shape: entry.shape, anchor: entry.anchor, op: entry.op, members: entry.members });
|
|
646
844
|
}));
|
|
647
845
|
}
|
|
648
846
|
/**
|
|
@@ -662,6 +860,32 @@ function makeRawQuery(theory, recs, rules) {
|
|
|
662
860
|
if (candidate !== signature) {
|
|
663
861
|
throw errors.new(`every rule of a query derives the same head — rule 0 selects (${signature}), rule ${index} selects (${candidate})`);
|
|
664
862
|
}
|
|
863
|
+
// The closed slice is part of the head too: one answer column decodes
|
|
864
|
+
// through one roster, so a union whose rules bind a column at
|
|
865
|
+
// different vocabularies (or one closed, one bare — the ids would
|
|
866
|
+
// mistranslate silently) is refused pointed. Vocabulary identity is
|
|
867
|
+
// value identity, the SDK's membership rule everywhere.
|
|
868
|
+
rule.select.forEach(function verifyClosedSlice(column, position) {
|
|
869
|
+
const lead = first.select[position];
|
|
870
|
+
if (lead !== undefined && column.closed !== lead.closed) {
|
|
871
|
+
throw errors.new(`every rule of a query derives the same head — the answer column ${lead.name} is ${renderClosedSlice(lead.closed)} in rule 0 but ${renderClosedSlice(column.closed)} in rule ${index} (one column decodes through one roster)`);
|
|
872
|
+
}
|
|
873
|
+
// The law-class wall on the union head: one answer column is one
|
|
874
|
+
// value space, so the classed slot each rule binds the column at
|
|
875
|
+
// must join across rules — the SAME fieldJoins judgment every
|
|
876
|
+
// join/eq/negated-atom position enforces. The SDK holds this wall
|
|
877
|
+
// because the wire IR carries no domains: the engine cannot
|
|
878
|
+
// backstop it, and without it a union mixes (say) Holder ids and
|
|
879
|
+
// Account ids in one column the consumer reads as one id space.
|
|
880
|
+
if (lead === undefined) {
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
const leadSlot = headSlotOf(first, lead);
|
|
884
|
+
const slot = headSlotOf(rule, column);
|
|
885
|
+
if (leadSlot !== undefined && slot !== undefined && !fieldJoins(leadSlot, slot)) {
|
|
886
|
+
throw errors.new(`every rule of a query derives the same head — the answer column ${lead.name} unions domain-unequal fields: bound at ${renderFieldKind(leadSlot)} in rule 0 but at ${renderFieldKind(slot)} in rule ${index} (a union column joins only class-equal slots; bare pairs only with bare)`);
|
|
887
|
+
}
|
|
888
|
+
});
|
|
665
889
|
});
|
|
666
890
|
const data = Object.freeze({
|
|
667
891
|
recs: Object.freeze([...recs]),
|
|
@@ -681,7 +905,7 @@ function makeRawQuery(theory, recs, rules) {
|
|
|
681
905
|
return value;
|
|
682
906
|
}
|
|
683
907
|
/**
|
|
684
|
-
* The query values' trusted admission seam (the
|
|
908
|
+
* The query values' trusted admission seam (the {@link isTypedScope} pattern):
|
|
685
909
|
* the checkable fact — the value was assembled over the identical theory —
|
|
686
910
|
* is verified before the raw value is admitted at its typed face.
|
|
687
911
|
*/
|
|
@@ -713,33 +937,25 @@ function query(theory) {
|
|
|
713
937
|
Object.freeze(start);
|
|
714
938
|
return start;
|
|
715
939
|
}
|
|
716
|
-
/** The typed shape refusal of the literal tagger — a genuine failure, never data. */
|
|
717
|
-
function literalShapeError(context, expected, value) {
|
|
718
|
-
return errors.new(`${context}: expected ${expected}, got ${typeof value}`);
|
|
719
|
-
}
|
|
720
|
-
/** Narrows an interval-shaped literal (a plain `{ start, end }` bigint pair). */
|
|
721
|
-
function isIntervalShaped(value) {
|
|
722
|
-
return (typeof value === "object" &&
|
|
723
|
-
value !== null &&
|
|
724
|
-
"start" in value &&
|
|
725
|
-
"end" in value &&
|
|
726
|
-
typeof value.start === "bigint" &&
|
|
727
|
-
typeof value.end === "bigint");
|
|
728
|
-
}
|
|
729
940
|
/**
|
|
730
|
-
* Tags one closed-reference literal: the
|
|
731
|
-
*
|
|
732
|
-
* make any
|
|
733
|
-
* never handle
|
|
941
|
+
* Tags one closed-reference literal: the handle NAME, verified against the
|
|
942
|
+
* roster (the belt the wide fallback type cannot provide — structural
|
|
943
|
+
* values make any string spellable here) and translated to its
|
|
944
|
+
* declaration-order row id, tagged u64 — queries cross ids, never handle
|
|
945
|
+
* names; the wire is untouched. THE single roster-verification point of
|
|
946
|
+
* the query surface: atom-binding literals, comparison literals,
|
|
947
|
+
* execute-time params, and membership-array members all reach it (never
|
|
948
|
+
* duplicate the check per call site).
|
|
734
949
|
*/
|
|
735
950
|
function taggedHandleId(context, closed, value) {
|
|
736
|
-
if (typeof value !== "
|
|
737
|
-
throw literalShapeError(context, `a ${closed.name} handle
|
|
951
|
+
if (typeof value !== "string") {
|
|
952
|
+
throw literalShapeError(context, `a ${closed.name} handle name (string)`, value);
|
|
738
953
|
}
|
|
739
|
-
|
|
740
|
-
|
|
954
|
+
const id = closed.handles.indexOf(value);
|
|
955
|
+
if (id < 0) {
|
|
956
|
+
throw errors.new(`${context}: "${value}" is not a handle of ${closed.name} — the roster is ${closed.handles.join(", ")}`);
|
|
741
957
|
}
|
|
742
|
-
return { kind: "u64", value };
|
|
958
|
+
return { kind: "u64", value: BigInt(id) };
|
|
743
959
|
}
|
|
744
960
|
/**
|
|
745
961
|
* Tags one literal in an interval element domain: a bigint tags as the
|
|
@@ -753,7 +969,7 @@ function taggedAtElementDomain(context, element, value) {
|
|
|
753
969
|
}
|
|
754
970
|
return { kind: "i64", value };
|
|
755
971
|
}
|
|
756
|
-
if (
|
|
972
|
+
if (isIntervalValue(value)) {
|
|
757
973
|
if (element === "u64") {
|
|
758
974
|
return { kind: "intervalU64", start: value.start, end: value.end };
|
|
759
975
|
}
|
|
@@ -770,8 +986,9 @@ function taggedAtElementDomain(context, element, value) {
|
|
|
770
986
|
* tagged u64 after a roster verification.
|
|
771
987
|
*/
|
|
772
988
|
function taggedLiteral(context, field, value) {
|
|
773
|
-
|
|
774
|
-
|
|
989
|
+
const roster = rosterOf(field);
|
|
990
|
+
if (roster !== undefined) {
|
|
991
|
+
return taggedHandleId(context, roster, value);
|
|
775
992
|
}
|
|
776
993
|
switch (field.kind) {
|
|
777
994
|
case "bool": {
|
|
@@ -796,6 +1013,18 @@ function taggedLiteral(context, field, value) {
|
|
|
796
1013
|
if (typeof value !== "string") {
|
|
797
1014
|
throw literalShapeError(context, "string", value);
|
|
798
1015
|
}
|
|
1016
|
+
/**
|
|
1017
|
+
* The marshal's bijection law at the query seam (`marshal.ts`
|
|
1018
|
+
* cellOf): a lone surrogate would be lossily replaced with
|
|
1019
|
+
* U+FFFD at the bridge's UTF-8 crossing and silently match a
|
|
1020
|
+
* fact the typed write surface can never store — distinct JS
|
|
1021
|
+
* strings collapsing to one wire query. This is the single
|
|
1022
|
+
* seam every query string literal, string param
|
|
1023
|
+
* (`taggedCmpLiteral`), and membership member lowers through.
|
|
1024
|
+
*/
|
|
1025
|
+
if (!value.isWellFormed()) {
|
|
1026
|
+
throw literalShapeError(context, "well-formed string", value);
|
|
1027
|
+
}
|
|
799
1028
|
return { kind: "string", value };
|
|
800
1029
|
}
|
|
801
1030
|
case "bytes": {
|
|
@@ -812,11 +1041,11 @@ function taggedLiteral(context, field, value) {
|
|
|
812
1041
|
* Tags one host literal at a COMPARISON or PARAM position, where the
|
|
813
1042
|
* SIBLING anchors the type: a measure sibling is u64, an interval-field
|
|
814
1043
|
* sibling contributes its element domain (so both a point literal in
|
|
815
|
-
* `
|
|
1044
|
+
* `pointIn` and a `span` literal in `allen` tag correctly), a scalar
|
|
816
1045
|
* sibling its own type. At `pointIn` the operand order is interval-left,
|
|
817
1046
|
* point-right (`ir::CmpOp::PointIn`), so an interval-shaped literal
|
|
818
|
-
* beside a scalar element-typed sibling is the LEGAL
|
|
819
|
-
* `
|
|
1047
|
+
* beside a scalar element-typed sibling is the LEGAL interval operand of
|
|
1048
|
+
* `pointIn(t, span(...))` and tags as the interval of the sibling's
|
|
820
1049
|
* element domain; under every other operator an interval shape against a
|
|
821
1050
|
* scalar sibling stays refused (the engine's IllegalComparison — the
|
|
822
1051
|
* bug-hunt fix, preserved op-aware).
|
|
@@ -828,13 +1057,13 @@ function taggedCmpLiteral(context, sibling, value, op) {
|
|
|
828
1057
|
}
|
|
829
1058
|
return { kind: "u64", value };
|
|
830
1059
|
}
|
|
831
|
-
if (
|
|
1060
|
+
if (rosterOf(sibling) === undefined && sibling.kind === "interval") {
|
|
832
1061
|
return taggedAtElementDomain(context, sibling.element, value);
|
|
833
1062
|
}
|
|
834
1063
|
if (op === "pointIn" &&
|
|
835
|
-
|
|
1064
|
+
rosterOf(sibling) === undefined &&
|
|
836
1065
|
(sibling.kind === "u64" || sibling.kind === "i64") &&
|
|
837
|
-
|
|
1066
|
+
isIntervalValue(value)) {
|
|
838
1067
|
return taggedAtElementDomain(context, sibling.kind, value);
|
|
839
1068
|
}
|
|
840
1069
|
return taggedLiteral(context, sibling, value);
|
|
@@ -866,7 +1095,7 @@ function paramIdOf(ctx, name) {
|
|
|
866
1095
|
* Lowers one EDB atom (either polarity). A CLOSED owner lowers through the
|
|
867
1096
|
* same edb source — its ordinal is its record-declaration slot exactly like
|
|
868
1097
|
* an ordinary relation's — with field ordinals over the SEALED shape: `id`
|
|
869
|
-
* at 0, each payload column at its declared index + 1 (`
|
|
1098
|
+
* at 0, each payload column at its declared index + 1 (`sealedFieldsOf`
|
|
870
1099
|
* carries the shift; the lowering golden pins it).
|
|
871
1100
|
*/
|
|
872
1101
|
function lowerAtom(ctx, atom, ids) {
|
|
@@ -878,7 +1107,7 @@ function lowerAtom(ctx, atom, ids) {
|
|
|
878
1107
|
if (relationId === undefined) {
|
|
879
1108
|
throw errors.new(`query lowering: relation ${atom.relation.name} has no ordinal`);
|
|
880
1109
|
}
|
|
881
|
-
const ordered =
|
|
1110
|
+
const ordered = sealedFieldsOf(atom.relation);
|
|
882
1111
|
const bindings = atom.bindings.map(function lowerBinding(binding) {
|
|
883
1112
|
const ordinal = ordered.findIndex(function byName(candidate) {
|
|
884
1113
|
return candidate.name === binding.field;
|
|
@@ -890,7 +1119,12 @@ function lowerAtom(ctx, atom, ids) {
|
|
|
890
1119
|
});
|
|
891
1120
|
return { source: { kind: "edb", relation: relationId }, bindings };
|
|
892
1121
|
}
|
|
893
|
-
/**
|
|
1122
|
+
/**
|
|
1123
|
+
* Lowers one binding term. A membership ARRAY (`literalSet`) lowers to the
|
|
1124
|
+
* existing param-set term over its content-addressed registry entry — the
|
|
1125
|
+
* program IR is byte-identical to the same set spelled `r.inSet`; the SDK
|
|
1126
|
+
* supplies the translated member set itself at execute (`wireParams`).
|
|
1127
|
+
*/
|
|
894
1128
|
function lowerBindingTerm(ctx, context, binding, ids) {
|
|
895
1129
|
const bound = binding.term;
|
|
896
1130
|
switch (bound.kind) {
|
|
@@ -900,6 +1134,8 @@ function lowerBindingTerm(ctx, context, binding, ids) {
|
|
|
900
1134
|
return { kind: "param", param: paramIdOf(ctx, bound.name) };
|
|
901
1135
|
case "setParam":
|
|
902
1136
|
return { kind: "paramSet", param: paramIdOf(ctx, bound.name) };
|
|
1137
|
+
case "literalSet":
|
|
1138
|
+
return { kind: "paramSet", param: paramIdOf(ctx, bound.name) };
|
|
903
1139
|
case "literal":
|
|
904
1140
|
return { kind: "literal", value: taggedLiteral(context, binding.data, bound.value) };
|
|
905
1141
|
}
|