@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/src/query/lower.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import * as errors from "@superbuilders/errors"
|
|
2
1
|
import { sealedFieldsOf } from "#closed.ts"
|
|
2
|
+
import { AuthoringError, SdkInvariantError } from "#errors.ts"
|
|
3
3
|
import type { AnyClosedRoster, AnyField } from "#fields.ts"
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
assertDeclarationOrderKey,
|
|
6
|
+
f64 as f64Field,
|
|
7
|
+
isFloatIntervalValue,
|
|
8
|
+
isIntervalValue,
|
|
9
|
+
literalShapeError,
|
|
10
|
+
rosterOf,
|
|
11
|
+
u64 as u64Field
|
|
12
|
+
} from "#fields.ts"
|
|
5
13
|
import type { ClassRecordOf, SchemaClasses } from "#law.ts"
|
|
6
14
|
import type {
|
|
7
15
|
AtomIr,
|
|
@@ -13,6 +21,7 @@ import type {
|
|
|
13
21
|
ParsedQuery,
|
|
14
22
|
QueryParam,
|
|
15
23
|
RuleIr,
|
|
24
|
+
ScalarExprIr,
|
|
16
25
|
TaggedValue,
|
|
17
26
|
TermIr
|
|
18
27
|
} from "#native.ts"
|
|
@@ -44,8 +53,10 @@ import type {
|
|
|
44
53
|
RuleItem
|
|
45
54
|
} from "#query/atom.ts"
|
|
46
55
|
import { allen, and, eq, ge, gt, le, lt, ne, not, or, pointIn } from "#query/atom.ts"
|
|
56
|
+
import type { QueryNode } from "#query/compute.ts"
|
|
57
|
+
import { computeFieldOf, computeVarsOf, isComputeExpr, MAX_COMPUTE_DEPTH } from "#query/compute.ts"
|
|
47
58
|
import type { CheckFind, CheckRecFind, FindShape, HeadRecordOf, RowOfFind } from "#query/find.ts"
|
|
48
|
-
import { count, max, min, pack, sum } from "#query/find.ts"
|
|
59
|
+
import { count, max, mean, min, pack, sum } from "#query/find.ts"
|
|
49
60
|
import { parseQueryIr } from "#query/parse-ir.ts"
|
|
50
61
|
import type {
|
|
51
62
|
AnyVar,
|
|
@@ -62,14 +73,20 @@ import {
|
|
|
62
73
|
fieldAntiJoins,
|
|
63
74
|
fieldJoins,
|
|
64
75
|
headFieldJoins,
|
|
76
|
+
importFacadeOf,
|
|
77
|
+
importFacadeOfOwner,
|
|
65
78
|
inferred,
|
|
79
|
+
isImportedSource,
|
|
66
80
|
isTerm,
|
|
81
|
+
labelImport,
|
|
67
82
|
makeParam,
|
|
68
83
|
makeSetParam,
|
|
69
84
|
renderFieldKind,
|
|
70
85
|
term
|
|
71
86
|
} from "#query/scope.ts"
|
|
87
|
+
import { literalWireOf } from "#scalar.ts"
|
|
72
88
|
import type { AnySchema, Schema, SchemaRelations } from "#schema.ts"
|
|
89
|
+
import { Uuid } from "#uuid.ts"
|
|
73
90
|
|
|
74
91
|
type QueryRelation<Rels extends SchemaRelations> = Extract<Rels[keyof Rels], MatchOwner>
|
|
75
92
|
|
|
@@ -121,6 +138,7 @@ interface TermOps {
|
|
|
121
138
|
readonly not: typeof not
|
|
122
139
|
readonly count: typeof count
|
|
123
140
|
readonly sum: typeof sum
|
|
141
|
+
readonly mean: typeof mean
|
|
124
142
|
readonly min: typeof min
|
|
125
143
|
readonly max: typeof max
|
|
126
144
|
readonly pack: typeof pack
|
|
@@ -147,12 +165,25 @@ interface QueryRuleScope<Rels extends SchemaRelations, Classes extends SchemaCla
|
|
|
147
165
|
bindings: B & CheckBindings<Classes, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>
|
|
148
166
|
): QueryRuleChain<Rels, BindParamsShape<MatchFields<R>, B>, Classes>
|
|
149
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Nonrecursive composition (chapter 34): a typed query template of the
|
|
170
|
+
* SAME schema is a relation expression — its whole body splices as a
|
|
171
|
+
* derived stage; every head column must be bound to a variable minted
|
|
172
|
+
* by `v(imported)`. Naming materializes nothing.
|
|
173
|
+
*/
|
|
174
|
+
match<Q extends AnyQuery, const B extends ImportMatchShape<Q>>(
|
|
175
|
+
imported: Q,
|
|
176
|
+
bindings: B
|
|
177
|
+
): QueryRuleChain<Rels, Record<never, never>, Classes>
|
|
178
|
+
|
|
150
179
|
interior<const B extends Readonly<Record<string, AnyVar>>>(
|
|
151
180
|
name: string,
|
|
152
181
|
bindings: B & CheckInteriorBindings<B>
|
|
153
182
|
): QueryRuleChain<Rels, Record<never, never>, Classes>
|
|
154
183
|
}
|
|
155
184
|
|
|
185
|
+
type ImportMatchShape<Q extends AnyQuery> = Readonly<Record<keyof RowOf<Q> & string, AnyVar>>
|
|
186
|
+
|
|
156
187
|
interface QueryRuleChain<
|
|
157
188
|
Rels extends SchemaRelations,
|
|
158
189
|
P extends ParamsRecord,
|
|
@@ -176,6 +207,11 @@ interface QueryRuleChain<
|
|
|
176
207
|
bindings: B & CheckBindings<Classes, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>
|
|
177
208
|
): QueryRuleChain<Rels, Flatten<P & BindParamsShape<MatchFields<R>, B>>, Classes>
|
|
178
209
|
|
|
210
|
+
match<Q extends AnyQuery, const B extends ImportMatchShape<Q>>(
|
|
211
|
+
imported: Q,
|
|
212
|
+
bindings: B
|
|
213
|
+
): QueryRuleChain<Rels, P, Classes>
|
|
214
|
+
|
|
179
215
|
where<const C extends AnyCond>(
|
|
180
216
|
cond: CheckCond<Classes, C> & C
|
|
181
217
|
): QueryRuleChain<Rels, Flatten<P & CondParamsShape<C>>, Classes>
|
|
@@ -206,6 +242,10 @@ interface InteriorRuleScope<Rels extends SchemaRelations, Classes extends Schema
|
|
|
206
242
|
relation: R,
|
|
207
243
|
bindings: B & CheckBindings<Classes, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>
|
|
208
244
|
): InteriorRuleChain<Rels, BindParamsShape<MatchFields<R>, B>, Classes>
|
|
245
|
+
match<Q extends AnyQuery, const B extends ImportMatchShape<Q>>(
|
|
246
|
+
imported: Q,
|
|
247
|
+
bindings: B
|
|
248
|
+
): InteriorRuleChain<Rels, Record<never, never>, Classes>
|
|
209
249
|
interior<const B extends Readonly<Record<string, AnyVar>>>(
|
|
210
250
|
name: string,
|
|
211
251
|
bindings: B & CheckInteriorBindings<B>
|
|
@@ -233,6 +273,10 @@ interface InteriorRuleChain<
|
|
|
233
273
|
relation: R,
|
|
234
274
|
bindings: B & CheckBindings<Classes, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>
|
|
235
275
|
): InteriorRuleChain<Rels, Flatten<P & BindParamsShape<MatchFields<R>, B>>, Classes>
|
|
276
|
+
match<Q extends AnyQuery, const B extends ImportMatchShape<Q>>(
|
|
277
|
+
imported: Q,
|
|
278
|
+
bindings: B
|
|
279
|
+
): InteriorRuleChain<Rels, P, Classes>
|
|
236
280
|
where<const C extends AnyCond>(
|
|
237
281
|
cond: CheckCond<Classes, C> & C
|
|
238
282
|
): InteriorRuleChain<Rels, Flatten<P & CondParamsShape<C>>, Classes>
|
|
@@ -240,7 +284,11 @@ interface InteriorRuleChain<
|
|
|
240
284
|
name: string,
|
|
241
285
|
bindings: B & CheckInteriorBindings<B>
|
|
242
286
|
): InteriorRuleChain<Rels, P, Classes>
|
|
243
|
-
|
|
287
|
+
/**
|
|
288
|
+
* Nonrecursive derived stages emit aggregate/computed outputs too
|
|
289
|
+
* (C05): only the RECURSIVE head stays projection-only.
|
|
290
|
+
*/
|
|
291
|
+
find<const F extends FindShape>(entries: F & CheckFind<F>): RuleValue<RowOfFind<F>, P, HeadRecordOf<Classes, F>>
|
|
244
292
|
}
|
|
245
293
|
|
|
246
294
|
interface RecRuleScope<Rels extends SchemaRelations, Classes extends SchemaClasses = SchemaClasses> extends TermOps {
|
|
@@ -260,6 +308,11 @@ interface RecRuleScope<Rels extends SchemaRelations, Classes extends SchemaClass
|
|
|
260
308
|
relation: R,
|
|
261
309
|
bindings: B & CheckBindings<Classes, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>
|
|
262
310
|
): RecRuleChain<Rels, BindParamsShape<MatchFields<R>, B>, Classes>
|
|
311
|
+
/** Frozen finite nonrecursive imports may feed base/step (chapter 34). */
|
|
312
|
+
match<Q extends AnyQuery, const B extends ImportMatchShape<Q>>(
|
|
313
|
+
imported: Q,
|
|
314
|
+
bindings: B
|
|
315
|
+
): RecRuleChain<Rels, Record<never, never>, Classes>
|
|
263
316
|
interior<const B extends Readonly<Record<string, AnyVar>>>(
|
|
264
317
|
name: string,
|
|
265
318
|
bindings: B & CheckInteriorBindings<B>
|
|
@@ -287,6 +340,10 @@ interface RecRuleChain<
|
|
|
287
340
|
relation: R,
|
|
288
341
|
bindings: B & CheckBindings<Classes, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>
|
|
289
342
|
): RecRuleChain<Rels, Flatten<P & BindParamsShape<MatchFields<R>, B>>, Classes>
|
|
343
|
+
match<Q extends AnyQuery, const B extends ImportMatchShape<Q>>(
|
|
344
|
+
imported: Q,
|
|
345
|
+
bindings: B
|
|
346
|
+
): RecRuleChain<Rels, P, Classes>
|
|
290
347
|
where<const C extends AnyCond>(
|
|
291
348
|
cond: CheckCond<Classes, C> & C
|
|
292
349
|
): RecRuleChain<Rels, Flatten<P & CondParamsShape<C>>, Classes>
|
|
@@ -336,6 +393,9 @@ interface Query<
|
|
|
336
393
|
build: (r: QueryRuleScope<Rels, Classes>) => RV
|
|
337
394
|
): Query<Rels, Row | RowOf<RV>, Flatten<Params & ParamsOf<RV>>, Classes>
|
|
338
395
|
|
|
396
|
+
/** A diagnostic name for composition/tracing — never a schema relation. */
|
|
397
|
+
named(label: string): Query<Rels, Row, Params, Classes>
|
|
398
|
+
|
|
339
399
|
interior(name: string, ...builds: never[]): never
|
|
340
400
|
|
|
341
401
|
reach(name: string, arms: never): never
|
|
@@ -395,6 +455,7 @@ const termOps: TermOps = Object.freeze({
|
|
|
395
455
|
not,
|
|
396
456
|
count,
|
|
397
457
|
sum,
|
|
458
|
+
mean,
|
|
398
459
|
min,
|
|
399
460
|
max,
|
|
400
461
|
pack
|
|
@@ -428,10 +489,23 @@ interface ResolvedBindings {
|
|
|
428
489
|
* cross-binding joins mutually class-equal by transitivity.
|
|
429
490
|
*/
|
|
430
491
|
function mintSlotOf(context: ChainContext, ref: AnyVar): ClassedField {
|
|
492
|
+
const facade = importFacadeOfOwner(ref.owner)
|
|
493
|
+
if (facade !== undefined) {
|
|
494
|
+
// An import var's mint slot is the imported head column's slot:
|
|
495
|
+
// the descriptor it projected and the carrier class that survived
|
|
496
|
+
// the projection (aggregate outputs are bare derived scalars).
|
|
497
|
+
const column = facade.columns.get(ref.column)
|
|
498
|
+
if (column === undefined || (facade.source.schema as AnySchema) !== context.theory) {
|
|
499
|
+
throw new AuthoringError({
|
|
500
|
+
message: `the variable ${ref.label} was minted from a query template schema ${context.theory.name} does not own`
|
|
501
|
+
})
|
|
502
|
+
}
|
|
503
|
+
return column.slot ?? { field: ref.field, class: undefined }
|
|
504
|
+
}
|
|
431
505
|
if (context.theory.relations[ref.owner.name] !== ref.owner) {
|
|
432
|
-
throw
|
|
433
|
-
`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`
|
|
434
|
-
)
|
|
506
|
+
throw new AuthoringError({
|
|
507
|
+
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`
|
|
508
|
+
})
|
|
435
509
|
}
|
|
436
510
|
return { field: ref.field, class: context.classes[ref.owner.name]?.[ref.column] }
|
|
437
511
|
}
|
|
@@ -443,17 +517,19 @@ function membershipSet(
|
|
|
443
517
|
): { readonly name: string; readonly members: readonly string[] } {
|
|
444
518
|
const roster = rosterOf(field)
|
|
445
519
|
if (roster === undefined) {
|
|
446
|
-
throw
|
|
447
|
-
`${context}: a membership array is the closed-reference spelling — ordinary field membership is a bound ∈-set param (r.inSet)`
|
|
448
|
-
)
|
|
520
|
+
throw new AuthoringError({
|
|
521
|
+
message: `${context}: a membership array is the closed-reference spelling — ordinary field membership is a bound ∈-set param (r.inSet)`
|
|
522
|
+
})
|
|
449
523
|
}
|
|
450
524
|
if (value.length === 0) {
|
|
451
|
-
throw
|
|
525
|
+
throw new AuthoringError({
|
|
526
|
+
message: `${context}: an empty membership array selects nothing — write the query you mean`
|
|
527
|
+
})
|
|
452
528
|
}
|
|
453
529
|
if (value.length === 1) {
|
|
454
|
-
throw
|
|
455
|
-
`${context}: a one-element membership array is the bare literal respelled — write the literal (the canonical-utterance law: one meaning, one spelling)`
|
|
456
|
-
)
|
|
530
|
+
throw new AuthoringError({
|
|
531
|
+
message: `${context}: a one-element membership array is the bare literal respelled — write the literal (the canonical-utterance law: one meaning, one spelling)`
|
|
532
|
+
})
|
|
457
533
|
}
|
|
458
534
|
const seen = new Set<string>()
|
|
459
535
|
const members = value.map(function memberName(member) {
|
|
@@ -461,9 +537,9 @@ function membershipSet(
|
|
|
461
537
|
throw literalShapeError(context, `a ${roster.name} handle name (string)`, member)
|
|
462
538
|
}
|
|
463
539
|
if (seen.has(member)) {
|
|
464
|
-
throw
|
|
465
|
-
`${context}: the membership array spells ${member} twice — write it once (the canonical-utterance law: one meaning, one spelling)`
|
|
466
|
-
)
|
|
540
|
+
throw new AuthoringError({
|
|
541
|
+
message: `${context}: the membership array spells ${member} twice — write it once (the canonical-utterance law: one meaning, one spelling)`
|
|
542
|
+
})
|
|
467
543
|
}
|
|
468
544
|
seen.add(member)
|
|
469
545
|
return member
|
|
@@ -492,7 +568,7 @@ function resolveBindings(
|
|
|
492
568
|
return candidate.name === fieldName
|
|
493
569
|
})
|
|
494
570
|
if (declared === undefined) {
|
|
495
|
-
throw
|
|
571
|
+
throw new AuthoringError({ message: `${label} has no field ${fieldName}` })
|
|
496
572
|
}
|
|
497
573
|
const fieldClass = relationClasses?.[fieldName]
|
|
498
574
|
let bound: BindingEntry["term"]
|
|
@@ -503,9 +579,9 @@ function resolveBindings(
|
|
|
503
579
|
const mint = mintSlotOf(context, ref)
|
|
504
580
|
const positionSlot: ClassedField = { field: declared.field, class: fieldClass }
|
|
505
581
|
if (!joins(mint, positionSlot)) {
|
|
506
|
-
throw
|
|
507
|
-
`${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)`
|
|
508
|
-
)
|
|
582
|
+
throw new AuthoringError({
|
|
583
|
+
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)`
|
|
584
|
+
})
|
|
509
585
|
}
|
|
510
586
|
bound = Object.freeze({ kind: "var" as const, ref })
|
|
511
587
|
vars.push(ref)
|
|
@@ -615,7 +691,9 @@ function condDataOf(cond: AnyCond, uses: ParamUse[]): CondData {
|
|
|
615
691
|
if (cond.op === "allen") {
|
|
616
692
|
const maskValue = cond.mask
|
|
617
693
|
if (typeof maskValue !== "number") {
|
|
618
|
-
throw
|
|
694
|
+
throw new AuthoringError({
|
|
695
|
+
message: "allen: the mask position takes a 13-bit mask number built from the ALLEN constants"
|
|
696
|
+
})
|
|
619
697
|
}
|
|
620
698
|
return Object.freeze({
|
|
621
699
|
kind: "cmp" as const,
|
|
@@ -632,14 +710,14 @@ function condDataOf(cond: AnyCond, uses: ParamUse[]): CondData {
|
|
|
632
710
|
})
|
|
633
711
|
return Object.freeze({ kind: "tree" as const, op: cond.op, children: Object.freeze(children) })
|
|
634
712
|
}
|
|
635
|
-
throw
|
|
636
|
-
"a negated atom is not a condition-tree node — pass not(...) to where() directly, never inside and()/or()"
|
|
637
|
-
)
|
|
713
|
+
throw new AuthoringError({
|
|
714
|
+
message: "a negated atom is not a condition-tree node — pass not(...) to where() directly, never inside and()/or()"
|
|
715
|
+
})
|
|
638
716
|
}
|
|
639
717
|
|
|
640
718
|
function advanceWhere(context: ChainContext, state: RuleBuildState, cond: AnyCond): RuleBuildState {
|
|
641
719
|
if (typeof cond !== "object" || cond === null || !("cond" in cond)) {
|
|
642
|
-
throw
|
|
720
|
+
throw new AuthoringError({ message: "where() takes a comparison, an and()/or() tree, or a negated atom" })
|
|
643
721
|
}
|
|
644
722
|
if (cond.cond === "notInterior") {
|
|
645
723
|
const bindings: Readonly<Record<string, unknown>> = Object.fromEntries(
|
|
@@ -684,9 +762,9 @@ function advanceInterior(
|
|
|
684
762
|
continue
|
|
685
763
|
}
|
|
686
764
|
if (!isTerm(value) || value[term] !== "var") {
|
|
687
|
-
throw
|
|
688
|
-
`interior ${target.name}: position ${key} takes a variable — bind literals and params through where()/match()`
|
|
689
|
-
)
|
|
765
|
+
throw new AuthoringError({
|
|
766
|
+
message: `interior ${target.name}: position ${key} takes a variable — bind literals and params through where()/match()`
|
|
767
|
+
})
|
|
690
768
|
}
|
|
691
769
|
resolved.push(Object.freeze({ key, ref: value }))
|
|
692
770
|
}
|
|
@@ -712,7 +790,7 @@ function asVarTerm(context: string, value: unknown): AnyVar {
|
|
|
712
790
|
if (isTerm(value) && value[term] === "var") {
|
|
713
791
|
return value
|
|
714
792
|
}
|
|
715
|
-
throw
|
|
793
|
+
throw new AuthoringError({ message: `${context}: expected a variable` })
|
|
716
794
|
}
|
|
717
795
|
|
|
718
796
|
function aggDataOf(name: string, entry: { readonly agg: string; readonly over?: unknown }): AggData {
|
|
@@ -722,17 +800,18 @@ function aggDataOf(name: string, entry: { readonly agg: string; readonly over?:
|
|
|
722
800
|
const over = entry.over
|
|
723
801
|
switch (entry.agg) {
|
|
724
802
|
case "sum":
|
|
803
|
+
case "mean":
|
|
725
804
|
case "min":
|
|
726
805
|
case "max": {
|
|
727
806
|
if (isTerm(over) && over[term] === "var") {
|
|
728
807
|
return Object.freeze({ op: "fold" as const, fold: entry.agg, over })
|
|
729
808
|
}
|
|
730
|
-
throw
|
|
809
|
+
throw new AuthoringError({ message: `find ${name} (${entry.agg}): takes a variable` })
|
|
731
810
|
}
|
|
732
811
|
case "pack":
|
|
733
812
|
return Object.freeze({ op: "pack" as const, over: asVarTerm(`find ${name} (pack)`, over) })
|
|
734
813
|
default:
|
|
735
|
-
throw
|
|
814
|
+
throw new AuthoringError({ message: `find ${name}: unknown aggregate ${entry.agg}` })
|
|
736
815
|
}
|
|
737
816
|
}
|
|
738
817
|
|
|
@@ -746,7 +825,17 @@ function findColumnOf(name: string, entry: unknown): FindColumn {
|
|
|
746
825
|
slot: undefined
|
|
747
826
|
})
|
|
748
827
|
}
|
|
749
|
-
throw
|
|
828
|
+
throw new AuthoringError({
|
|
829
|
+
message: `find ${name}: a ${entry[term]} is not projectable — find takes variables, aggregates or Compute expressions`
|
|
830
|
+
})
|
|
831
|
+
}
|
|
832
|
+
if (isComputeExpr(entry)) {
|
|
833
|
+
return Object.freeze({
|
|
834
|
+
name,
|
|
835
|
+
entry: Object.freeze({ kind: "compute" as const, expr: entry, result: entry.result }),
|
|
836
|
+
closed: undefined,
|
|
837
|
+
slot: undefined
|
|
838
|
+
})
|
|
750
839
|
}
|
|
751
840
|
if (isAggregateEntry(entry)) {
|
|
752
841
|
return Object.freeze({
|
|
@@ -756,7 +845,9 @@ function findColumnOf(name: string, entry: unknown): FindColumn {
|
|
|
756
845
|
slot: undefined
|
|
757
846
|
})
|
|
758
847
|
}
|
|
759
|
-
throw
|
|
848
|
+
throw new AuthoringError({
|
|
849
|
+
message: `find ${name}: not a find entry — find takes variables, aggregates or Compute expressions`
|
|
850
|
+
})
|
|
760
851
|
}
|
|
761
852
|
|
|
762
853
|
/**
|
|
@@ -764,9 +855,9 @@ function findColumnOf(name: string, entry: unknown): FindColumn {
|
|
|
764
855
|
* § orderability): a closed reference is equality-and-membership only.
|
|
765
856
|
*/
|
|
766
857
|
function closedOrderError(context: string, position: string, vocabulary: string): Error {
|
|
767
|
-
return
|
|
768
|
-
`${context}: ${position} is a ${vocabulary} reference — declaration order is an accident, not semantics: vocabularies do not order (equality, membership, and counting remain)`
|
|
769
|
-
)
|
|
858
|
+
return new AuthoringError({
|
|
859
|
+
message: `${context}: ${position} is a ${vocabulary} reference — declaration order is an accident, not semantics: vocabularies do not order (equality, membership, and counting remain)`
|
|
860
|
+
})
|
|
770
861
|
}
|
|
771
862
|
|
|
772
863
|
function isOrderOp(op: CmpKind | "binding"): op is "lt" | "le" | "gt" | "ge" | "pointIn" {
|
|
@@ -775,15 +866,17 @@ function isOrderOp(op: CmpKind | "binding"): op is "lt" | "le" | "gt" | "ge" | "
|
|
|
775
866
|
|
|
776
867
|
function assertBound(where: string, bound: ReadonlySet<AnyVar>, ref: AnyVar): void {
|
|
777
868
|
if (!bound.has(ref)) {
|
|
778
|
-
throw
|
|
869
|
+
throw new AuthoringError({
|
|
870
|
+
message: `${where}: the variable ${ref.label} is not bound by a relation atom of the rule`
|
|
871
|
+
})
|
|
779
872
|
}
|
|
780
873
|
}
|
|
781
874
|
|
|
782
875
|
function assertInterval(where: string, ref: AnyVar): void {
|
|
783
876
|
if (ref.field.kind !== "interval") {
|
|
784
|
-
throw
|
|
785
|
-
`${where}: ${ref.label} is not interval-typed — the measure is defined over interval-typed variables only`
|
|
786
|
-
)
|
|
877
|
+
throw new AuthoringError({
|
|
878
|
+
message: `${where}: ${ref.label} is not interval-typed — the measure is defined over interval-typed variables only`
|
|
879
|
+
})
|
|
787
880
|
}
|
|
788
881
|
}
|
|
789
882
|
|
|
@@ -795,17 +888,41 @@ function assertNotClosed(where: string, position: string, ref: AnyVar): void {
|
|
|
795
888
|
}
|
|
796
889
|
|
|
797
890
|
function assertNumeric(where: string, position: string, ref: AnyVar): void {
|
|
798
|
-
if (ref.field.kind !== "u64" && ref.field.kind !== "i64") {
|
|
799
|
-
throw
|
|
891
|
+
if (ref.field.kind !== "u64" && ref.field.kind !== "i64" && ref.field.kind !== "f64") {
|
|
892
|
+
throw new AuthoringError({
|
|
893
|
+
message: `${where}: ${position} ${ref.label} is ${ref.field.kind}, not numeric — a fold reads u64/i64/f64 only`
|
|
894
|
+
})
|
|
800
895
|
}
|
|
801
896
|
}
|
|
802
897
|
|
|
898
|
+
/**
|
|
899
|
+
* The head column's slot: a projected variable keeps its mint slot; an
|
|
900
|
+
* aggregate output is a NEW derived scalar — its descriptor follows the
|
|
901
|
+
* aggregate's typing (count is u64, mean is f64, sum/min/max keep the
|
|
902
|
+
* input's scalar kind, pack keeps the interval kind) and its class is
|
|
903
|
+
* BARE: a derived value is not the carrier the class law tracked.
|
|
904
|
+
*/
|
|
803
905
|
function findColumnSlotOf(context: ChainContext, column: FindColumn): ClassedField | undefined {
|
|
804
906
|
const entry = column.entry
|
|
805
907
|
if (entry.kind === "var") {
|
|
806
908
|
return mintSlotOf(context, entry.over)
|
|
807
909
|
}
|
|
808
|
-
|
|
910
|
+
if (entry.kind === "compute") {
|
|
911
|
+
return { field: computeFieldOf(entry.result), class: undefined }
|
|
912
|
+
}
|
|
913
|
+
const agg = entry.agg
|
|
914
|
+
switch (agg.op) {
|
|
915
|
+
case "count":
|
|
916
|
+
return { field: u64Field, class: undefined }
|
|
917
|
+
case "fold": {
|
|
918
|
+
if (agg.fold === "mean") {
|
|
919
|
+
return { field: f64Field, class: undefined }
|
|
920
|
+
}
|
|
921
|
+
return { field: agg.over.field, class: undefined }
|
|
922
|
+
}
|
|
923
|
+
case "pack":
|
|
924
|
+
return { field: agg.over.field, class: undefined }
|
|
925
|
+
}
|
|
809
926
|
}
|
|
810
927
|
|
|
811
928
|
function validateColumn(context: ChainContext, bound: ReadonlySet<AnyVar>, column: FindColumn): void {
|
|
@@ -815,6 +932,15 @@ function validateColumn(context: ChainContext, bound: ReadonlySet<AnyVar>, colum
|
|
|
815
932
|
assertBound(where, bound, entry.over)
|
|
816
933
|
return
|
|
817
934
|
}
|
|
935
|
+
if (entry.kind === "compute") {
|
|
936
|
+
// Kind agreement, closed-reference and depth walls hold at
|
|
937
|
+
// construction (#query/compute.ts); boundness is a RULE property,
|
|
938
|
+
// judged here like every other term position.
|
|
939
|
+
for (const ref of computeVarsOf(entry.expr)) {
|
|
940
|
+
assertBound(where, bound, ref)
|
|
941
|
+
}
|
|
942
|
+
return
|
|
943
|
+
}
|
|
818
944
|
const agg = entry.agg
|
|
819
945
|
switch (agg.op) {
|
|
820
946
|
case "count":
|
|
@@ -823,6 +949,9 @@ function validateColumn(context: ChainContext, bound: ReadonlySet<AnyVar>, colum
|
|
|
823
949
|
assertBound(where, bound, agg.over)
|
|
824
950
|
assertNotClosed(where, `the ${agg.fold} input`, agg.over)
|
|
825
951
|
assertNumeric(where, `the ${agg.fold} input`, agg.over)
|
|
952
|
+
if (agg.fold === "mean" && agg.over.field.kind !== "f64") {
|
|
953
|
+
throw new AuthoringError({ message: `${where}: mean requires an f64 input; cast integers explicitly` })
|
|
954
|
+
}
|
|
826
955
|
return
|
|
827
956
|
}
|
|
828
957
|
case "pack":
|
|
@@ -850,9 +979,9 @@ function validateCond(context: ChainContext, bound: ReadonlySet<AnyVar>, cond: C
|
|
|
850
979
|
const lhs = mintSlotOf(context, cond.lhs.ref)
|
|
851
980
|
const rhs = mintSlotOf(context, cond.rhs.ref)
|
|
852
981
|
if (!fieldJoins(lhs, rhs)) {
|
|
853
|
-
throw
|
|
854
|
-
`${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)`
|
|
855
|
-
)
|
|
982
|
+
throw new AuthoringError({
|
|
983
|
+
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)`
|
|
984
|
+
})
|
|
856
985
|
}
|
|
857
986
|
}
|
|
858
987
|
return
|
|
@@ -882,23 +1011,23 @@ function validateInterior(
|
|
|
882
1011
|
})
|
|
883
1012
|
for (const key of keys) {
|
|
884
1013
|
if (!headNames.includes(key)) {
|
|
885
|
-
throw
|
|
886
|
-
`${label}: interior ${item.target.name} binds ${key}, not a head column of ${item.target.name} (head columns: ${headNames.join(", ")})`
|
|
887
|
-
)
|
|
1014
|
+
throw new AuthoringError({
|
|
1015
|
+
message: `${label}: interior ${item.target.name} binds ${key}, not a head column of ${item.target.name} (head columns: ${headNames.join(", ")})`
|
|
1016
|
+
})
|
|
888
1017
|
}
|
|
889
1018
|
}
|
|
890
1019
|
for (const name of headNames) {
|
|
891
1020
|
if (!keys.includes(name)) {
|
|
892
|
-
throw
|
|
893
|
-
`${label}: interior ${item.target.name} omits the head column ${name} — an interior join binds every head column of ${item.target.name}`
|
|
894
|
-
)
|
|
1021
|
+
throw new AuthoringError({
|
|
1022
|
+
message: `${label}: interior ${item.target.name} omits the head column ${name} — an interior join binds every head column of ${item.target.name}`
|
|
1023
|
+
})
|
|
895
1024
|
}
|
|
896
1025
|
}
|
|
897
1026
|
for (const binding of item.bindings) {
|
|
898
1027
|
if (item.kind === "negatedInterior" && !bound.has(binding.ref)) {
|
|
899
|
-
throw
|
|
900
|
-
`${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)`
|
|
901
|
-
)
|
|
1028
|
+
throw new AuthoringError({
|
|
1029
|
+
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)`
|
|
1030
|
+
})
|
|
902
1031
|
}
|
|
903
1032
|
const headColumn = headColumns.find(function byName(column) {
|
|
904
1033
|
return column.name === binding.key
|
|
@@ -908,9 +1037,9 @@ function validateInterior(
|
|
|
908
1037
|
}
|
|
909
1038
|
const mint = mintSlotOf(context, binding.ref)
|
|
910
1039
|
if (!fieldJoins(headColumn.slot, mint)) {
|
|
911
|
-
throw
|
|
912
|
-
`${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`
|
|
913
|
-
)
|
|
1040
|
+
throw new AuthoringError({
|
|
1041
|
+
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`
|
|
1042
|
+
})
|
|
914
1043
|
}
|
|
915
1044
|
}
|
|
916
1045
|
}
|
|
@@ -918,7 +1047,7 @@ function validateInterior(
|
|
|
918
1047
|
function completeRule(context: ChainContext, state: RuleBuildState, rawColumns: readonly FindColumn[]): RuleData {
|
|
919
1048
|
const label = contextLabel(context)
|
|
920
1049
|
if (rawColumns.length === 0) {
|
|
921
|
-
throw
|
|
1050
|
+
throw new AuthoringError({ message: `${label}: a find needs at least one entry` })
|
|
922
1051
|
}
|
|
923
1052
|
const columns = rawColumns.map(function enrichColumn(column): FindColumn {
|
|
924
1053
|
assertDeclarationOrderKey(`${label} find column`, column.name)
|
|
@@ -930,9 +1059,9 @@ function completeRule(context: ChainContext, state: RuleBuildState, rawColumns:
|
|
|
930
1059
|
if (item.kind === "negated") {
|
|
931
1060
|
for (const binding of item.atom.bindings) {
|
|
932
1061
|
if (binding.term.kind === "var" && !state.bound.has(binding.term.ref)) {
|
|
933
|
-
throw
|
|
934
|
-
`${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)`
|
|
935
|
-
)
|
|
1062
|
+
throw new AuthoringError({
|
|
1063
|
+
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)`
|
|
1064
|
+
})
|
|
936
1065
|
}
|
|
937
1066
|
}
|
|
938
1067
|
}
|
|
@@ -951,14 +1080,14 @@ function makeRuleValue<Row, P extends ParamsRecord>(rule: RuleData): RuleValue<R
|
|
|
951
1080
|
}
|
|
952
1081
|
|
|
953
1082
|
interface RawChain {
|
|
954
|
-
match(relation: MatchOwner, bindings: Readonly<Record<string, unknown>>): RawChain
|
|
1083
|
+
match(relation: MatchOwner | AnyQuery, bindings: Readonly<Record<string, unknown>>): RawChain
|
|
955
1084
|
where(cond: AnyCond): RawChain
|
|
956
1085
|
interior(name: string, bindings: Readonly<Record<string, unknown>>): RawChain
|
|
957
1086
|
find(entries: Readonly<Record<string, unknown>>): RuleValue<never, never>
|
|
958
1087
|
}
|
|
959
1088
|
|
|
960
1089
|
interface RawScope extends TermOps {
|
|
961
|
-
match(relation: MatchOwner, bindings: Readonly<Record<string, unknown>>): RawChain
|
|
1090
|
+
match(relation: MatchOwner | AnyQuery, bindings: Readonly<Record<string, unknown>>): RawChain
|
|
962
1091
|
interior(name: string, bindings: Readonly<Record<string, unknown>>): RawChain
|
|
963
1092
|
}
|
|
964
1093
|
|
|
@@ -997,26 +1126,30 @@ function lookupDerived(context: ChainContext, name: string): DerivedTable {
|
|
|
997
1126
|
})
|
|
998
1127
|
if (interior !== undefined) {
|
|
999
1128
|
if (context.kind === "interior" && name === context.self) {
|
|
1000
|
-
throw
|
|
1001
|
-
`interior ${name}: an interior does not read itself — declaration order is topological (a self-read is InteriorNotPrior)`
|
|
1002
|
-
)
|
|
1129
|
+
throw new AuthoringError({
|
|
1130
|
+
message: `interior ${name}: an interior does not read itself — declaration order is topological (a self-read is InteriorNotPrior)`
|
|
1131
|
+
})
|
|
1003
1132
|
}
|
|
1004
1133
|
return interior
|
|
1005
1134
|
}
|
|
1006
1135
|
const rec = "rec" in context ? context.rec : undefined
|
|
1007
1136
|
if (rec !== undefined && rec.name === name) {
|
|
1008
1137
|
if (context.kind === "interior") {
|
|
1009
|
-
throw
|
|
1138
|
+
throw new AuthoringError({
|
|
1139
|
+
message: `interior ${context.self}: interiors cannot read the rec — this cut's interiors are a prefix`
|
|
1140
|
+
})
|
|
1010
1141
|
}
|
|
1011
1142
|
if (context.kind === "rec-base") {
|
|
1012
|
-
throw
|
|
1143
|
+
throw new AuthoringError({
|
|
1144
|
+
message: `rec ${rec.name}: a base arm does not read the rec — self-atoms belong on rec arms`
|
|
1145
|
+
})
|
|
1013
1146
|
}
|
|
1014
1147
|
if (!isRecHead(rec)) {
|
|
1015
|
-
throw
|
|
1148
|
+
throw new AuthoringError({ message: `rec ${rec.name}: rec arms resolve the rec head after base arms seal it` })
|
|
1016
1149
|
}
|
|
1017
1150
|
return rec
|
|
1018
1151
|
}
|
|
1019
|
-
throw
|
|
1152
|
+
throw new AuthoringError({ message: `${contextLabel(context)}: no derived table named ${name} is in scope` })
|
|
1020
1153
|
}
|
|
1021
1154
|
|
|
1022
1155
|
function interiorAdvance(
|
|
@@ -1028,6 +1161,159 @@ function interiorAdvance(
|
|
|
1028
1161
|
return advanceInterior(state, lookupDerived(context, name), bindings, "interior")
|
|
1029
1162
|
}
|
|
1030
1163
|
|
|
1164
|
+
/**
|
|
1165
|
+
* Imported query templates as relation-expression sources (chapter 34's
|
|
1166
|
+
* nonrecursive composition). The imported CQ's whole body — its inner
|
|
1167
|
+
* interiors plus its main rules as one derived table — is SPLICED into the
|
|
1168
|
+
* consuming query's interior roster at assembly ({@link makeRawQuery});
|
|
1169
|
+
* naming does not materialize anything, and the imported template keeps its
|
|
1170
|
+
* owned immutable IR. One entry is minted per imported query VALUE, so two
|
|
1171
|
+
* references to the same template read one derived table.
|
|
1172
|
+
*/
|
|
1173
|
+
interface ImportEntry {
|
|
1174
|
+
readonly table: InteriorData
|
|
1175
|
+
readonly inner: readonly InteriorData[]
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
const importEntries = new WeakMap<object, ImportEntry>()
|
|
1179
|
+
const importTables = new WeakSet<InteriorData>()
|
|
1180
|
+
|
|
1181
|
+
function importEntryOf(context: ChainContext, imported: AnyQuery): ImportEntry {
|
|
1182
|
+
const cached = importEntries.get(imported)
|
|
1183
|
+
if (cached !== undefined) {
|
|
1184
|
+
if (imported.schema !== context.theory) {
|
|
1185
|
+
throw new AuthoringError({
|
|
1186
|
+
message: `${contextLabel(context)}: the imported query belongs to schema ${imported.schema.name}, not ${context.theory.name} — templates compose within one schema`
|
|
1187
|
+
})
|
|
1188
|
+
}
|
|
1189
|
+
return cached
|
|
1190
|
+
}
|
|
1191
|
+
if (imported.schema !== context.theory) {
|
|
1192
|
+
throw new AuthoringError({
|
|
1193
|
+
message: `${contextLabel(context)}: the imported query belongs to schema ${imported.schema.name}, not ${context.theory.name} — templates compose within one schema`
|
|
1194
|
+
})
|
|
1195
|
+
}
|
|
1196
|
+
if (imported.data.kind === "reach") {
|
|
1197
|
+
throw new AuthoringError({
|
|
1198
|
+
message: `${contextLabel(context)}: a recursive query is not importable as a relation expression yet — declare the recursion on the consuming query (C05 boundary, recorded)`
|
|
1199
|
+
})
|
|
1200
|
+
}
|
|
1201
|
+
const userParams = imported.data.params.filter(function userSupplied(entry) {
|
|
1202
|
+
return entry.membership === undefined
|
|
1203
|
+
})
|
|
1204
|
+
if (userParams.length > 0) {
|
|
1205
|
+
throw new AuthoringError({
|
|
1206
|
+
message: `${contextLabel(context)}: the imported query takes parameters (${userParams
|
|
1207
|
+
.map((entry) => entry.name)
|
|
1208
|
+
.join(", ")}) — bind an import's meaning at declaration; parameterized imports are not supported`
|
|
1209
|
+
})
|
|
1210
|
+
}
|
|
1211
|
+
const facade = importFacadeOf(imported)
|
|
1212
|
+
const table: InteriorData = Object.freeze({
|
|
1213
|
+
name: facade.owner.name,
|
|
1214
|
+
finds: imported.data.finds,
|
|
1215
|
+
rules: imported.data.rules
|
|
1216
|
+
})
|
|
1217
|
+
const entry: ImportEntry = Object.freeze({ table, inner: imported.data.interiors })
|
|
1218
|
+
importEntries.set(imported, entry)
|
|
1219
|
+
importTables.add(table)
|
|
1220
|
+
importInner.set(table, entry.inner)
|
|
1221
|
+
return entry
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
const importInner = new WeakMap<InteriorData, readonly InteriorData[]>()
|
|
1225
|
+
|
|
1226
|
+
/**
|
|
1227
|
+
* Every import table the assembled query references, in first-reference
|
|
1228
|
+
* order (declared interiors first, then rec arms, then main rules — the
|
|
1229
|
+
* lowering walk), deduplicated by table identity.
|
|
1230
|
+
*/
|
|
1231
|
+
function referencedImports(
|
|
1232
|
+
interiors: readonly InteriorData[],
|
|
1233
|
+
rec: RecData | undefined,
|
|
1234
|
+
rules: readonly RuleData[]
|
|
1235
|
+
): readonly InteriorData[] {
|
|
1236
|
+
const seen = new Set<InteriorData>()
|
|
1237
|
+
const out: InteriorData[] = []
|
|
1238
|
+
function walk(ruleList: readonly RuleData[]): void {
|
|
1239
|
+
for (const rule of ruleList) {
|
|
1240
|
+
for (const item of rule.items) {
|
|
1241
|
+
if (item.kind !== "interior" && item.kind !== "negatedInterior") {
|
|
1242
|
+
continue
|
|
1243
|
+
}
|
|
1244
|
+
const target = item.target as InteriorData
|
|
1245
|
+
if (importTables.has(target) && !seen.has(target)) {
|
|
1246
|
+
seen.add(target)
|
|
1247
|
+
out.push(target)
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
for (const interior of interiors) {
|
|
1253
|
+
walk(interior.rules)
|
|
1254
|
+
}
|
|
1255
|
+
if (rec !== undefined) {
|
|
1256
|
+
walk(rec.base)
|
|
1257
|
+
walk(rec.rec)
|
|
1258
|
+
}
|
|
1259
|
+
walk(rules)
|
|
1260
|
+
return out
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
/**
|
|
1264
|
+
* Splices referenced imports ahead of the declared interiors: each import's
|
|
1265
|
+
* inner interiors first (its rules read them), then the import's own table.
|
|
1266
|
+
* Interior prior-ness is preserved — declared interiors never read imports
|
|
1267
|
+
* they could not have named, and main/rec rules may read anything earlier.
|
|
1268
|
+
* Duplicate names across the final roster refuse.
|
|
1269
|
+
*/
|
|
1270
|
+
function spliceImports(
|
|
1271
|
+
interiors: readonly InteriorData[],
|
|
1272
|
+
rec: RecData | undefined,
|
|
1273
|
+
rules: readonly RuleData[]
|
|
1274
|
+
): readonly InteriorData[] {
|
|
1275
|
+
const imports = referencedImports(interiors, rec, rules)
|
|
1276
|
+
if (imports.length === 0) {
|
|
1277
|
+
return interiors
|
|
1278
|
+
}
|
|
1279
|
+
const spliced: InteriorData[] = []
|
|
1280
|
+
const names = new Set<string>()
|
|
1281
|
+
const added = new Set<InteriorData>()
|
|
1282
|
+
function add(table: InteriorData): void {
|
|
1283
|
+
if (added.has(table)) {
|
|
1284
|
+
return
|
|
1285
|
+
}
|
|
1286
|
+
if (names.has(table.name)) {
|
|
1287
|
+
throw new AuthoringError({
|
|
1288
|
+
message: `query: the derived-table name ${table.name} appears twice after import splicing — rename the colliding interior (names are unique)`
|
|
1289
|
+
})
|
|
1290
|
+
}
|
|
1291
|
+
added.add(table)
|
|
1292
|
+
names.add(table.name)
|
|
1293
|
+
spliced.push(table)
|
|
1294
|
+
}
|
|
1295
|
+
for (const table of imports) {
|
|
1296
|
+
for (const inner of importInner.get(table) ?? []) {
|
|
1297
|
+
add(inner)
|
|
1298
|
+
}
|
|
1299
|
+
add(table)
|
|
1300
|
+
}
|
|
1301
|
+
for (const declared of interiors) {
|
|
1302
|
+
add(declared)
|
|
1303
|
+
}
|
|
1304
|
+
return Object.freeze(spliced)
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
function importAdvance(
|
|
1308
|
+
context: ChainContext,
|
|
1309
|
+
state: RuleBuildState,
|
|
1310
|
+
imported: AnyQuery,
|
|
1311
|
+
bindings: Readonly<Record<string, unknown>>
|
|
1312
|
+
): RuleBuildState {
|
|
1313
|
+
const entry = importEntryOf(context, imported)
|
|
1314
|
+
return advanceInterior(state, entry.table, bindings, "interior")
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1031
1317
|
function notInteriorAdvance(
|
|
1032
1318
|
context: ChainContext,
|
|
1033
1319
|
state: RuleBuildState,
|
|
@@ -1035,25 +1321,28 @@ function notInteriorAdvance(
|
|
|
1035
1321
|
bindings: Readonly<Record<string, unknown>>
|
|
1036
1322
|
): RuleBuildState {
|
|
1037
1323
|
if (context.kind === "rec-base" || context.kind === "rec-arm") {
|
|
1038
|
-
throw
|
|
1039
|
-
`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`
|
|
1040
|
-
)
|
|
1324
|
+
throw new AuthoringError({
|
|
1325
|
+
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`
|
|
1326
|
+
})
|
|
1041
1327
|
}
|
|
1042
1328
|
return advanceInterior(state, lookupDerived(context, name), bindings, "negatedInterior")
|
|
1043
1329
|
}
|
|
1044
1330
|
|
|
1045
1331
|
function findColumns(context: ChainContext, entries: Readonly<Record<string, unknown>>): FindColumn[] {
|
|
1046
1332
|
const columns: FindColumn[] = []
|
|
1047
|
-
|
|
1333
|
+
// Nonrecursive derived stages (interiors) may emit aggregate/computed
|
|
1334
|
+
// outputs (C05: "do not leave Interior.rules projection only"); ONLY the
|
|
1335
|
+
// recursive feedback cycle stays projection-only — no aggregate,
|
|
1336
|
+
// arithmetic-created value or negation flows through it.
|
|
1337
|
+
const recName = context.kind === "rec-base" || context.kind === "rec-arm" ? context.self.name : undefined
|
|
1048
1338
|
for (const [name, entry] of Object.entries(entries)) {
|
|
1049
1339
|
if (entry === undefined) {
|
|
1050
1340
|
continue
|
|
1051
1341
|
}
|
|
1052
|
-
if (
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
)
|
|
1342
|
+
if (recName !== undefined && !(isTerm(entry) && entry[term] === "var")) {
|
|
1343
|
+
throw new AuthoringError({
|
|
1344
|
+
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)`
|
|
1345
|
+
})
|
|
1057
1346
|
}
|
|
1058
1347
|
columns.push(findColumnOf(name, entry))
|
|
1059
1348
|
}
|
|
@@ -1063,7 +1352,10 @@ function findColumns(context: ChainContext, entries: Readonly<Record<string, unk
|
|
|
1063
1352
|
function makeRawChain(context: ChainContext, state: RuleBuildState): RawChain {
|
|
1064
1353
|
const chain: RawChain = {
|
|
1065
1354
|
match(relation, bindings) {
|
|
1066
|
-
|
|
1355
|
+
if (isImportedSource(relation)) {
|
|
1356
|
+
return makeRawChain(context, importAdvance(context, state, relation as unknown as AnyQuery, bindings))
|
|
1357
|
+
}
|
|
1358
|
+
return makeRawChain(context, advanceMatch(context, state, relation as MatchOwner, bindings))
|
|
1067
1359
|
},
|
|
1068
1360
|
where(cond) {
|
|
1069
1361
|
return makeRawChain(context, advanceWhere(context, state, cond))
|
|
@@ -1083,7 +1375,10 @@ function makeRawScope(context: ChainContext): RawScope {
|
|
|
1083
1375
|
const scope: RawScope = {
|
|
1084
1376
|
...termOps,
|
|
1085
1377
|
match(relation, bindings) {
|
|
1086
|
-
|
|
1378
|
+
if (isImportedSource(relation)) {
|
|
1379
|
+
return makeRawChain(context, importAdvance(context, EMPTY_RULE, relation as unknown as AnyQuery, bindings))
|
|
1380
|
+
}
|
|
1381
|
+
return makeRawChain(context, advanceMatch(context, EMPTY_RULE, relation as MatchOwner, bindings))
|
|
1087
1382
|
},
|
|
1088
1383
|
interior(name, bindings) {
|
|
1089
1384
|
return makeRawChain(context, interiorAdvance(context, EMPTY_RULE, name, bindings))
|
|
@@ -1112,7 +1407,7 @@ function makeQueryRuleScope<Rels extends SchemaRelations, Classes extends Schema
|
|
|
1112
1407
|
): QueryRuleScope<Rels, Classes> {
|
|
1113
1408
|
const raw = makeRawScope({ kind: "query", classes: theory.classes, theory, ...env })
|
|
1114
1409
|
if (!isTypedScope<QueryRuleScope<Rels, Classes>>(raw)) {
|
|
1115
|
-
throw
|
|
1410
|
+
throw new SdkInvariantError({ message: "query rule builder construction incomplete" })
|
|
1116
1411
|
}
|
|
1117
1412
|
return raw
|
|
1118
1413
|
}
|
|
@@ -1124,7 +1419,7 @@ function makeInteriorRuleScope<Rels extends SchemaRelations, Classes extends Sch
|
|
|
1124
1419
|
): InteriorRuleScope<Rels, Classes> {
|
|
1125
1420
|
const raw = makeRawScope({ kind: "interior", self, classes: theory.classes, theory, ...env })
|
|
1126
1421
|
if (!isTypedScope<InteriorRuleScope<Rels, Classes>>(raw)) {
|
|
1127
|
-
throw
|
|
1422
|
+
throw new SdkInvariantError({ message: "interior rule builder construction incomplete" })
|
|
1128
1423
|
}
|
|
1129
1424
|
return raw
|
|
1130
1425
|
}
|
|
@@ -1164,7 +1459,7 @@ function makeRecRuleScope<Rels extends SchemaRelations, Classes extends SchemaCl
|
|
|
1164
1459
|
...env
|
|
1165
1460
|
})
|
|
1166
1461
|
if (!isTypedScope<RecRuleScope<Rels, Classes>>(raw)) {
|
|
1167
|
-
throw
|
|
1462
|
+
throw new SdkInvariantError({ message: "rec rule builder construction incomplete" })
|
|
1168
1463
|
}
|
|
1169
1464
|
return raw
|
|
1170
1465
|
}
|
|
@@ -1178,6 +1473,9 @@ function headSignature(column: FindColumn): string {
|
|
|
1178
1473
|
if (entry.kind === "var") {
|
|
1179
1474
|
return `${column.name}:var`
|
|
1180
1475
|
}
|
|
1476
|
+
if (entry.kind === "compute") {
|
|
1477
|
+
return `${column.name}:compute`
|
|
1478
|
+
}
|
|
1181
1479
|
const agg = entry.agg
|
|
1182
1480
|
if (agg.op === "fold") {
|
|
1183
1481
|
return `${column.name}:${agg.fold}`
|
|
@@ -1230,22 +1528,22 @@ function paramRegistryOf(
|
|
|
1230
1528
|
continue
|
|
1231
1529
|
}
|
|
1232
1530
|
if ((existing.members === undefined) !== (use.members === undefined)) {
|
|
1233
|
-
throw
|
|
1234
|
-
`query param ${use.name} collides with a membership array's registry entry — name the param differently`
|
|
1235
|
-
)
|
|
1531
|
+
throw new AuthoringError({
|
|
1532
|
+
message: `query param ${use.name} collides with a membership array's registry entry — name the param differently`
|
|
1533
|
+
})
|
|
1236
1534
|
}
|
|
1237
1535
|
if (existing.shape !== use.shape) {
|
|
1238
|
-
throw
|
|
1239
|
-
`query param ${use.name} is used both as a ${existing.shape} param and a ${use.shape} param — one name, one shape`
|
|
1240
|
-
)
|
|
1536
|
+
throw new AuthoringError({
|
|
1537
|
+
message: `query param ${use.name} is used both as a ${existing.shape} param and a ${use.shape} param — one name, one shape`
|
|
1538
|
+
})
|
|
1241
1539
|
}
|
|
1242
1540
|
if (existing.anchor !== undefined && use.anchor !== undefined) {
|
|
1243
1541
|
const registered = rosterOf(existing.anchor)
|
|
1244
1542
|
const anchored = rosterOf(use.anchor)
|
|
1245
1543
|
if (registered !== anchored) {
|
|
1246
|
-
throw
|
|
1247
|
-
`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`
|
|
1248
|
-
)
|
|
1544
|
+
throw new AuthoringError({
|
|
1545
|
+
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`
|
|
1546
|
+
})
|
|
1249
1547
|
}
|
|
1250
1548
|
}
|
|
1251
1549
|
if (existing.anchor === undefined && use.anchor !== undefined) {
|
|
@@ -1274,14 +1572,14 @@ function paramRegistryOf(
|
|
|
1274
1572
|
order.map(function entryOf(name): ParamEntry {
|
|
1275
1573
|
const entry = byName.get(name)
|
|
1276
1574
|
if (entry === undefined) {
|
|
1277
|
-
throw
|
|
1575
|
+
throw new AuthoringError({ message: `query param ${name} lost its registry entry` })
|
|
1278
1576
|
}
|
|
1279
1577
|
|
|
1280
1578
|
let membership: QueryParam | undefined
|
|
1281
1579
|
if (entry.members !== undefined) {
|
|
1282
1580
|
const anchor = entry.anchor
|
|
1283
1581
|
if (anchor === undefined) {
|
|
1284
|
-
throw
|
|
1582
|
+
throw new AuthoringError({ message: `query param ${name} lost its membership anchor` })
|
|
1285
1583
|
}
|
|
1286
1584
|
membership = Object.freeze({
|
|
1287
1585
|
kind: "set" as const,
|
|
@@ -1301,6 +1599,7 @@ interface RawQuery {
|
|
|
1301
1599
|
readonly schema: AnySchema
|
|
1302
1600
|
readonly data: QueryData
|
|
1303
1601
|
rule(build: (r: RawScope) => RuleValue<never, never>): RawQuery
|
|
1602
|
+
named(label: string): RawQuery
|
|
1304
1603
|
interior(name: string, ...builds: never[]): never
|
|
1305
1604
|
reach(name: string, arms: never): never
|
|
1306
1605
|
}
|
|
@@ -1316,30 +1615,30 @@ interface RawQuery {
|
|
|
1316
1615
|
function alignedHeadOf(label: string, rules: readonly RuleData[]): readonly FindColumn[] {
|
|
1317
1616
|
const first = rules[0]
|
|
1318
1617
|
if (first === undefined) {
|
|
1319
|
-
throw
|
|
1618
|
+
throw new AuthoringError({ message: `${label} needs at least one rule` })
|
|
1320
1619
|
}
|
|
1321
1620
|
const signature = first.finds.map(headSignature).join(", ")
|
|
1322
1621
|
rules.forEach(function verifyHead(rule, index) {
|
|
1323
1622
|
const candidate = rule.finds.map(headSignature).join(", ")
|
|
1324
1623
|
if (candidate !== signature) {
|
|
1325
|
-
throw
|
|
1326
|
-
`every rule of ${label} derives the same head — rule 0 finds (${signature}), rule ${index} finds (${candidate})`
|
|
1327
|
-
)
|
|
1624
|
+
throw new AuthoringError({
|
|
1625
|
+
message: `every rule of ${label} derives the same head — rule 0 finds (${signature}), rule ${index} finds (${candidate})`
|
|
1626
|
+
})
|
|
1328
1627
|
}
|
|
1329
1628
|
rule.finds.forEach(function verifyClosedSlice(column, position) {
|
|
1330
1629
|
const lead = first.finds[position]
|
|
1331
1630
|
if (lead !== undefined && column.closed !== lead.closed) {
|
|
1332
|
-
throw
|
|
1333
|
-
`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)`
|
|
1334
|
-
)
|
|
1631
|
+
throw new AuthoringError({
|
|
1632
|
+
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)`
|
|
1633
|
+
})
|
|
1335
1634
|
}
|
|
1336
1635
|
if (lead === undefined) {
|
|
1337
1636
|
return
|
|
1338
1637
|
}
|
|
1339
1638
|
if (lead.slot !== undefined && column.slot !== undefined && !headFieldJoins(lead.slot, column.slot)) {
|
|
1340
|
-
throw
|
|
1341
|
-
`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)`
|
|
1342
|
-
)
|
|
1639
|
+
throw new AuthoringError({
|
|
1640
|
+
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)`
|
|
1641
|
+
})
|
|
1343
1642
|
}
|
|
1344
1643
|
})
|
|
1345
1644
|
})
|
|
@@ -1366,9 +1665,9 @@ function alignedHeadOf(label: string, rules: readonly RuleData[]): readonly Find
|
|
|
1366
1665
|
}
|
|
1367
1666
|
|
|
1368
1667
|
function afterMainError(what: string): Error {
|
|
1369
|
-
return
|
|
1370
|
-
`query: ${what} after a main rule is unwritable — declaration order is interiors, then rec, then main`
|
|
1371
|
-
)
|
|
1668
|
+
return new AuthoringError({
|
|
1669
|
+
message: `query: ${what} after a main rule is unwritable — declaration order is interiors, then rec, then main`
|
|
1670
|
+
})
|
|
1372
1671
|
}
|
|
1373
1672
|
|
|
1374
1673
|
function makeRawQuery(
|
|
@@ -1380,12 +1679,13 @@ function makeRawQuery(
|
|
|
1380
1679
|
const mergedFinds = alignedHeadOf("a query", rules)
|
|
1381
1680
|
const first = rules[0]
|
|
1382
1681
|
if (first === undefined) {
|
|
1383
|
-
throw
|
|
1682
|
+
throw new AuthoringError({ message: "a query needs at least one rule" })
|
|
1384
1683
|
}
|
|
1385
1684
|
const env: DerivedEnv = rec === undefined ? { interiors } : { interiors, rec }
|
|
1386
|
-
const
|
|
1685
|
+
const allInteriors = spliceImports(interiors, rec, rules)
|
|
1686
|
+
const frozenInteriors = Object.freeze([...allInteriors])
|
|
1387
1687
|
const frozenRules = Object.freeze([...rules])
|
|
1388
|
-
const params = paramRegistryOf(
|
|
1688
|
+
const params = paramRegistryOf(allInteriors, rec, rules)
|
|
1389
1689
|
const data: QueryData =
|
|
1390
1690
|
rec === undefined
|
|
1391
1691
|
? Object.freeze({
|
|
@@ -1410,6 +1710,12 @@ function makeRawQuery(
|
|
|
1410
1710
|
const built = build(makeRawScope({ kind: "query", classes: theory.classes, theory, ...env }))
|
|
1411
1711
|
return makeRawQuery(theory, interiors, rec, [...rules, built.rule])
|
|
1412
1712
|
},
|
|
1713
|
+
named(label) {
|
|
1714
|
+
// A diagnostic name for composition/tracing — not a schema
|
|
1715
|
+
// relation, a persistent view or a separate CTE type.
|
|
1716
|
+
labelImport(value, label)
|
|
1717
|
+
return value
|
|
1718
|
+
},
|
|
1413
1719
|
interior() {
|
|
1414
1720
|
throw afterMainError("interior")
|
|
1415
1721
|
},
|
|
@@ -1437,7 +1743,7 @@ function collectInterior<Rels extends SchemaRelations, Classes extends SchemaCla
|
|
|
1437
1743
|
builds: readonly InteriorBuild<Rels, Classes>[]
|
|
1438
1744
|
): InteriorData {
|
|
1439
1745
|
if (builds.length === 0) {
|
|
1440
|
-
throw
|
|
1746
|
+
throw new AuthoringError({ message: `query: interior ${name} needs at least one rule` })
|
|
1441
1747
|
}
|
|
1442
1748
|
const rules = builds.map(function buildRule(buildOne) {
|
|
1443
1749
|
return buildOne(makeInteriorRuleScope<Rels, Classes>(theory, env, name)).rule
|
|
@@ -1454,10 +1760,10 @@ function collectRec<Rels extends SchemaRelations, Classes extends SchemaClasses>
|
|
|
1454
1760
|
recBuilds: readonly RecBuild<Rels, Classes>[]
|
|
1455
1761
|
): RecData {
|
|
1456
1762
|
if (baseBuilds.length === 0) {
|
|
1457
|
-
throw
|
|
1763
|
+
throw new AuthoringError({ message: `query: rec ${name} has no base arms` })
|
|
1458
1764
|
}
|
|
1459
1765
|
if (recBuilds.length === 0) {
|
|
1460
|
-
throw
|
|
1766
|
+
throw new AuthoringError({ message: `query: rec ${name} has no rec arms` })
|
|
1461
1767
|
}
|
|
1462
1768
|
const handle: RecHandle = Object.freeze({ name })
|
|
1463
1769
|
const baseEnv: DerivedEnv = { interiors, rec: handle }
|
|
@@ -1467,11 +1773,11 @@ function collectRec<Rels extends SchemaRelations, Classes extends SchemaClasses>
|
|
|
1467
1773
|
const baseFinds = alignedHeadOf(`rec ${name}`, base)
|
|
1468
1774
|
const first = base[0]
|
|
1469
1775
|
if (first === undefined) {
|
|
1470
|
-
throw
|
|
1776
|
+
throw new AuthoringError({ message: `query: rec ${name} has no base arms` })
|
|
1471
1777
|
}
|
|
1472
1778
|
const firstFind = baseFinds[0]
|
|
1473
1779
|
if (firstFind === undefined) {
|
|
1474
|
-
throw
|
|
1780
|
+
throw new AuthoringError({ message: `query: rec ${name} has no head` })
|
|
1475
1781
|
}
|
|
1476
1782
|
const finds: RecHead["finds"] = [firstFind, ...baseFinds.slice(1)]
|
|
1477
1783
|
const head: RecHead = Object.freeze({ name, finds })
|
|
@@ -1482,12 +1788,12 @@ function collectRec<Rels extends SchemaRelations, Classes extends SchemaClasses>
|
|
|
1482
1788
|
const mergedAll = alignedHeadOf(`rec ${name}`, [...base, ...rec])
|
|
1483
1789
|
const mergedFirst = mergedAll[0]
|
|
1484
1790
|
if (mergedFirst === undefined) {
|
|
1485
|
-
throw
|
|
1791
|
+
throw new AuthoringError({ message: `query: rec ${name} has no head` })
|
|
1486
1792
|
}
|
|
1487
1793
|
const sealedFinds: RecHead["finds"] = [mergedFirst, ...mergedAll.slice(1)]
|
|
1488
1794
|
const firstRec = rec[0]
|
|
1489
1795
|
if (firstRec === undefined) {
|
|
1490
|
-
throw
|
|
1796
|
+
throw new AuthoringError({ message: `query: rec ${name} has no rec arms` })
|
|
1491
1797
|
}
|
|
1492
1798
|
const sealedBase: RecData["base"] = [first, ...base.slice(1)]
|
|
1493
1799
|
const sealedRec: RecData["rec"] = [firstRec, ...rec.slice(1)]
|
|
@@ -1515,10 +1821,10 @@ function makeQueryStart<Rels extends SchemaRelations, Classes extends SchemaClas
|
|
|
1515
1821
|
return interior.name === name
|
|
1516
1822
|
})
|
|
1517
1823
|
) {
|
|
1518
|
-
throw
|
|
1824
|
+
throw new AuthoringError({ message: `query: interior ${name} is already declared — names are unique` })
|
|
1519
1825
|
}
|
|
1520
1826
|
if (name.length === 0) {
|
|
1521
|
-
throw
|
|
1827
|
+
throw new AuthoringError({ message: "query: an interior needs a name" })
|
|
1522
1828
|
}
|
|
1523
1829
|
const data = collectInterior(theory, env, name, builds)
|
|
1524
1830
|
return makeQueryStart<Rels, Classes, Flatten<P & BuildsParams<Builds>>>(theory, [...interiors, data])
|
|
@@ -1532,10 +1838,10 @@ function makeQueryStart<Rels extends SchemaRelations, Classes extends SchemaClas
|
|
|
1532
1838
|
return interior.name === name
|
|
1533
1839
|
})
|
|
1534
1840
|
) {
|
|
1535
|
-
throw
|
|
1841
|
+
throw new AuthoringError({ message: `query: interior and rec share the name ${name}` })
|
|
1536
1842
|
}
|
|
1537
1843
|
if (name.length === 0) {
|
|
1538
|
-
throw
|
|
1844
|
+
throw new AuthoringError({ message: "query: reach needs a name" })
|
|
1539
1845
|
}
|
|
1540
1846
|
const data = collectRec(theory, interiors, name, arms.base, arms.rec)
|
|
1541
1847
|
return makeQueryReachStart<Rels, Classes, Flatten<P & BuildsParams<Base> & BuildsParams<Step>>>(
|
|
@@ -1589,14 +1895,30 @@ function taggedHandleId(
|
|
|
1589
1895
|
}
|
|
1590
1896
|
const id = closed.handles.indexOf(value)
|
|
1591
1897
|
if (id < 0) {
|
|
1592
|
-
throw
|
|
1593
|
-
`${context}: "${value}" is not a handle of ${closed.name} — the roster is ${closed.handles.join(", ")}`
|
|
1594
|
-
)
|
|
1898
|
+
throw new AuthoringError({
|
|
1899
|
+
message: `${context}: "${value}" is not a handle of ${closed.name} — the roster is ${closed.handles.join(", ")}`
|
|
1900
|
+
})
|
|
1595
1901
|
}
|
|
1596
1902
|
return { kind: "u64", value: BigInt(id) }
|
|
1597
1903
|
}
|
|
1598
1904
|
|
|
1599
|
-
function taggedAtElementDomain(context: string, element: "u64" | "i64", value: unknown): TaggedValue {
|
|
1905
|
+
function taggedAtElementDomain(context: string, element: "u64" | "i64" | "f64", value: unknown): TaggedValue {
|
|
1906
|
+
if (element === "f64") {
|
|
1907
|
+
if (typeof value === "number") {
|
|
1908
|
+
return { kind: "f64", value }
|
|
1909
|
+
}
|
|
1910
|
+
if (isFloatIntervalValue(value)) {
|
|
1911
|
+
if (Number.isNaN(value.start) || Number.isNaN(value.end) || !(value.start < value.end)) {
|
|
1912
|
+
throw literalShapeError(context, "a nonempty float interval with non-NaN endpoints", value)
|
|
1913
|
+
}
|
|
1914
|
+
return {
|
|
1915
|
+
kind: "intervalF64",
|
|
1916
|
+
start: Object.is(value.start, -0) ? 0 : value.start,
|
|
1917
|
+
end: Object.is(value.end, -0) ? 0 : value.end
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
throw literalShapeError(context, "number (point) or { start, end } numbers (interval)", value)
|
|
1921
|
+
}
|
|
1600
1922
|
if (typeof value === "bigint") {
|
|
1601
1923
|
if (element === "u64") {
|
|
1602
1924
|
return { kind: "u64", value }
|
|
@@ -1636,6 +1958,18 @@ function taggedLiteral(context: string, field: AnyField, value: unknown): Tagged
|
|
|
1636
1958
|
}
|
|
1637
1959
|
return { kind: "i64", value }
|
|
1638
1960
|
}
|
|
1961
|
+
case "f64": {
|
|
1962
|
+
if (typeof value !== "number") {
|
|
1963
|
+
throw literalShapeError(context, "number", value)
|
|
1964
|
+
}
|
|
1965
|
+
return { kind: "f64", value }
|
|
1966
|
+
}
|
|
1967
|
+
case "uuid": {
|
|
1968
|
+
if (!Uuid.isUuid(value)) {
|
|
1969
|
+
throw literalShapeError(context, "a UUID (canonical UUID text)", value)
|
|
1970
|
+
}
|
|
1971
|
+
return { kind: "uuid", value }
|
|
1972
|
+
}
|
|
1639
1973
|
case "str": {
|
|
1640
1974
|
if (typeof value !== "string") {
|
|
1641
1975
|
throw literalShapeError(context, "string", value)
|
|
@@ -1677,6 +2011,9 @@ function taggedCmpLiteral(context: string, sibling: AnyField, value: unknown, op
|
|
|
1677
2011
|
) {
|
|
1678
2012
|
return taggedAtElementDomain(context, sibling.kind, value)
|
|
1679
2013
|
}
|
|
2014
|
+
if (op === "pointIn" && rosterOf(sibling) === undefined && sibling.kind === "f64" && isFloatIntervalValue(value)) {
|
|
2015
|
+
return taggedAtElementDomain(context, "f64", value)
|
|
2016
|
+
}
|
|
1680
2017
|
return taggedLiteral(context, sibling, value)
|
|
1681
2018
|
}
|
|
1682
2019
|
|
|
@@ -1710,7 +2047,7 @@ function freshVarIds(): VarIds {
|
|
|
1710
2047
|
function paramIdOf(ctx: LowerContext, name: string): number {
|
|
1711
2048
|
const id = ctx.paramIds.get(name)
|
|
1712
2049
|
if (id === undefined) {
|
|
1713
|
-
throw
|
|
2050
|
+
throw new AuthoringError({ message: `query lowering: param ${name} is not in the query's registry` })
|
|
1714
2051
|
}
|
|
1715
2052
|
return id
|
|
1716
2053
|
}
|
|
@@ -1718,13 +2055,13 @@ function paramIdOf(ctx: LowerContext, name: string): number {
|
|
|
1718
2055
|
function lowerAtom(ctx: LowerContext, atom: AtomData, ids: VarIds): AtomIr {
|
|
1719
2056
|
const member = ctx.theory.relations[atom.relation.name]
|
|
1720
2057
|
if (member !== atom.relation) {
|
|
1721
|
-
throw
|
|
1722
|
-
`query lowering: relation ${atom.relation.name} is not the relation value schema ${ctx.theory.name} declares`
|
|
1723
|
-
)
|
|
2058
|
+
throw new AuthoringError({
|
|
2059
|
+
message: `query lowering: relation ${atom.relation.name} is not the relation value schema ${ctx.theory.name} declares`
|
|
2060
|
+
})
|
|
1724
2061
|
}
|
|
1725
2062
|
const relationId = ctx.relationIds.get(atom.relation.name)
|
|
1726
2063
|
if (relationId === undefined) {
|
|
1727
|
-
throw
|
|
2064
|
+
throw new AuthoringError({ message: `query lowering: relation ${atom.relation.name} has no ordinal` })
|
|
1728
2065
|
}
|
|
1729
2066
|
const ordered = sealedFieldsOf(atom.relation)
|
|
1730
2067
|
const bindings: Array<readonly [number, TermIr]> = atom.bindings.map(function lowerBinding(binding) {
|
|
@@ -1732,7 +2069,9 @@ function lowerAtom(ctx: LowerContext, atom: AtomData, ids: VarIds): AtomIr {
|
|
|
1732
2069
|
return candidate.name === binding.field
|
|
1733
2070
|
})
|
|
1734
2071
|
if (ordinal < 0) {
|
|
1735
|
-
throw
|
|
2072
|
+
throw new AuthoringError({
|
|
2073
|
+
message: `query lowering: relation ${atom.relation.name} has no field ${binding.field}`
|
|
2074
|
+
})
|
|
1736
2075
|
}
|
|
1737
2076
|
return [ordinal, lowerBindingTerm(ctx, `${atom.relation.name}.${binding.field}`, binding, ids)] as const
|
|
1738
2077
|
})
|
|
@@ -1769,17 +2108,17 @@ function lowerInteriorAtom(
|
|
|
1769
2108
|
): AtomIr {
|
|
1770
2109
|
const interior = ctx.interiorIds.get(target.name)
|
|
1771
2110
|
if (interior === undefined) {
|
|
1772
|
-
throw
|
|
2111
|
+
throw new AuthoringError({ message: `query lowering: derived table ${target.name} was not declared on this query` })
|
|
1773
2112
|
}
|
|
1774
2113
|
if (target.finds.length === 0) {
|
|
1775
|
-
throw
|
|
2114
|
+
throw new AuthoringError({ message: `query lowering: derived table ${target.name} has no head` })
|
|
1776
2115
|
}
|
|
1777
2116
|
const irBindings: Array<readonly [number, TermIr]> = target.finds.map(function lowerPosition(column, position) {
|
|
1778
2117
|
const binding = bindings.find(function byKey(candidate) {
|
|
1779
2118
|
return candidate.key === column.name
|
|
1780
2119
|
})
|
|
1781
2120
|
if (binding === undefined) {
|
|
1782
|
-
throw
|
|
2121
|
+
throw new AuthoringError({ message: `query lowering: interior ${target.name} omits head column ${column.name}` })
|
|
1783
2122
|
}
|
|
1784
2123
|
return [position, { kind: "var", var: ids.of(binding.ref) } as const] as const
|
|
1785
2124
|
})
|
|
@@ -1797,9 +2136,9 @@ function lowerCmpTerm(ctx: LowerContext, side: CmpTermData, sibling: CmpTermData
|
|
|
1797
2136
|
case "literal": {
|
|
1798
2137
|
const anchor = cmpAnchorOf(ctx, sibling)
|
|
1799
2138
|
if (anchor === undefined) {
|
|
1800
|
-
throw
|
|
1801
|
-
"query lowering: a comparison literal needs a bound-variable or anchored-param sibling to type it"
|
|
1802
|
-
)
|
|
2139
|
+
throw new AuthoringError({
|
|
2140
|
+
message: "query lowering: a comparison literal needs a bound-variable or anchored-param sibling to type it"
|
|
2141
|
+
})
|
|
1803
2142
|
}
|
|
1804
2143
|
return { kind: "literal", value: taggedCmpLiteral("comparison literal", anchor, side.value, op) }
|
|
1805
2144
|
}
|
|
@@ -1843,10 +2182,56 @@ function lowerCondition(ctx: LowerContext, cond: CondData, ids: VarIds): Conditi
|
|
|
1843
2182
|
}
|
|
1844
2183
|
}
|
|
1845
2184
|
|
|
2185
|
+
/**
|
|
2186
|
+
* Lowers one computed scalar node to the C05 wire (`#native.ts`'s
|
|
2187
|
+
* `ScalarExprIr`): the shared grammar arms pass through 1:1. Cached
|
|
2188
|
+
* construction depth is the bound; this walk emits wire only and does
|
|
2189
|
+
* not re-judge kinds (L14/native compile owns binding).
|
|
2190
|
+
*/
|
|
2191
|
+
function lowerComputeNode(node: QueryNode, ids: VarIds): ScalarExprIr {
|
|
2192
|
+
if (node.depth > MAX_COMPUTE_DEPTH) {
|
|
2193
|
+
throw new AuthoringError({
|
|
2194
|
+
message: `query lowering: a compute expression is deeper than ${MAX_COMPUTE_DEPTH} nodes (the engine's scalar depth bound)`
|
|
2195
|
+
})
|
|
2196
|
+
}
|
|
2197
|
+
return lowerComputeGrammar(node, ids)
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
function lowerComputeGrammar(node: QueryNode, ids: VarIds): ScalarExprIr {
|
|
2201
|
+
switch (node.kind) {
|
|
2202
|
+
case "var":
|
|
2203
|
+
return { kind: "var", var: ids.of(node.ref as AnyVar) }
|
|
2204
|
+
case "literal":
|
|
2205
|
+
return { kind: "literal", value: literalWireOf(node.value) }
|
|
2206
|
+
case "negate":
|
|
2207
|
+
case "isNaN":
|
|
2208
|
+
case "isFinite":
|
|
2209
|
+
return { kind: node.kind, expr: lowerComputeGrammar(node.expr, ids) }
|
|
2210
|
+
case "cast":
|
|
2211
|
+
return { kind: "cast", cast: node.cast, expr: lowerComputeGrammar(node.expr, ids) }
|
|
2212
|
+
case "add":
|
|
2213
|
+
case "subtract":
|
|
2214
|
+
case "multiply":
|
|
2215
|
+
case "divide":
|
|
2216
|
+
return {
|
|
2217
|
+
kind: node.kind,
|
|
2218
|
+
left: lowerComputeGrammar(node.left, ids),
|
|
2219
|
+
right: lowerComputeGrammar(node.right, ids)
|
|
2220
|
+
}
|
|
2221
|
+
default:
|
|
2222
|
+
throw new AuthoringError({
|
|
2223
|
+
message: "query lowering: a query-var tree cannot carry a source-field leaf — use Compute over bound variables"
|
|
2224
|
+
})
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
|
|
1846
2228
|
function lowerFind(entry: FindEntryData, ids: VarIds): FindTermIr {
|
|
1847
2229
|
if (entry.kind === "var") {
|
|
1848
2230
|
return { kind: "var", var: ids.of(entry.over) }
|
|
1849
2231
|
}
|
|
2232
|
+
if (entry.kind === "compute") {
|
|
2233
|
+
return { kind: "compute", expr: lowerComputeNode(entry.expr, ids) }
|
|
2234
|
+
}
|
|
1850
2235
|
const agg = entry.agg
|
|
1851
2236
|
switch (agg.op) {
|
|
1852
2237
|
case "count":
|
|
@@ -1874,6 +2259,9 @@ function headTermOf(column: FindColumn): HeadTermIr {
|
|
|
1874
2259
|
if (entry.kind === "var") {
|
|
1875
2260
|
return { kind: "var" }
|
|
1876
2261
|
}
|
|
2262
|
+
if (entry.kind === "compute") {
|
|
2263
|
+
return { kind: "compute" }
|
|
2264
|
+
}
|
|
1877
2265
|
return { kind: "aggregate", op: headOpOf(entry.agg) }
|
|
1878
2266
|
}
|
|
1879
2267
|
|
|
@@ -1933,9 +2321,9 @@ function lowerQuery(q: AnyQuery): ParsedQuery {
|
|
|
1933
2321
|
const params = new Map<string, ParamEntry>()
|
|
1934
2322
|
q.data.params.forEach(function assignParamId(entry, index) {
|
|
1935
2323
|
if (entry.anchor === undefined) {
|
|
1936
|
-
throw
|
|
1937
|
-
`query param ${entry.name} has no field-anchored use — bind it in an atom or compare it against a bound variable`
|
|
1938
|
-
)
|
|
2324
|
+
throw new AuthoringError({
|
|
2325
|
+
message: `query param ${entry.name} has no field-anchored use — bind it in an atom or compare it against a bound variable`
|
|
2326
|
+
})
|
|
1939
2327
|
}
|
|
1940
2328
|
paramIds.set(entry.name, index)
|
|
1941
2329
|
params.set(entry.name, entry)
|