@bjornpagen/bumbledb 0.20.2 → 1.0.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 +283 -1611
- package/README.md +156 -118
- package/dist/capacity.d.ts +16 -12
- package/dist/capacity.d.ts.map +1 -1
- package/dist/capacity.js +16 -48
- package/dist/capacity.js.map +1 -1
- package/dist/changes.d.ts +54 -0
- package/dist/changes.d.ts.map +1 -0
- package/dist/changes.js +245 -0
- package/dist/changes.js.map +1 -0
- package/dist/close.d.ts +24 -0
- package/dist/close.d.ts.map +1 -0
- package/dist/close.js +34 -0
- package/dist/close.js.map +1 -0
- package/dist/closed.d.ts +1 -3
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +24 -12
- package/dist/closed.js.map +1 -1
- package/dist/codec.d.ts +56 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +289 -0
- package/dist/codec.js.map +1 -0
- package/dist/compile.d.ts +68 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +75 -0
- package/dist/compile.js.map +1 -0
- package/dist/db-native.d.ts +170 -0
- package/dist/db-native.d.ts.map +1 -0
- package/dist/db-native.js +5 -0
- package/dist/db-native.js.map +1 -0
- package/dist/db.d.ts +101 -233
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +232 -1084
- package/dist/db.js.map +1 -1
- package/dist/errors.d.ts +57 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +32 -0
- package/dist/errors.js.map +1 -0
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +2 -2
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +65 -24
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +94 -24
- package/dist/fields.js.map +1 -1
- package/dist/index.d.ts +37 -32
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -21
- package/dist/index.js.map +1 -1
- package/dist/internal/log.d.ts +24 -0
- package/dist/internal/log.d.ts.map +1 -0
- package/dist/internal/log.js +10 -0
- package/dist/internal/log.js.map +1 -0
- package/dist/law.d.ts +8 -13
- package/dist/law.d.ts.map +1 -1
- package/dist/law.js +6 -59
- package/dist/law.js.map +1 -1
- package/dist/lower.d.ts +0 -13
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +9 -17
- package/dist/lower.js.map +1 -1
- package/dist/migration.d.ts +33 -0
- package/dist/migration.d.ts.map +1 -0
- package/dist/migration.js +48 -0
- package/dist/migration.js.map +1 -0
- package/dist/native.d.ts +256 -301
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +65 -108
- package/dist/native.js.map +1 -1
- package/dist/query/atom.d.ts +24 -13
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +7 -3
- package/dist/query/atom.js.map +1 -1
- package/dist/query/compute.d.ts +68 -0
- package/dist/query/compute.d.ts.map +1 -0
- package/dist/query/compute.js +141 -0
- package/dist/query/compute.js.map +1 -0
- package/dist/query/find.d.ts +15 -8
- package/dist/query/find.d.ts.map +1 -1
- package/dist/query/find.js +6 -2
- package/dist/query/find.js.map +1 -1
- package/dist/query/lower.d.ts +23 -2
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +433 -80
- package/dist/query/lower.js.map +1 -1
- package/dist/query/parse-ir.d.ts.map +1 -1
- package/dist/query/parse-ir.js +17 -8
- package/dist/query/parse-ir.js.map +1 -1
- package/dist/query/run.d.ts +12 -2
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +26 -9
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +47 -3
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +83 -19
- package/dist/query/scope.js.map +1 -1
- package/dist/relation.d.ts +3 -8
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +16 -10
- package/dist/relation.js.map +1 -1
- package/dist/result.d.ts +47 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/result.js +50 -0
- package/dist/result.js.map +1 -0
- package/dist/rows.d.ts +69 -0
- package/dist/rows.d.ts.map +1 -0
- package/dist/rows.js +328 -0
- package/dist/rows.js.map +1 -0
- package/dist/runtime-codes.d.ts +2 -0
- package/dist/runtime-codes.d.ts.map +1 -0
- package/dist/runtime-codes.js +19 -0
- package/dist/runtime-codes.js.map +1 -0
- package/dist/runtime-errors.d.ts +106 -0
- package/dist/runtime-errors.d.ts.map +1 -0
- package/dist/runtime-errors.js +62 -0
- package/dist/runtime-errors.js.map +1 -0
- package/dist/runtime-native.d.ts +171 -0
- package/dist/runtime-native.d.ts.map +1 -0
- package/dist/runtime-native.js +5 -0
- package/dist/runtime-native.js.map +1 -0
- package/dist/runtime.d.ts +73 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +293 -0
- package/dist/runtime.js.map +1 -0
- package/dist/scalar.d.ts +165 -0
- package/dist/scalar.d.ts.map +1 -0
- package/dist/scalar.js +344 -0
- package/dist/scalar.js.map +1 -0
- package/dist/schema.d.ts +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +34 -37
- package/dist/schema.js.map +1 -1
- package/dist/shape.d.ts +28 -0
- package/dist/shape.d.ts.map +1 -0
- package/dist/shape.js +2 -0
- package/dist/shape.js.map +1 -0
- package/dist/spec.d.ts +27 -3
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js +22 -1
- package/dist/spec.js.map +1 -1
- package/dist/statements.d.ts +16 -3
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +54 -28
- package/dist/statements.js.map +1 -1
- package/dist/uuid.d.ts +23 -0
- package/dist/uuid.d.ts.map +1 -0
- package/dist/uuid.js +54 -0
- package/dist/uuid.js.map +1 -0
- package/pack-provenance.json +7 -0
- package/package.json +12 -16
- package/src/capacity.ts +34 -54
- package/src/changes.ts +361 -0
- package/src/close.ts +53 -0
- package/src/closed.ts +25 -19
- package/src/codec.ts +379 -0
- package/src/compile.ts +149 -0
- package/src/db-native.ts +247 -0
- package/src/db.ts +461 -1632
- package/src/errors.ts +50 -0
- package/src/face.ts +2 -2
- package/src/fields.ts +182 -55
- package/src/index.ts +62 -126
- package/src/internal/log.ts +51 -0
- package/src/law.ts +13 -28
- package/src/lower.ts +9 -4
- package/src/migration.ts +69 -0
- package/src/native.ts +326 -487
- package/src/query/atom.ts +61 -25
- package/src/query/compute.ts +237 -0
- package/src/query/find.ts +31 -12
- package/src/query/lower.ts +531 -143
- package/src/query/parse-ir.ts +18 -13
- package/src/query/run.ts +32 -16
- package/src/query/scope.ts +140 -8
- package/src/relation.ts +17 -21
- package/src/result.ts +130 -0
- package/src/rows.ts +404 -0
- package/src/runtime-codes.ts +18 -0
- package/src/runtime-errors.ts +69 -0
- package/src/runtime-native.ts +242 -0
- package/src/runtime.ts +440 -0
- package/src/scalar.ts +560 -0
- package/src/schema.ts +35 -37
- package/src/shape.ts +31 -0
- package/src/spec.ts +33 -3
- package/src/statements.ts +75 -68
- package/src/uuid.ts +69 -0
- package/dist/marshal.d.ts +0 -44
- package/dist/marshal.d.ts.map +0 -1
- package/dist/marshal.js +0 -165
- package/dist/marshal.js.map +0 -1
- package/src/marshal.ts +0 -200
package/dist/query/lower.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import * as errors from "@superbuilders/errors";
|
|
2
1
|
import { sealedFieldsOf } from "#closed.ts";
|
|
3
|
-
import {
|
|
2
|
+
import { AuthoringError, SdkInvariantError } from "#errors.ts";
|
|
3
|
+
import { assertDeclarationOrderKey, f64 as f64Field, isFloatIntervalValue, isIntervalValue, literalShapeError, rosterOf, u64 as u64Field } from "#fields.ts";
|
|
4
4
|
import { allen, and, eq, ge, gt, le, lt, ne, not, or, pointIn } from "#query/atom.ts";
|
|
5
|
-
import {
|
|
5
|
+
import { computeFieldOf, computeVarsOf, isComputeExpr, MAX_COMPUTE_DEPTH } from "#query/compute.ts";
|
|
6
|
+
import { count, max, mean, min, pack, sum } from "#query/find.ts";
|
|
6
7
|
import { parseQueryIr } from "#query/parse-ir.ts";
|
|
7
|
-
import { fieldAntiJoins, fieldJoins, headFieldJoins, inferred, isTerm, makeParam, makeSetParam, renderFieldKind, term } from "#query/scope.ts";
|
|
8
|
+
import { fieldAntiJoins, fieldJoins, headFieldJoins, importFacadeOf, importFacadeOfOwner, inferred, isImportedSource, isTerm, labelImport, makeParam, makeSetParam, renderFieldKind, term } from "#query/scope.ts";
|
|
9
|
+
import { literalWireOf } from "#scalar.ts";
|
|
10
|
+
import { Uuid } from "#uuid.ts";
|
|
8
11
|
const termOps = Object.freeze({
|
|
9
12
|
param: makeParam,
|
|
10
13
|
inSet: makeSetParam,
|
|
@@ -21,6 +24,7 @@ const termOps = Object.freeze({
|
|
|
21
24
|
not,
|
|
22
25
|
count,
|
|
23
26
|
sum,
|
|
27
|
+
mean,
|
|
24
28
|
min,
|
|
25
29
|
max,
|
|
26
30
|
pack
|
|
@@ -40,21 +44,42 @@ const EMPTY_RULE = Object.freeze({
|
|
|
40
44
|
* cross-binding joins mutually class-equal by transitivity.
|
|
41
45
|
*/
|
|
42
46
|
function mintSlotOf(context, ref) {
|
|
47
|
+
const facade = importFacadeOfOwner(ref.owner);
|
|
48
|
+
if (facade !== undefined) {
|
|
49
|
+
// An import var's mint slot is the imported head column's slot:
|
|
50
|
+
// the descriptor it projected and the carrier class that survived
|
|
51
|
+
// the projection (aggregate outputs are bare derived scalars).
|
|
52
|
+
const column = facade.columns.get(ref.column);
|
|
53
|
+
if (column === undefined || facade.source.schema !== context.theory) {
|
|
54
|
+
throw new AuthoringError({
|
|
55
|
+
message: `the variable ${ref.label} was minted from a query template schema ${context.theory.name} does not own`
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return column.slot ?? { field: ref.field, class: undefined };
|
|
59
|
+
}
|
|
43
60
|
if (context.theory.relations[ref.owner.name] !== ref.owner) {
|
|
44
|
-
throw
|
|
61
|
+
throw new AuthoringError({
|
|
62
|
+
message: `the variable ${ref.label} was minted from a relation schema ${context.theory.name} does not declare — mint variables with v() from the schema's own relations`
|
|
63
|
+
});
|
|
45
64
|
}
|
|
46
65
|
return { field: ref.field, class: context.classes[ref.owner.name]?.[ref.column] };
|
|
47
66
|
}
|
|
48
67
|
function membershipSet(context, field, value) {
|
|
49
68
|
const roster = rosterOf(field);
|
|
50
69
|
if (roster === undefined) {
|
|
51
|
-
throw
|
|
70
|
+
throw new AuthoringError({
|
|
71
|
+
message: `${context}: a membership array is the closed-reference spelling — ordinary field membership is a bound ∈-set param (r.inSet)`
|
|
72
|
+
});
|
|
52
73
|
}
|
|
53
74
|
if (value.length === 0) {
|
|
54
|
-
throw
|
|
75
|
+
throw new AuthoringError({
|
|
76
|
+
message: `${context}: an empty membership array selects nothing — write the query you mean`
|
|
77
|
+
});
|
|
55
78
|
}
|
|
56
79
|
if (value.length === 1) {
|
|
57
|
-
throw
|
|
80
|
+
throw new AuthoringError({
|
|
81
|
+
message: `${context}: a one-element membership array is the bare literal respelled — write the literal (the canonical-utterance law: one meaning, one spelling)`
|
|
82
|
+
});
|
|
58
83
|
}
|
|
59
84
|
const seen = new Set();
|
|
60
85
|
const members = value.map(function memberName(member) {
|
|
@@ -62,7 +87,9 @@ function membershipSet(context, field, value) {
|
|
|
62
87
|
throw literalShapeError(context, `a ${roster.name} handle name (string)`, member);
|
|
63
88
|
}
|
|
64
89
|
if (seen.has(member)) {
|
|
65
|
-
throw
|
|
90
|
+
throw new AuthoringError({
|
|
91
|
+
message: `${context}: the membership array spells ${member} twice — write it once (the canonical-utterance law: one meaning, one spelling)`
|
|
92
|
+
});
|
|
66
93
|
}
|
|
67
94
|
seen.add(member);
|
|
68
95
|
return member;
|
|
@@ -84,7 +111,7 @@ function resolveBindings(context, label, relation, bindings, joins = fieldJoins)
|
|
|
84
111
|
return candidate.name === fieldName;
|
|
85
112
|
});
|
|
86
113
|
if (declared === undefined) {
|
|
87
|
-
throw
|
|
114
|
+
throw new AuthoringError({ message: `${label} has no field ${fieldName}` });
|
|
88
115
|
}
|
|
89
116
|
const fieldClass = relationClasses?.[fieldName];
|
|
90
117
|
let bound;
|
|
@@ -95,7 +122,9 @@ function resolveBindings(context, label, relation, bindings, joins = fieldJoins)
|
|
|
95
122
|
const mint = mintSlotOf(context, ref);
|
|
96
123
|
const positionSlot = { field: declared.field, class: fieldClass };
|
|
97
124
|
if (!joins(mint, positionSlot)) {
|
|
98
|
-
throw
|
|
125
|
+
throw new AuthoringError({
|
|
126
|
+
message: `${label}: the variable ${ref.label} joins domain-unequal fields — minted at ${renderFieldKind(mint)}, reused at ${renderFieldKind(positionSlot)} (a var joins only class-equal slots; bare pairs only with bare)`
|
|
127
|
+
});
|
|
99
128
|
}
|
|
100
129
|
bound = Object.freeze({ kind: "var", ref });
|
|
101
130
|
vars.push(ref);
|
|
@@ -190,7 +219,9 @@ function condDataOf(cond, uses) {
|
|
|
190
219
|
if (cond.op === "allen") {
|
|
191
220
|
const maskValue = cond.mask;
|
|
192
221
|
if (typeof maskValue !== "number") {
|
|
193
|
-
throw
|
|
222
|
+
throw new AuthoringError({
|
|
223
|
+
message: "allen: the mask position takes a 13-bit mask number built from the ALLEN constants"
|
|
224
|
+
});
|
|
194
225
|
}
|
|
195
226
|
return Object.freeze({
|
|
196
227
|
kind: "cmp",
|
|
@@ -207,11 +238,13 @@ function condDataOf(cond, uses) {
|
|
|
207
238
|
});
|
|
208
239
|
return Object.freeze({ kind: "tree", op: cond.op, children: Object.freeze(children) });
|
|
209
240
|
}
|
|
210
|
-
throw
|
|
241
|
+
throw new AuthoringError({
|
|
242
|
+
message: "a negated atom is not a condition-tree node — pass not(...) to where() directly, never inside and()/or()"
|
|
243
|
+
});
|
|
211
244
|
}
|
|
212
245
|
function advanceWhere(context, state, cond) {
|
|
213
246
|
if (typeof cond !== "object" || cond === null || !("cond" in cond)) {
|
|
214
|
-
throw
|
|
247
|
+
throw new AuthoringError({ message: "where() takes a comparison, an and()/or() tree, or a negated atom" });
|
|
215
248
|
}
|
|
216
249
|
if (cond.cond === "notInterior") {
|
|
217
250
|
const bindings = Object.fromEntries(Object.entries(cond.bindings ?? {}).filter(function defined([, value]) {
|
|
@@ -246,7 +279,9 @@ function advanceInterior(state, target, bindings, kind) {
|
|
|
246
279
|
continue;
|
|
247
280
|
}
|
|
248
281
|
if (!isTerm(value) || value[term] !== "var") {
|
|
249
|
-
throw
|
|
282
|
+
throw new AuthoringError({
|
|
283
|
+
message: `interior ${target.name}: position ${key} takes a variable — bind literals and params through where()/match()`
|
|
284
|
+
});
|
|
250
285
|
}
|
|
251
286
|
resolved.push(Object.freeze({ key, ref: value }));
|
|
252
287
|
}
|
|
@@ -270,7 +305,7 @@ function asVarTerm(context, value) {
|
|
|
270
305
|
if (isTerm(value) && value[term] === "var") {
|
|
271
306
|
return value;
|
|
272
307
|
}
|
|
273
|
-
throw
|
|
308
|
+
throw new AuthoringError({ message: `${context}: expected a variable` });
|
|
274
309
|
}
|
|
275
310
|
function aggDataOf(name, entry) {
|
|
276
311
|
if (entry.agg === "count") {
|
|
@@ -279,17 +314,18 @@ function aggDataOf(name, entry) {
|
|
|
279
314
|
const over = entry.over;
|
|
280
315
|
switch (entry.agg) {
|
|
281
316
|
case "sum":
|
|
317
|
+
case "mean":
|
|
282
318
|
case "min":
|
|
283
319
|
case "max": {
|
|
284
320
|
if (isTerm(over) && over[term] === "var") {
|
|
285
321
|
return Object.freeze({ op: "fold", fold: entry.agg, over });
|
|
286
322
|
}
|
|
287
|
-
throw
|
|
323
|
+
throw new AuthoringError({ message: `find ${name} (${entry.agg}): takes a variable` });
|
|
288
324
|
}
|
|
289
325
|
case "pack":
|
|
290
326
|
return Object.freeze({ op: "pack", over: asVarTerm(`find ${name} (pack)`, over) });
|
|
291
327
|
default:
|
|
292
|
-
throw
|
|
328
|
+
throw new AuthoringError({ message: `find ${name}: unknown aggregate ${entry.agg}` });
|
|
293
329
|
}
|
|
294
330
|
}
|
|
295
331
|
function findColumnOf(name, entry) {
|
|
@@ -302,7 +338,17 @@ function findColumnOf(name, entry) {
|
|
|
302
338
|
slot: undefined
|
|
303
339
|
});
|
|
304
340
|
}
|
|
305
|
-
throw
|
|
341
|
+
throw new AuthoringError({
|
|
342
|
+
message: `find ${name}: a ${entry[term]} is not projectable — find takes variables, aggregates or Compute expressions`
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
if (isComputeExpr(entry)) {
|
|
346
|
+
return Object.freeze({
|
|
347
|
+
name,
|
|
348
|
+
entry: Object.freeze({ kind: "compute", expr: entry, result: entry.result }),
|
|
349
|
+
closed: undefined,
|
|
350
|
+
slot: undefined
|
|
351
|
+
});
|
|
306
352
|
}
|
|
307
353
|
if (isAggregateEntry(entry)) {
|
|
308
354
|
return Object.freeze({
|
|
@@ -312,26 +358,34 @@ function findColumnOf(name, entry) {
|
|
|
312
358
|
slot: undefined
|
|
313
359
|
});
|
|
314
360
|
}
|
|
315
|
-
throw
|
|
361
|
+
throw new AuthoringError({
|
|
362
|
+
message: `find ${name}: not a find entry — find takes variables, aggregates or Compute expressions`
|
|
363
|
+
});
|
|
316
364
|
}
|
|
317
365
|
/**
|
|
318
366
|
* The orderable ban's pointed refusal
|
|
319
367
|
* § orderability): a closed reference is equality-and-membership only.
|
|
320
368
|
*/
|
|
321
369
|
function closedOrderError(context, position, vocabulary) {
|
|
322
|
-
return
|
|
370
|
+
return new AuthoringError({
|
|
371
|
+
message: `${context}: ${position} is a ${vocabulary} reference — declaration order is an accident, not semantics: vocabularies do not order (equality, membership, and counting remain)`
|
|
372
|
+
});
|
|
323
373
|
}
|
|
324
374
|
function isOrderOp(op) {
|
|
325
375
|
return op === "lt" || op === "le" || op === "gt" || op === "ge" || op === "pointIn";
|
|
326
376
|
}
|
|
327
377
|
function assertBound(where, bound, ref) {
|
|
328
378
|
if (!bound.has(ref)) {
|
|
329
|
-
throw
|
|
379
|
+
throw new AuthoringError({
|
|
380
|
+
message: `${where}: the variable ${ref.label} is not bound by a relation atom of the rule`
|
|
381
|
+
});
|
|
330
382
|
}
|
|
331
383
|
}
|
|
332
384
|
function assertInterval(where, ref) {
|
|
333
385
|
if (ref.field.kind !== "interval") {
|
|
334
|
-
throw
|
|
386
|
+
throw new AuthoringError({
|
|
387
|
+
message: `${where}: ${ref.label} is not interval-typed — the measure is defined over interval-typed variables only`
|
|
388
|
+
});
|
|
335
389
|
}
|
|
336
390
|
}
|
|
337
391
|
function assertNotClosed(where, position, ref) {
|
|
@@ -341,16 +395,40 @@ function assertNotClosed(where, position, ref) {
|
|
|
341
395
|
}
|
|
342
396
|
}
|
|
343
397
|
function assertNumeric(where, position, ref) {
|
|
344
|
-
if (ref.field.kind !== "u64" && ref.field.kind !== "i64") {
|
|
345
|
-
throw
|
|
398
|
+
if (ref.field.kind !== "u64" && ref.field.kind !== "i64" && ref.field.kind !== "f64") {
|
|
399
|
+
throw new AuthoringError({
|
|
400
|
+
message: `${where}: ${position} ${ref.label} is ${ref.field.kind}, not numeric — a fold reads u64/i64/f64 only`
|
|
401
|
+
});
|
|
346
402
|
}
|
|
347
403
|
}
|
|
404
|
+
/**
|
|
405
|
+
* The head column's slot: a projected variable keeps its mint slot; an
|
|
406
|
+
* aggregate output is a NEW derived scalar — its descriptor follows the
|
|
407
|
+
* aggregate's typing (count is u64, mean is f64, sum/min/max keep the
|
|
408
|
+
* input's scalar kind, pack keeps the interval kind) and its class is
|
|
409
|
+
* BARE: a derived value is not the carrier the class law tracked.
|
|
410
|
+
*/
|
|
348
411
|
function findColumnSlotOf(context, column) {
|
|
349
412
|
const entry = column.entry;
|
|
350
413
|
if (entry.kind === "var") {
|
|
351
414
|
return mintSlotOf(context, entry.over);
|
|
352
415
|
}
|
|
353
|
-
|
|
416
|
+
if (entry.kind === "compute") {
|
|
417
|
+
return { field: computeFieldOf(entry.result), class: undefined };
|
|
418
|
+
}
|
|
419
|
+
const agg = entry.agg;
|
|
420
|
+
switch (agg.op) {
|
|
421
|
+
case "count":
|
|
422
|
+
return { field: u64Field, class: undefined };
|
|
423
|
+
case "fold": {
|
|
424
|
+
if (agg.fold === "mean") {
|
|
425
|
+
return { field: f64Field, class: undefined };
|
|
426
|
+
}
|
|
427
|
+
return { field: agg.over.field, class: undefined };
|
|
428
|
+
}
|
|
429
|
+
case "pack":
|
|
430
|
+
return { field: agg.over.field, class: undefined };
|
|
431
|
+
}
|
|
354
432
|
}
|
|
355
433
|
function validateColumn(context, bound, column) {
|
|
356
434
|
const where = `${contextLabel(context)} find ${column.name}`;
|
|
@@ -359,6 +437,15 @@ function validateColumn(context, bound, column) {
|
|
|
359
437
|
assertBound(where, bound, entry.over);
|
|
360
438
|
return;
|
|
361
439
|
}
|
|
440
|
+
if (entry.kind === "compute") {
|
|
441
|
+
// Kind agreement, closed-reference and depth walls hold at
|
|
442
|
+
// construction (#query/compute.ts); boundness is a RULE property,
|
|
443
|
+
// judged here like every other term position.
|
|
444
|
+
for (const ref of computeVarsOf(entry.expr)) {
|
|
445
|
+
assertBound(where, bound, ref);
|
|
446
|
+
}
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
362
449
|
const agg = entry.agg;
|
|
363
450
|
switch (agg.op) {
|
|
364
451
|
case "count":
|
|
@@ -367,6 +454,9 @@ function validateColumn(context, bound, column) {
|
|
|
367
454
|
assertBound(where, bound, agg.over);
|
|
368
455
|
assertNotClosed(where, `the ${agg.fold} input`, agg.over);
|
|
369
456
|
assertNumeric(where, `the ${agg.fold} input`, agg.over);
|
|
457
|
+
if (agg.fold === "mean" && agg.over.field.kind !== "f64") {
|
|
458
|
+
throw new AuthoringError({ message: `${where}: mean requires an f64 input; cast integers explicitly` });
|
|
459
|
+
}
|
|
370
460
|
return;
|
|
371
461
|
}
|
|
372
462
|
case "pack":
|
|
@@ -393,7 +483,9 @@ function validateCond(context, bound, cond) {
|
|
|
393
483
|
const lhs = mintSlotOf(context, cond.lhs.ref);
|
|
394
484
|
const rhs = mintSlotOf(context, cond.rhs.ref);
|
|
395
485
|
if (!fieldJoins(lhs, rhs)) {
|
|
396
|
-
throw
|
|
486
|
+
throw new AuthoringError({
|
|
487
|
+
message: `${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)`
|
|
488
|
+
});
|
|
397
489
|
}
|
|
398
490
|
}
|
|
399
491
|
return;
|
|
@@ -413,17 +505,23 @@ function validateInterior(context, bound, item, columns) {
|
|
|
413
505
|
});
|
|
414
506
|
for (const key of keys) {
|
|
415
507
|
if (!headNames.includes(key)) {
|
|
416
|
-
throw
|
|
508
|
+
throw new AuthoringError({
|
|
509
|
+
message: `${label}: interior ${item.target.name} binds ${key}, not a head column of ${item.target.name} (head columns: ${headNames.join(", ")})`
|
|
510
|
+
});
|
|
417
511
|
}
|
|
418
512
|
}
|
|
419
513
|
for (const name of headNames) {
|
|
420
514
|
if (!keys.includes(name)) {
|
|
421
|
-
throw
|
|
515
|
+
throw new AuthoringError({
|
|
516
|
+
message: `${label}: interior ${item.target.name} omits the head column ${name} — an interior join binds every head column of ${item.target.name}`
|
|
517
|
+
});
|
|
422
518
|
}
|
|
423
519
|
}
|
|
424
520
|
for (const binding of item.bindings) {
|
|
425
521
|
if (item.kind === "negatedInterior" && !bound.has(binding.ref)) {
|
|
426
|
-
throw
|
|
522
|
+
throw new AuthoringError({
|
|
523
|
+
message: `${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)`
|
|
524
|
+
});
|
|
427
525
|
}
|
|
428
526
|
const headColumn = headColumns.find(function byName(column) {
|
|
429
527
|
return column.name === binding.key;
|
|
@@ -433,14 +531,16 @@ function validateInterior(context, bound, item, columns) {
|
|
|
433
531
|
}
|
|
434
532
|
const mint = mintSlotOf(context, binding.ref);
|
|
435
533
|
if (!fieldJoins(headColumn.slot, mint)) {
|
|
436
|
-
throw
|
|
534
|
+
throw new AuthoringError({
|
|
535
|
+
message: `${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`
|
|
536
|
+
});
|
|
437
537
|
}
|
|
438
538
|
}
|
|
439
539
|
}
|
|
440
540
|
function completeRule(context, state, rawColumns) {
|
|
441
541
|
const label = contextLabel(context);
|
|
442
542
|
if (rawColumns.length === 0) {
|
|
443
|
-
throw
|
|
543
|
+
throw new AuthoringError({ message: `${label}: a find needs at least one entry` });
|
|
444
544
|
}
|
|
445
545
|
const columns = rawColumns.map(function enrichColumn(column) {
|
|
446
546
|
assertDeclarationOrderKey(`${label} find column`, column.name);
|
|
@@ -452,7 +552,9 @@ function completeRule(context, state, rawColumns) {
|
|
|
452
552
|
if (item.kind === "negated") {
|
|
453
553
|
for (const binding of item.atom.bindings) {
|
|
454
554
|
if (binding.term.kind === "var" && !state.bound.has(binding.term.ref)) {
|
|
455
|
-
throw
|
|
555
|
+
throw new AuthoringError({
|
|
556
|
+
message: `${label}: negated ${item.atom.relation.name} atom binds the variable ${binding.term.ref.label} at position ${binding.field}, but no positive atom of the rule binds it — a negated atom binds nothing, only rejects (the safety rule)`
|
|
557
|
+
});
|
|
456
558
|
}
|
|
457
559
|
}
|
|
458
560
|
}
|
|
@@ -489,44 +591,177 @@ function lookupDerived(context, name) {
|
|
|
489
591
|
});
|
|
490
592
|
if (interior !== undefined) {
|
|
491
593
|
if (context.kind === "interior" && name === context.self) {
|
|
492
|
-
throw
|
|
594
|
+
throw new AuthoringError({
|
|
595
|
+
message: `interior ${name}: an interior does not read itself — declaration order is topological (a self-read is InteriorNotPrior)`
|
|
596
|
+
});
|
|
493
597
|
}
|
|
494
598
|
return interior;
|
|
495
599
|
}
|
|
496
600
|
const rec = "rec" in context ? context.rec : undefined;
|
|
497
601
|
if (rec !== undefined && rec.name === name) {
|
|
498
602
|
if (context.kind === "interior") {
|
|
499
|
-
throw
|
|
603
|
+
throw new AuthoringError({
|
|
604
|
+
message: `interior ${context.self}: interiors cannot read the rec — this cut's interiors are a prefix`
|
|
605
|
+
});
|
|
500
606
|
}
|
|
501
607
|
if (context.kind === "rec-base") {
|
|
502
|
-
throw
|
|
608
|
+
throw new AuthoringError({
|
|
609
|
+
message: `rec ${rec.name}: a base arm does not read the rec — self-atoms belong on rec arms`
|
|
610
|
+
});
|
|
503
611
|
}
|
|
504
612
|
if (!isRecHead(rec)) {
|
|
505
|
-
throw
|
|
613
|
+
throw new AuthoringError({ message: `rec ${rec.name}: rec arms resolve the rec head after base arms seal it` });
|
|
506
614
|
}
|
|
507
615
|
return rec;
|
|
508
616
|
}
|
|
509
|
-
throw
|
|
617
|
+
throw new AuthoringError({ message: `${contextLabel(context)}: no derived table named ${name} is in scope` });
|
|
510
618
|
}
|
|
511
619
|
function interiorAdvance(context, state, name, bindings) {
|
|
512
620
|
return advanceInterior(state, lookupDerived(context, name), bindings, "interior");
|
|
513
621
|
}
|
|
622
|
+
const importEntries = new WeakMap();
|
|
623
|
+
const importTables = new WeakSet();
|
|
624
|
+
function importEntryOf(context, imported) {
|
|
625
|
+
const cached = importEntries.get(imported);
|
|
626
|
+
if (cached !== undefined) {
|
|
627
|
+
if (imported.schema !== context.theory) {
|
|
628
|
+
throw new AuthoringError({
|
|
629
|
+
message: `${contextLabel(context)}: the imported query belongs to schema ${imported.schema.name}, not ${context.theory.name} — templates compose within one schema`
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
return cached;
|
|
633
|
+
}
|
|
634
|
+
if (imported.schema !== context.theory) {
|
|
635
|
+
throw new AuthoringError({
|
|
636
|
+
message: `${contextLabel(context)}: the imported query belongs to schema ${imported.schema.name}, not ${context.theory.name} — templates compose within one schema`
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
if (imported.data.kind === "reach") {
|
|
640
|
+
throw new AuthoringError({
|
|
641
|
+
message: `${contextLabel(context)}: a recursive query is not importable as a relation expression yet — declare the recursion on the consuming query (C05 boundary, recorded)`
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
const userParams = imported.data.params.filter(function userSupplied(entry) {
|
|
645
|
+
return entry.membership === undefined;
|
|
646
|
+
});
|
|
647
|
+
if (userParams.length > 0) {
|
|
648
|
+
throw new AuthoringError({
|
|
649
|
+
message: `${contextLabel(context)}: the imported query takes parameters (${userParams
|
|
650
|
+
.map((entry) => entry.name)
|
|
651
|
+
.join(", ")}) — bind an import's meaning at declaration; parameterized imports are not supported`
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
const facade = importFacadeOf(imported);
|
|
655
|
+
const table = Object.freeze({
|
|
656
|
+
name: facade.owner.name,
|
|
657
|
+
finds: imported.data.finds,
|
|
658
|
+
rules: imported.data.rules
|
|
659
|
+
});
|
|
660
|
+
const entry = Object.freeze({ table, inner: imported.data.interiors });
|
|
661
|
+
importEntries.set(imported, entry);
|
|
662
|
+
importTables.add(table);
|
|
663
|
+
importInner.set(table, entry.inner);
|
|
664
|
+
return entry;
|
|
665
|
+
}
|
|
666
|
+
const importInner = new WeakMap();
|
|
667
|
+
/**
|
|
668
|
+
* Every import table the assembled query references, in first-reference
|
|
669
|
+
* order (declared interiors first, then rec arms, then main rules — the
|
|
670
|
+
* lowering walk), deduplicated by table identity.
|
|
671
|
+
*/
|
|
672
|
+
function referencedImports(interiors, rec, rules) {
|
|
673
|
+
const seen = new Set();
|
|
674
|
+
const out = [];
|
|
675
|
+
function walk(ruleList) {
|
|
676
|
+
for (const rule of ruleList) {
|
|
677
|
+
for (const item of rule.items) {
|
|
678
|
+
if (item.kind !== "interior" && item.kind !== "negatedInterior") {
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
const target = item.target;
|
|
682
|
+
if (importTables.has(target) && !seen.has(target)) {
|
|
683
|
+
seen.add(target);
|
|
684
|
+
out.push(target);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
for (const interior of interiors) {
|
|
690
|
+
walk(interior.rules);
|
|
691
|
+
}
|
|
692
|
+
if (rec !== undefined) {
|
|
693
|
+
walk(rec.base);
|
|
694
|
+
walk(rec.rec);
|
|
695
|
+
}
|
|
696
|
+
walk(rules);
|
|
697
|
+
return out;
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Splices referenced imports ahead of the declared interiors: each import's
|
|
701
|
+
* inner interiors first (its rules read them), then the import's own table.
|
|
702
|
+
* Interior prior-ness is preserved — declared interiors never read imports
|
|
703
|
+
* they could not have named, and main/rec rules may read anything earlier.
|
|
704
|
+
* Duplicate names across the final roster refuse.
|
|
705
|
+
*/
|
|
706
|
+
function spliceImports(interiors, rec, rules) {
|
|
707
|
+
const imports = referencedImports(interiors, rec, rules);
|
|
708
|
+
if (imports.length === 0) {
|
|
709
|
+
return interiors;
|
|
710
|
+
}
|
|
711
|
+
const spliced = [];
|
|
712
|
+
const names = new Set();
|
|
713
|
+
const added = new Set();
|
|
714
|
+
function add(table) {
|
|
715
|
+
if (added.has(table)) {
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
if (names.has(table.name)) {
|
|
719
|
+
throw new AuthoringError({
|
|
720
|
+
message: `query: the derived-table name ${table.name} appears twice after import splicing — rename the colliding interior (names are unique)`
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
added.add(table);
|
|
724
|
+
names.add(table.name);
|
|
725
|
+
spliced.push(table);
|
|
726
|
+
}
|
|
727
|
+
for (const table of imports) {
|
|
728
|
+
for (const inner of importInner.get(table) ?? []) {
|
|
729
|
+
add(inner);
|
|
730
|
+
}
|
|
731
|
+
add(table);
|
|
732
|
+
}
|
|
733
|
+
for (const declared of interiors) {
|
|
734
|
+
add(declared);
|
|
735
|
+
}
|
|
736
|
+
return Object.freeze(spliced);
|
|
737
|
+
}
|
|
738
|
+
function importAdvance(context, state, imported, bindings) {
|
|
739
|
+
const entry = importEntryOf(context, imported);
|
|
740
|
+
return advanceInterior(state, entry.table, bindings, "interior");
|
|
741
|
+
}
|
|
514
742
|
function notInteriorAdvance(context, state, name, bindings) {
|
|
515
743
|
if (context.kind === "rec-base" || context.kind === "rec-arm") {
|
|
516
|
-
throw
|
|
744
|
+
throw new AuthoringError({
|
|
745
|
+
message: `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`
|
|
746
|
+
});
|
|
517
747
|
}
|
|
518
748
|
return advanceInterior(state, lookupDerived(context, name), bindings, "negatedInterior");
|
|
519
749
|
}
|
|
520
750
|
function findColumns(context, entries) {
|
|
521
751
|
const columns = [];
|
|
522
|
-
|
|
752
|
+
// Nonrecursive derived stages (interiors) may emit aggregate/computed
|
|
753
|
+
// outputs (C05: "do not leave Interior.rules projection only"); ONLY the
|
|
754
|
+
// recursive feedback cycle stays projection-only — no aggregate,
|
|
755
|
+
// arithmetic-created value or negation flows through it.
|
|
756
|
+
const recName = context.kind === "rec-base" || context.kind === "rec-arm" ? context.self.name : undefined;
|
|
523
757
|
for (const [name, entry] of Object.entries(entries)) {
|
|
524
758
|
if (entry === undefined) {
|
|
525
759
|
continue;
|
|
526
760
|
}
|
|
527
|
-
if (
|
|
528
|
-
|
|
529
|
-
|
|
761
|
+
if (recName !== undefined && !(isTerm(entry) && entry[term] === "var")) {
|
|
762
|
+
throw new AuthoringError({
|
|
763
|
+
message: `rec ${recName}: a rec head projects bound variables only — no aggregate or arithmetic-created value flows through the feedback cycle (compute over the finished set in the main rules)`
|
|
764
|
+
});
|
|
530
765
|
}
|
|
531
766
|
columns.push(findColumnOf(name, entry));
|
|
532
767
|
}
|
|
@@ -535,6 +770,9 @@ function findColumns(context, entries) {
|
|
|
535
770
|
function makeRawChain(context, state) {
|
|
536
771
|
const chain = {
|
|
537
772
|
match(relation, bindings) {
|
|
773
|
+
if (isImportedSource(relation)) {
|
|
774
|
+
return makeRawChain(context, importAdvance(context, state, relation, bindings));
|
|
775
|
+
}
|
|
538
776
|
return makeRawChain(context, advanceMatch(context, state, relation, bindings));
|
|
539
777
|
},
|
|
540
778
|
where(cond) {
|
|
@@ -554,6 +792,9 @@ function makeRawScope(context) {
|
|
|
554
792
|
const scope = {
|
|
555
793
|
...termOps,
|
|
556
794
|
match(relation, bindings) {
|
|
795
|
+
if (isImportedSource(relation)) {
|
|
796
|
+
return makeRawChain(context, importAdvance(context, EMPTY_RULE, relation, bindings));
|
|
797
|
+
}
|
|
557
798
|
return makeRawChain(context, advanceMatch(context, EMPTY_RULE, relation, bindings));
|
|
558
799
|
},
|
|
559
800
|
interior(name, bindings) {
|
|
@@ -578,14 +819,14 @@ function isTypedScope(scope) {
|
|
|
578
819
|
function makeQueryRuleScope(theory, env) {
|
|
579
820
|
const raw = makeRawScope({ kind: "query", classes: theory.classes, theory, ...env });
|
|
580
821
|
if (!isTypedScope(raw)) {
|
|
581
|
-
throw
|
|
822
|
+
throw new SdkInvariantError({ message: "query rule builder construction incomplete" });
|
|
582
823
|
}
|
|
583
824
|
return raw;
|
|
584
825
|
}
|
|
585
826
|
function makeInteriorRuleScope(theory, env, self) {
|
|
586
827
|
const raw = makeRawScope({ kind: "interior", self, classes: theory.classes, theory, ...env });
|
|
587
828
|
if (!isTypedScope(raw)) {
|
|
588
|
-
throw
|
|
829
|
+
throw new SdkInvariantError({ message: "interior rule builder construction incomplete" });
|
|
589
830
|
}
|
|
590
831
|
return raw;
|
|
591
832
|
}
|
|
@@ -606,7 +847,7 @@ function makeRecRuleScope(theory, env, self, kind) {
|
|
|
606
847
|
...env
|
|
607
848
|
});
|
|
608
849
|
if (!isTypedScope(raw)) {
|
|
609
|
-
throw
|
|
850
|
+
throw new SdkInvariantError({ message: "rec rule builder construction incomplete" });
|
|
610
851
|
}
|
|
611
852
|
return raw;
|
|
612
853
|
}
|
|
@@ -618,6 +859,9 @@ function headSignature(column) {
|
|
|
618
859
|
if (entry.kind === "var") {
|
|
619
860
|
return `${column.name}:var`;
|
|
620
861
|
}
|
|
862
|
+
if (entry.kind === "compute") {
|
|
863
|
+
return `${column.name}:compute`;
|
|
864
|
+
}
|
|
621
865
|
const agg = entry.agg;
|
|
622
866
|
if (agg.op === "fold") {
|
|
623
867
|
return `${column.name}:${agg.fold}`;
|
|
@@ -656,16 +900,22 @@ function paramRegistryOf(interiors, rec, rules) {
|
|
|
656
900
|
continue;
|
|
657
901
|
}
|
|
658
902
|
if ((existing.members === undefined) !== (use.members === undefined)) {
|
|
659
|
-
throw
|
|
903
|
+
throw new AuthoringError({
|
|
904
|
+
message: `query param ${use.name} collides with a membership array's registry entry — name the param differently`
|
|
905
|
+
});
|
|
660
906
|
}
|
|
661
907
|
if (existing.shape !== use.shape) {
|
|
662
|
-
throw
|
|
908
|
+
throw new AuthoringError({
|
|
909
|
+
message: `query param ${use.name} is used both as a ${existing.shape} param and a ${use.shape} param — one name, one shape`
|
|
910
|
+
});
|
|
663
911
|
}
|
|
664
912
|
if (existing.anchor !== undefined && use.anchor !== undefined) {
|
|
665
913
|
const registered = rosterOf(existing.anchor);
|
|
666
914
|
const anchored = rosterOf(use.anchor);
|
|
667
915
|
if (registered !== anchored) {
|
|
668
|
-
throw
|
|
916
|
+
throw new AuthoringError({
|
|
917
|
+
message: `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`
|
|
918
|
+
});
|
|
669
919
|
}
|
|
670
920
|
}
|
|
671
921
|
if (existing.anchor === undefined && use.anchor !== undefined) {
|
|
@@ -693,13 +943,13 @@ function paramRegistryOf(interiors, rec, rules) {
|
|
|
693
943
|
return Object.freeze(order.map(function entryOf(name) {
|
|
694
944
|
const entry = byName.get(name);
|
|
695
945
|
if (entry === undefined) {
|
|
696
|
-
throw
|
|
946
|
+
throw new AuthoringError({ message: `query param ${name} lost its registry entry` });
|
|
697
947
|
}
|
|
698
948
|
let membership;
|
|
699
949
|
if (entry.members !== undefined) {
|
|
700
950
|
const anchor = entry.anchor;
|
|
701
951
|
if (anchor === undefined) {
|
|
702
|
-
throw
|
|
952
|
+
throw new AuthoringError({ message: `query param ${name} lost its membership anchor` });
|
|
703
953
|
}
|
|
704
954
|
membership = Object.freeze({
|
|
705
955
|
kind: "set",
|
|
@@ -722,24 +972,30 @@ function paramRegistryOf(interiors, rec, rules) {
|
|
|
722
972
|
function alignedHeadOf(label, rules) {
|
|
723
973
|
const first = rules[0];
|
|
724
974
|
if (first === undefined) {
|
|
725
|
-
throw
|
|
975
|
+
throw new AuthoringError({ message: `${label} needs at least one rule` });
|
|
726
976
|
}
|
|
727
977
|
const signature = first.finds.map(headSignature).join(", ");
|
|
728
978
|
rules.forEach(function verifyHead(rule, index) {
|
|
729
979
|
const candidate = rule.finds.map(headSignature).join(", ");
|
|
730
980
|
if (candidate !== signature) {
|
|
731
|
-
throw
|
|
981
|
+
throw new AuthoringError({
|
|
982
|
+
message: `every rule of ${label} derives the same head — rule 0 finds (${signature}), rule ${index} finds (${candidate})`
|
|
983
|
+
});
|
|
732
984
|
}
|
|
733
985
|
rule.finds.forEach(function verifyClosedSlice(column, position) {
|
|
734
986
|
const lead = first.finds[position];
|
|
735
987
|
if (lead !== undefined && column.closed !== lead.closed) {
|
|
736
|
-
throw
|
|
988
|
+
throw new AuthoringError({
|
|
989
|
+
message: `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)`
|
|
990
|
+
});
|
|
737
991
|
}
|
|
738
992
|
if (lead === undefined) {
|
|
739
993
|
return;
|
|
740
994
|
}
|
|
741
995
|
if (lead.slot !== undefined && column.slot !== undefined && !headFieldJoins(lead.slot, column.slot)) {
|
|
742
|
-
throw
|
|
996
|
+
throw new AuthoringError({
|
|
997
|
+
message: `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 class-equal slots; same-wire u64 admits a bare side, and the merged head is bare)`
|
|
998
|
+
});
|
|
743
999
|
}
|
|
744
1000
|
});
|
|
745
1001
|
});
|
|
@@ -765,18 +1021,21 @@ function alignedHeadOf(label, rules) {
|
|
|
765
1021
|
return Object.freeze(merged);
|
|
766
1022
|
}
|
|
767
1023
|
function afterMainError(what) {
|
|
768
|
-
return
|
|
1024
|
+
return new AuthoringError({
|
|
1025
|
+
message: `query: ${what} after a main rule is unwritable — declaration order is interiors, then rec, then main`
|
|
1026
|
+
});
|
|
769
1027
|
}
|
|
770
1028
|
function makeRawQuery(theory, interiors, rec, rules) {
|
|
771
1029
|
const mergedFinds = alignedHeadOf("a query", rules);
|
|
772
1030
|
const first = rules[0];
|
|
773
1031
|
if (first === undefined) {
|
|
774
|
-
throw
|
|
1032
|
+
throw new AuthoringError({ message: "a query needs at least one rule" });
|
|
775
1033
|
}
|
|
776
1034
|
const env = rec === undefined ? { interiors } : { interiors, rec };
|
|
777
|
-
const
|
|
1035
|
+
const allInteriors = spliceImports(interiors, rec, rules);
|
|
1036
|
+
const frozenInteriors = Object.freeze([...allInteriors]);
|
|
778
1037
|
const frozenRules = Object.freeze([...rules]);
|
|
779
|
-
const params = paramRegistryOf(
|
|
1038
|
+
const params = paramRegistryOf(allInteriors, rec, rules);
|
|
780
1039
|
const data = rec === undefined
|
|
781
1040
|
? Object.freeze({
|
|
782
1041
|
kind: "cq",
|
|
@@ -800,6 +1059,12 @@ function makeRawQuery(theory, interiors, rec, rules) {
|
|
|
800
1059
|
const built = build(makeRawScope({ kind: "query", classes: theory.classes, theory, ...env }));
|
|
801
1060
|
return makeRawQuery(theory, interiors, rec, [...rules, built.rule]);
|
|
802
1061
|
},
|
|
1062
|
+
named(label) {
|
|
1063
|
+
// A diagnostic name for composition/tracing — not a schema
|
|
1064
|
+
// relation, a persistent view or a separate CTE type.
|
|
1065
|
+
labelImport(value, label);
|
|
1066
|
+
return value;
|
|
1067
|
+
},
|
|
803
1068
|
interior() {
|
|
804
1069
|
throw afterMainError("interior");
|
|
805
1070
|
},
|
|
@@ -815,7 +1080,7 @@ function makeQuery(theory, interiors, rec, rules) {
|
|
|
815
1080
|
}
|
|
816
1081
|
function collectInterior(theory, env, name, builds) {
|
|
817
1082
|
if (builds.length === 0) {
|
|
818
|
-
throw
|
|
1083
|
+
throw new AuthoringError({ message: `query: interior ${name} needs at least one rule` });
|
|
819
1084
|
}
|
|
820
1085
|
const rules = builds.map(function buildRule(buildOne) {
|
|
821
1086
|
return buildOne(makeInteriorRuleScope(theory, env, name)).rule;
|
|
@@ -825,10 +1090,10 @@ function collectInterior(theory, env, name, builds) {
|
|
|
825
1090
|
}
|
|
826
1091
|
function collectRec(theory, interiors, name, baseBuilds, recBuilds) {
|
|
827
1092
|
if (baseBuilds.length === 0) {
|
|
828
|
-
throw
|
|
1093
|
+
throw new AuthoringError({ message: `query: rec ${name} has no base arms` });
|
|
829
1094
|
}
|
|
830
1095
|
if (recBuilds.length === 0) {
|
|
831
|
-
throw
|
|
1096
|
+
throw new AuthoringError({ message: `query: rec ${name} has no rec arms` });
|
|
832
1097
|
}
|
|
833
1098
|
const handle = Object.freeze({ name });
|
|
834
1099
|
const baseEnv = { interiors, rec: handle };
|
|
@@ -838,11 +1103,11 @@ function collectRec(theory, interiors, name, baseBuilds, recBuilds) {
|
|
|
838
1103
|
const baseFinds = alignedHeadOf(`rec ${name}`, base);
|
|
839
1104
|
const first = base[0];
|
|
840
1105
|
if (first === undefined) {
|
|
841
|
-
throw
|
|
1106
|
+
throw new AuthoringError({ message: `query: rec ${name} has no base arms` });
|
|
842
1107
|
}
|
|
843
1108
|
const firstFind = baseFinds[0];
|
|
844
1109
|
if (firstFind === undefined) {
|
|
845
|
-
throw
|
|
1110
|
+
throw new AuthoringError({ message: `query: rec ${name} has no head` });
|
|
846
1111
|
}
|
|
847
1112
|
const finds = [firstFind, ...baseFinds.slice(1)];
|
|
848
1113
|
const head = Object.freeze({ name, finds });
|
|
@@ -853,12 +1118,12 @@ function collectRec(theory, interiors, name, baseBuilds, recBuilds) {
|
|
|
853
1118
|
const mergedAll = alignedHeadOf(`rec ${name}`, [...base, ...rec]);
|
|
854
1119
|
const mergedFirst = mergedAll[0];
|
|
855
1120
|
if (mergedFirst === undefined) {
|
|
856
|
-
throw
|
|
1121
|
+
throw new AuthoringError({ message: `query: rec ${name} has no head` });
|
|
857
1122
|
}
|
|
858
1123
|
const sealedFinds = [mergedFirst, ...mergedAll.slice(1)];
|
|
859
1124
|
const firstRec = rec[0];
|
|
860
1125
|
if (firstRec === undefined) {
|
|
861
|
-
throw
|
|
1126
|
+
throw new AuthoringError({ message: `query: rec ${name} has no rec arms` });
|
|
862
1127
|
}
|
|
863
1128
|
const sealedBase = [first, ...base.slice(1)];
|
|
864
1129
|
const sealedRec = [firstRec, ...rec.slice(1)];
|
|
@@ -877,10 +1142,10 @@ function makeQueryStart(theory, interiors) {
|
|
|
877
1142
|
if (interiors.some(function sameName(interior) {
|
|
878
1143
|
return interior.name === name;
|
|
879
1144
|
})) {
|
|
880
|
-
throw
|
|
1145
|
+
throw new AuthoringError({ message: `query: interior ${name} is already declared — names are unique` });
|
|
881
1146
|
}
|
|
882
1147
|
if (name.length === 0) {
|
|
883
|
-
throw
|
|
1148
|
+
throw new AuthoringError({ message: "query: an interior needs a name" });
|
|
884
1149
|
}
|
|
885
1150
|
const data = collectInterior(theory, env, name, builds);
|
|
886
1151
|
return makeQueryStart(theory, [...interiors, data]);
|
|
@@ -889,10 +1154,10 @@ function makeQueryStart(theory, interiors) {
|
|
|
889
1154
|
if (interiors.some(function sameName(interior) {
|
|
890
1155
|
return interior.name === name;
|
|
891
1156
|
})) {
|
|
892
|
-
throw
|
|
1157
|
+
throw new AuthoringError({ message: `query: interior and rec share the name ${name}` });
|
|
893
1158
|
}
|
|
894
1159
|
if (name.length === 0) {
|
|
895
|
-
throw
|
|
1160
|
+
throw new AuthoringError({ message: "query: reach needs a name" });
|
|
896
1161
|
}
|
|
897
1162
|
const data = collectRec(theory, interiors, name, arms.base, arms.rec);
|
|
898
1163
|
return makeQueryReachStart(theory, interiors, data);
|
|
@@ -925,11 +1190,29 @@ function taggedHandleId(context, closed, value) {
|
|
|
925
1190
|
}
|
|
926
1191
|
const id = closed.handles.indexOf(value);
|
|
927
1192
|
if (id < 0) {
|
|
928
|
-
throw
|
|
1193
|
+
throw new AuthoringError({
|
|
1194
|
+
message: `${context}: "${value}" is not a handle of ${closed.name} — the roster is ${closed.handles.join(", ")}`
|
|
1195
|
+
});
|
|
929
1196
|
}
|
|
930
1197
|
return { kind: "u64", value: BigInt(id) };
|
|
931
1198
|
}
|
|
932
1199
|
function taggedAtElementDomain(context, element, value) {
|
|
1200
|
+
if (element === "f64") {
|
|
1201
|
+
if (typeof value === "number") {
|
|
1202
|
+
return { kind: "f64", value };
|
|
1203
|
+
}
|
|
1204
|
+
if (isFloatIntervalValue(value)) {
|
|
1205
|
+
if (Number.isNaN(value.start) || Number.isNaN(value.end) || !(value.start < value.end)) {
|
|
1206
|
+
throw literalShapeError(context, "a nonempty float interval with non-NaN endpoints", value);
|
|
1207
|
+
}
|
|
1208
|
+
return {
|
|
1209
|
+
kind: "intervalF64",
|
|
1210
|
+
start: Object.is(value.start, -0) ? 0 : value.start,
|
|
1211
|
+
end: Object.is(value.end, -0) ? 0 : value.end
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
throw literalShapeError(context, "number (point) or { start, end } numbers (interval)", value);
|
|
1215
|
+
}
|
|
933
1216
|
if (typeof value === "bigint") {
|
|
934
1217
|
if (element === "u64") {
|
|
935
1218
|
return { kind: "u64", value };
|
|
@@ -968,6 +1251,18 @@ function taggedLiteral(context, field, value) {
|
|
|
968
1251
|
}
|
|
969
1252
|
return { kind: "i64", value };
|
|
970
1253
|
}
|
|
1254
|
+
case "f64": {
|
|
1255
|
+
if (typeof value !== "number") {
|
|
1256
|
+
throw literalShapeError(context, "number", value);
|
|
1257
|
+
}
|
|
1258
|
+
return { kind: "f64", value };
|
|
1259
|
+
}
|
|
1260
|
+
case "uuid": {
|
|
1261
|
+
if (!Uuid.isUuid(value)) {
|
|
1262
|
+
throw literalShapeError(context, "a UUID (canonical UUID text)", value);
|
|
1263
|
+
}
|
|
1264
|
+
return { kind: "uuid", value };
|
|
1265
|
+
}
|
|
971
1266
|
case "str": {
|
|
972
1267
|
if (typeof value !== "string") {
|
|
973
1268
|
throw literalShapeError(context, "string", value);
|
|
@@ -1006,6 +1301,9 @@ function taggedCmpLiteral(context, sibling, value, op) {
|
|
|
1006
1301
|
isIntervalValue(value)) {
|
|
1007
1302
|
return taggedAtElementDomain(context, sibling.kind, value);
|
|
1008
1303
|
}
|
|
1304
|
+
if (op === "pointIn" && rosterOf(sibling) === undefined && sibling.kind === "f64" && isFloatIntervalValue(value)) {
|
|
1305
|
+
return taggedAtElementDomain(context, "f64", value);
|
|
1306
|
+
}
|
|
1009
1307
|
return taggedLiteral(context, sibling, value);
|
|
1010
1308
|
}
|
|
1011
1309
|
function freshVarIds() {
|
|
@@ -1025,18 +1323,20 @@ function freshVarIds() {
|
|
|
1025
1323
|
function paramIdOf(ctx, name) {
|
|
1026
1324
|
const id = ctx.paramIds.get(name);
|
|
1027
1325
|
if (id === undefined) {
|
|
1028
|
-
throw
|
|
1326
|
+
throw new AuthoringError({ message: `query lowering: param ${name} is not in the query's registry` });
|
|
1029
1327
|
}
|
|
1030
1328
|
return id;
|
|
1031
1329
|
}
|
|
1032
1330
|
function lowerAtom(ctx, atom, ids) {
|
|
1033
1331
|
const member = ctx.theory.relations[atom.relation.name];
|
|
1034
1332
|
if (member !== atom.relation) {
|
|
1035
|
-
throw
|
|
1333
|
+
throw new AuthoringError({
|
|
1334
|
+
message: `query lowering: relation ${atom.relation.name} is not the relation value schema ${ctx.theory.name} declares`
|
|
1335
|
+
});
|
|
1036
1336
|
}
|
|
1037
1337
|
const relationId = ctx.relationIds.get(atom.relation.name);
|
|
1038
1338
|
if (relationId === undefined) {
|
|
1039
|
-
throw
|
|
1339
|
+
throw new AuthoringError({ message: `query lowering: relation ${atom.relation.name} has no ordinal` });
|
|
1040
1340
|
}
|
|
1041
1341
|
const ordered = sealedFieldsOf(atom.relation);
|
|
1042
1342
|
const bindings = atom.bindings.map(function lowerBinding(binding) {
|
|
@@ -1044,7 +1344,9 @@ function lowerAtom(ctx, atom, ids) {
|
|
|
1044
1344
|
return candidate.name === binding.field;
|
|
1045
1345
|
});
|
|
1046
1346
|
if (ordinal < 0) {
|
|
1047
|
-
throw
|
|
1347
|
+
throw new AuthoringError({
|
|
1348
|
+
message: `query lowering: relation ${atom.relation.name} has no field ${binding.field}`
|
|
1349
|
+
});
|
|
1048
1350
|
}
|
|
1049
1351
|
return [ordinal, lowerBindingTerm(ctx, `${atom.relation.name}.${binding.field}`, binding, ids)];
|
|
1050
1352
|
});
|
|
@@ -1074,17 +1376,17 @@ function lowerBindingTerm(ctx, context, binding, ids) {
|
|
|
1074
1376
|
function lowerInteriorAtom(ctx, target, bindings, ids) {
|
|
1075
1377
|
const interior = ctx.interiorIds.get(target.name);
|
|
1076
1378
|
if (interior === undefined) {
|
|
1077
|
-
throw
|
|
1379
|
+
throw new AuthoringError({ message: `query lowering: derived table ${target.name} was not declared on this query` });
|
|
1078
1380
|
}
|
|
1079
1381
|
if (target.finds.length === 0) {
|
|
1080
|
-
throw
|
|
1382
|
+
throw new AuthoringError({ message: `query lowering: derived table ${target.name} has no head` });
|
|
1081
1383
|
}
|
|
1082
1384
|
const irBindings = target.finds.map(function lowerPosition(column, position) {
|
|
1083
1385
|
const binding = bindings.find(function byKey(candidate) {
|
|
1084
1386
|
return candidate.key === column.name;
|
|
1085
1387
|
});
|
|
1086
1388
|
if (binding === undefined) {
|
|
1087
|
-
throw
|
|
1389
|
+
throw new AuthoringError({ message: `query lowering: interior ${target.name} omits head column ${column.name}` });
|
|
1088
1390
|
}
|
|
1089
1391
|
return [position, { kind: "var", var: ids.of(binding.ref) }];
|
|
1090
1392
|
});
|
|
@@ -1101,7 +1403,9 @@ function lowerCmpTerm(ctx, side, sibling, ids, op) {
|
|
|
1101
1403
|
case "literal": {
|
|
1102
1404
|
const anchor = cmpAnchorOf(ctx, sibling);
|
|
1103
1405
|
if (anchor === undefined) {
|
|
1104
|
-
throw
|
|
1406
|
+
throw new AuthoringError({
|
|
1407
|
+
message: "query lowering: a comparison literal needs a bound-variable or anchored-param sibling to type it"
|
|
1408
|
+
});
|
|
1105
1409
|
}
|
|
1106
1410
|
return { kind: "literal", value: taggedCmpLiteral("comparison literal", anchor, side.value, op) };
|
|
1107
1411
|
}
|
|
@@ -1141,10 +1445,54 @@ function lowerCondition(ctx, cond, ids) {
|
|
|
1141
1445
|
})
|
|
1142
1446
|
};
|
|
1143
1447
|
}
|
|
1448
|
+
/**
|
|
1449
|
+
* Lowers one computed scalar node to the C05 wire (`#native.ts`'s
|
|
1450
|
+
* `ScalarExprIr`): the shared grammar arms pass through 1:1. Cached
|
|
1451
|
+
* construction depth is the bound; this walk emits wire only and does
|
|
1452
|
+
* not re-judge kinds (L14/native compile owns binding).
|
|
1453
|
+
*/
|
|
1454
|
+
function lowerComputeNode(node, ids) {
|
|
1455
|
+
if (node.depth > MAX_COMPUTE_DEPTH) {
|
|
1456
|
+
throw new AuthoringError({
|
|
1457
|
+
message: `query lowering: a compute expression is deeper than ${MAX_COMPUTE_DEPTH} nodes (the engine's scalar depth bound)`
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
return lowerComputeGrammar(node, ids);
|
|
1461
|
+
}
|
|
1462
|
+
function lowerComputeGrammar(node, ids) {
|
|
1463
|
+
switch (node.kind) {
|
|
1464
|
+
case "var":
|
|
1465
|
+
return { kind: "var", var: ids.of(node.ref) };
|
|
1466
|
+
case "literal":
|
|
1467
|
+
return { kind: "literal", value: literalWireOf(node.value) };
|
|
1468
|
+
case "negate":
|
|
1469
|
+
case "isNaN":
|
|
1470
|
+
case "isFinite":
|
|
1471
|
+
return { kind: node.kind, expr: lowerComputeGrammar(node.expr, ids) };
|
|
1472
|
+
case "cast":
|
|
1473
|
+
return { kind: "cast", cast: node.cast, expr: lowerComputeGrammar(node.expr, ids) };
|
|
1474
|
+
case "add":
|
|
1475
|
+
case "subtract":
|
|
1476
|
+
case "multiply":
|
|
1477
|
+
case "divide":
|
|
1478
|
+
return {
|
|
1479
|
+
kind: node.kind,
|
|
1480
|
+
left: lowerComputeGrammar(node.left, ids),
|
|
1481
|
+
right: lowerComputeGrammar(node.right, ids)
|
|
1482
|
+
};
|
|
1483
|
+
default:
|
|
1484
|
+
throw new AuthoringError({
|
|
1485
|
+
message: "query lowering: a query-var tree cannot carry a source-field leaf — use Compute over bound variables"
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1144
1489
|
function lowerFind(entry, ids) {
|
|
1145
1490
|
if (entry.kind === "var") {
|
|
1146
1491
|
return { kind: "var", var: ids.of(entry.over) };
|
|
1147
1492
|
}
|
|
1493
|
+
if (entry.kind === "compute") {
|
|
1494
|
+
return { kind: "compute", expr: lowerComputeNode(entry.expr, ids) };
|
|
1495
|
+
}
|
|
1148
1496
|
const agg = entry.agg;
|
|
1149
1497
|
switch (agg.op) {
|
|
1150
1498
|
case "count":
|
|
@@ -1170,6 +1518,9 @@ function headTermOf(column) {
|
|
|
1170
1518
|
if (entry.kind === "var") {
|
|
1171
1519
|
return { kind: "var" };
|
|
1172
1520
|
}
|
|
1521
|
+
if (entry.kind === "compute") {
|
|
1522
|
+
return { kind: "compute" };
|
|
1523
|
+
}
|
|
1173
1524
|
return { kind: "aggregate", op: headOpOf(entry.agg) };
|
|
1174
1525
|
}
|
|
1175
1526
|
function lowerRule(ctx, rule) {
|
|
@@ -1227,7 +1578,9 @@ function lowerQuery(q) {
|
|
|
1227
1578
|
const params = new Map();
|
|
1228
1579
|
q.data.params.forEach(function assignParamId(entry, index) {
|
|
1229
1580
|
if (entry.anchor === undefined) {
|
|
1230
|
-
throw
|
|
1581
|
+
throw new AuthoringError({
|
|
1582
|
+
message: `query param ${entry.name} has no field-anchored use — bind it in an atom or compare it against a bound variable`
|
|
1583
|
+
});
|
|
1231
1584
|
}
|
|
1232
1585
|
paramIds.set(entry.name, index);
|
|
1233
1586
|
params.set(entry.name, entry);
|