@bjornpagen/bumbledb 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COOKBOOK.md +155 -136
- package/README.md +3 -7
- package/dist/capacity.d.ts +14 -1
- package/dist/capacity.d.ts.map +1 -1
- package/dist/capacity.js.map +1 -1
- package/dist/db.d.ts +77 -109
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +121 -339
- package/dist/db.js.map +1 -1
- package/dist/index.d.ts +11 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -10
- package/dist/index.js.map +1 -1
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +8 -1
- package/dist/lower.js.map +1 -1
- package/dist/native.d.ts +69 -50
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js.map +1 -1
- package/dist/query/atom.d.ts +162 -122
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +26 -22
- package/dist/query/atom.js.map +1 -1
- package/dist/query/find.d.ts +18 -35
- package/dist/query/find.d.ts.map +1 -1
- package/dist/query/find.js +13 -32
- package/dist/query/find.js.map +1 -1
- package/dist/query/lower.d.ts +113 -122
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +336 -260
- package/dist/query/lower.js.map +1 -1
- package/dist/query/parse-ir.d.ts +12 -0
- package/dist/query/parse-ir.d.ts.map +1 -0
- package/dist/query/parse-ir.js +71 -0
- package/dist/query/parse-ir.js.map +1 -0
- package/dist/query/run.d.ts +2 -2
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +2 -13
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +4 -16
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +1 -6
- package/dist/query/scope.js.map +1 -1
- package/dist/schema.js +2 -2
- package/dist/schema.js.map +1 -1
- package/dist/statements.d.ts +15 -9
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +14 -9
- package/dist/statements.js.map +1 -1
- package/package.json +2 -2
- package/src/capacity.ts +24 -1
- package/src/db.ts +182 -443
- package/src/index.ts +9 -23
- package/src/lower.ts +8 -1
- package/src/native.ts +69 -42
- package/src/query/atom.ts +255 -165
- package/src/query/find.ts +39 -80
- package/src/query/lower.ts +578 -432
- package/src/query/parse-ir.ts +82 -0
- package/src/query/run.ts +2 -14
- package/src/query/scope.ts +3 -21
- package/src/schema.ts +2 -2
- package/src/statements.ts +33 -17
- package/dist/order.d.ts +0 -87
- package/dist/order.d.ts.map +0 -1
- package/dist/order.js +0 -153
- package/dist/order.js.map +0 -1
- package/dist/query/predicate.d.ts +0 -91
- package/dist/query/predicate.d.ts.map +0 -1
- package/dist/query/predicate.js +0 -156
- package/dist/query/predicate.js.map +0 -1
- package/src/order.ts +0 -234
- package/src/query/predicate.ts +0 -269
package/src/query/lower.ts
CHANGED
|
@@ -18,11 +18,8 @@
|
|
|
18
18
|
* `Params` inferred to be EXACTLY the params the rules use (params are typed
|
|
19
19
|
* BY USE; a param no rule uses never registers). Variable IDENTITY is the
|
|
20
20
|
* object reference: reusing one value across binding positions IS the join,
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* equality, that alone makes every cross-binding join transitively
|
|
24
|
-
* class-equal. Lowering is a pure function of the query value down to the
|
|
25
|
-
* bridge's `ProgramIr` (`bumbledb/crates/bumbledb/src/ir.rs`): relations by
|
|
21
|
+
* lowering is a pure function of the query value down to the
|
|
22
|
+
* bridge's `QueryIr` (`bumbledb/crates/bumbledb/src/ir.rs`): relations by
|
|
26
23
|
* declaration ordinal, variables by dense per-rule first-occurrence ids
|
|
27
24
|
* (keyed on the object REFERENCE — the discipline is unchanged, only the map
|
|
28
25
|
* key moved from name to reference), params by first-use order. Lowering is
|
|
@@ -30,7 +27,7 @@
|
|
|
30
27
|
* two identically-written queries (fresh mints each) lower identically.
|
|
31
28
|
* Construction validates negation safety and boundness (typed by the var's
|
|
32
29
|
* label — object identity is invisible to the type tier, so these are
|
|
33
|
-
* construction-time walls); everything else (
|
|
30
|
+
* construction-time walls); everything else (types, aggregate
|
|
34
31
|
* rosters, rule caps) is the ENGINE's judge, surfacing at prepare.
|
|
35
32
|
*/
|
|
36
33
|
|
|
@@ -46,8 +43,7 @@ import type {
|
|
|
46
43
|
FindTermIr,
|
|
47
44
|
HeadOpIr,
|
|
48
45
|
HeadTermIr,
|
|
49
|
-
|
|
50
|
-
ProgramIr,
|
|
46
|
+
ParsedQuery,
|
|
51
47
|
QueryParam,
|
|
52
48
|
RuleIr,
|
|
53
49
|
TaggedValue,
|
|
@@ -66,36 +62,30 @@ import type {
|
|
|
66
62
|
CmpTermData,
|
|
67
63
|
CondData,
|
|
68
64
|
CondParamsShape,
|
|
65
|
+
DerivedTable,
|
|
69
66
|
FindColumn,
|
|
70
67
|
FindEntryData,
|
|
71
|
-
|
|
68
|
+
InteriorData,
|
|
72
69
|
MatchFields,
|
|
73
70
|
MatchOwner,
|
|
74
71
|
MatchShape,
|
|
75
72
|
ParamUse,
|
|
76
73
|
RecData,
|
|
74
|
+
RecHandle,
|
|
75
|
+
RecHead,
|
|
77
76
|
RuleData,
|
|
78
77
|
RuleItem
|
|
79
78
|
} from "#query/atom.ts"
|
|
80
79
|
import { allen, and, eq, ge, gt, le, lt, ne, not, or, pointIn } from "#query/atom.ts"
|
|
81
80
|
import type { CheckFind, CheckRecFind, FindShape, HeadRecordOf, RowOfFind } from "#query/find.ts"
|
|
82
|
-
import {
|
|
83
|
-
import
|
|
84
|
-
|
|
85
|
-
ClassedField,
|
|
86
|
-
Flatten,
|
|
87
|
-
InferredOf,
|
|
88
|
-
JoinOk,
|
|
89
|
-
MintSlotOf,
|
|
90
|
-
ParamEntry,
|
|
91
|
-
ParamsRecord
|
|
92
|
-
} from "#query/scope.ts"
|
|
81
|
+
import { count, max, min, pack, sum } from "#query/find.ts"
|
|
82
|
+
import { parseQueryIr } from "#query/parse-ir.ts"
|
|
83
|
+
import type { AnyVar, ClassedField, Flatten, InferredOf, ParamEntry, ParamsRecord, ShapeOf } from "#query/scope.ts"
|
|
93
84
|
import {
|
|
94
85
|
fieldJoins,
|
|
95
86
|
inferred,
|
|
96
87
|
isTerm,
|
|
97
88
|
makeDuration,
|
|
98
|
-
makeMaskParam,
|
|
99
89
|
makeParam,
|
|
100
90
|
makeSetParam,
|
|
101
91
|
renderFieldKind,
|
|
@@ -117,16 +107,16 @@ type ParamsOf<T> = InferredOf<T> extends { readonly params: infer P extends Para
|
|
|
117
107
|
type RowOf<T> = InferredOf<T> extends { readonly row: infer R } ? R : never
|
|
118
108
|
|
|
119
109
|
/**
|
|
120
|
-
* A
|
|
121
|
-
* name
|
|
122
|
-
* it; `undefined` on values that carry no head.
|
|
110
|
+
* A derived table's HEAD signature as classed slots, keyed by column
|
|
111
|
+
* name; `undefined` on values that carry no head.
|
|
123
112
|
*/
|
|
124
113
|
type HeadShape = Readonly<Record<string, ClassedField>> | undefined
|
|
125
114
|
|
|
126
115
|
/**
|
|
127
116
|
* One finished rule as a plain value: the runtime data plus the inferred
|
|
128
|
-
* row/params carrier (and, for
|
|
129
|
-
*
|
|
117
|
+
* row/params carrier (and, for an interior or recursive rule, the head
|
|
118
|
+
* record of classed slots an `.interior(name)` join pairs against).
|
|
119
|
+
* `.rule(...)` consumes it.
|
|
130
120
|
*/
|
|
131
121
|
interface RuleValue<Row, P extends ParamsRecord, Head extends HeadShape = undefined> {
|
|
132
122
|
readonly rule: RuleData
|
|
@@ -140,48 +130,34 @@ type AnyRuleValue = RuleValue<unknown, ParamsRecord, HeadShape>
|
|
|
140
130
|
type HeadOf<T> =
|
|
141
131
|
InferredOf<T> extends { readonly head: infer H extends Readonly<Record<string, ClassedField>> } ? H : undefined
|
|
142
132
|
|
|
133
|
+
/** One `.interior(name)` position's judgment: a variable. */
|
|
134
|
+
type InteriorBindingOk<V> = V extends AnyVar ? true : false
|
|
135
|
+
|
|
143
136
|
/**
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
137
|
+
* The validated `.interior(name)` bindings record: every entry must be a
|
|
138
|
+
* variable. Arity and class against the named table's head are
|
|
139
|
+
* construction-time (the name is a string, so the head is not a type-level
|
|
140
|
+
* fact).
|
|
147
141
|
*/
|
|
148
|
-
|
|
149
|
-
readonly
|
|
150
|
-
readonly data: RecData
|
|
151
|
-
readonly [inferred]?: { readonly params: P; readonly head: Head }
|
|
142
|
+
type CheckInteriorBindings<B> = {
|
|
143
|
+
readonly [K in keyof B]: InteriorBindingOk<B[K]> extends true ? B[K] : never
|
|
152
144
|
}
|
|
153
145
|
|
|
154
|
-
/** One
|
|
155
|
-
type
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
? true
|
|
159
|
-
: false
|
|
160
|
-
: true
|
|
161
|
-
: false
|
|
146
|
+
/** One interior-rule builder function. */
|
|
147
|
+
type InteriorBuild<Rels extends SchemaRelations, Classes extends SchemaClasses = SchemaClasses> = (
|
|
148
|
+
r: InteriorRuleScope<Rels, Classes>
|
|
149
|
+
) => AnyRuleValue
|
|
162
150
|
|
|
163
|
-
/**
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
? [keyof B] extends [keyof Head]
|
|
174
|
-
? [keyof Head] extends [keyof B]
|
|
175
|
-
? {
|
|
176
|
-
readonly [K in keyof B]: K extends keyof Head
|
|
177
|
-
? IdbBindingOk<Classes, Head[K], B[K]> extends true
|
|
178
|
-
? B[K]
|
|
179
|
-
: never
|
|
180
|
-
: never
|
|
181
|
-
}
|
|
182
|
-
: { readonly [K in keyof B]: never }
|
|
183
|
-
: { readonly [K in keyof B]: never }
|
|
184
|
-
: { readonly [K in keyof B]: B[K] extends AnyVar ? B[K] : never }
|
|
151
|
+
/** One rec-arm builder function. */
|
|
152
|
+
type RecBuild<Rels extends SchemaRelations, Classes extends SchemaClasses = SchemaClasses> = (
|
|
153
|
+
r: RecRuleScope<Rels, Classes>
|
|
154
|
+
) => AnyRuleValue
|
|
155
|
+
|
|
156
|
+
/** A build function's rule value. */
|
|
157
|
+
type BuiltRule<F> = F extends (r: never) => infer RV ? RV : never
|
|
158
|
+
|
|
159
|
+
/** The intersected params record of a tuple of rule builds. */
|
|
160
|
+
type BuildsParams<Builds extends readonly ((r: never) => AnyRuleValue)[]> = ShapeOf<ParamsOf<BuiltRule<Builds[number]>>>
|
|
185
161
|
|
|
186
162
|
/**
|
|
187
163
|
* The term/predicate/aggregate constructor vocabulary every rule builder
|
|
@@ -193,8 +169,6 @@ interface TermOps {
|
|
|
193
169
|
readonly param: typeof makeParam
|
|
194
170
|
/** Names one ∈-set parameter (the IR's `ParamSet`): bound to a readonly array at execution. */
|
|
195
171
|
readonly inSet: typeof makeSetParam
|
|
196
|
-
/** Names one Allen-mask parameter (`MaskTerm::Param`): a bind-time 13-bit mask number. */
|
|
197
|
-
readonly maskParam: typeof makeMaskParam
|
|
198
172
|
/** The measure of an interval-typed variable: `|[s, e)| = e − s`, u64. */
|
|
199
173
|
readonly duration: typeof makeDuration
|
|
200
174
|
readonly eq: typeof eq
|
|
@@ -209,12 +183,9 @@ interface TermOps {
|
|
|
209
183
|
readonly or: typeof or
|
|
210
184
|
readonly not: typeof not
|
|
211
185
|
readonly count: typeof count
|
|
212
|
-
readonly countDistinct: typeof countDistinct
|
|
213
186
|
readonly sum: typeof sum
|
|
214
187
|
readonly min: typeof min
|
|
215
188
|
readonly max: typeof max
|
|
216
|
-
readonly argMax: typeof argMax
|
|
217
|
-
readonly argMin: typeof argMin
|
|
218
189
|
readonly pack: typeof pack
|
|
219
190
|
}
|
|
220
191
|
|
|
@@ -225,6 +196,16 @@ interface QueryRuleScope<Rels extends SchemaRelations, Classes extends SchemaCla
|
|
|
225
196
|
relation: R,
|
|
226
197
|
bindings: B & CheckBindings<Classes, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>
|
|
227
198
|
): QueryRuleChain<Rels, BindParamsShape<MatchFields<R>, B>, Classes>
|
|
199
|
+
/**
|
|
200
|
+
* A rule may START with a finished table: an interior atom is a positive
|
|
201
|
+
* occurrence exactly as the engine represents it, so its variables ground
|
|
202
|
+
* — the identity projection `(c) | reach(c);` is spellable with no
|
|
203
|
+
* re-grounding join over a domain relation.
|
|
204
|
+
*/
|
|
205
|
+
interior<const B extends Readonly<Record<string, AnyVar>>>(
|
|
206
|
+
name: string,
|
|
207
|
+
bindings: B & CheckInteriorBindings<B>
|
|
208
|
+
): QueryRuleChain<Rels, Record<never, never>, Classes>
|
|
228
209
|
}
|
|
229
210
|
|
|
230
211
|
/** The chain of a plain query rule: more atoms, residual predicates, then the head. */
|
|
@@ -242,30 +223,30 @@ interface QueryRuleChain<
|
|
|
242
223
|
where<const C extends AnyCond>(
|
|
243
224
|
cond: CheckCond<Classes, C> & C
|
|
244
225
|
): QueryRuleChain<Rels, Flatten<P & CondParamsShape<C>>, Classes>
|
|
226
|
+
/** One interior atom over a finished table (an earlier interior, or the rec). */
|
|
227
|
+
interior<const B extends Readonly<Record<string, AnyVar>>>(
|
|
228
|
+
name: string,
|
|
229
|
+
bindings: B & CheckInteriorBindings<B>
|
|
230
|
+
): QueryRuleChain<Rels, P, Classes>
|
|
245
231
|
/** The head projection: a `find` RECORD whose keys name the answer columns. */
|
|
246
232
|
find<const F extends FindShape>(entries: F & CheckFind<F>): RuleValue<RowOfFind<F>, P>
|
|
247
233
|
}
|
|
248
234
|
|
|
249
|
-
/** The rule builder an
|
|
250
|
-
interface
|
|
235
|
+
/** The rule builder an `interior("mid", ...)` callback receives. */
|
|
236
|
+
interface InteriorRuleScope<Rels extends SchemaRelations, Classes extends SchemaClasses = SchemaClasses>
|
|
237
|
+
extends TermOps {
|
|
251
238
|
match<R extends QueryRelation<Rels>, const B extends MatchShape<MatchFields<R>>>(
|
|
252
239
|
relation: R,
|
|
253
240
|
bindings: B & CheckBindings<Classes, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>
|
|
254
|
-
):
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
* re-grounding join over a domain relation.
|
|
260
|
-
*/
|
|
261
|
-
idb<Target extends RecRef<string, ParamsRecord>, const B extends Readonly<Record<string, AnyVar>>>(
|
|
262
|
-
target: Target,
|
|
263
|
-
bindings: B & CheckIdbBindings<Classes, HeadOf<Target>, B>
|
|
264
|
-
): OutputRuleChain<Rels, ParamsOf<Target>, Classes>
|
|
241
|
+
): InteriorRuleChain<Rels, BindParamsShape<MatchFields<R>, B>, Classes>
|
|
242
|
+
interior<const B extends Readonly<Record<string, AnyVar>>>(
|
|
243
|
+
name: string,
|
|
244
|
+
bindings: B & CheckInteriorBindings<B>
|
|
245
|
+
): InteriorRuleChain<Rels, Record<never, never>, Classes>
|
|
265
246
|
}
|
|
266
247
|
|
|
267
|
-
/** The chain of an
|
|
268
|
-
interface
|
|
248
|
+
/** The chain of an interior rule: bound-variable heads only. */
|
|
249
|
+
interface InteriorRuleChain<
|
|
269
250
|
Rels extends SchemaRelations,
|
|
270
251
|
P extends ParamsRecord,
|
|
271
252
|
Classes extends SchemaClasses = SchemaClasses
|
|
@@ -273,70 +254,65 @@ interface OutputRuleChain<
|
|
|
273
254
|
match<R extends QueryRelation<Rels>, const B extends MatchShape<MatchFields<R>>>(
|
|
274
255
|
relation: R,
|
|
275
256
|
bindings: B & CheckBindings<Classes, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>
|
|
276
|
-
):
|
|
257
|
+
): InteriorRuleChain<Rels, Flatten<P & BindParamsShape<MatchFields<R>, B>>, Classes>
|
|
277
258
|
where<const C extends AnyCond>(
|
|
278
259
|
cond: CheckCond<Classes, C> & C
|
|
279
|
-
):
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
* carries its params into `Params` AND its head signature, so the join is
|
|
286
|
-
* key-exact and class-checked against the head at compile time.
|
|
287
|
-
*/
|
|
288
|
-
idb<Target extends RecRef<string, ParamsRecord>, const B extends Readonly<Record<string, AnyVar>>>(
|
|
289
|
-
target: Target,
|
|
290
|
-
bindings: B & CheckIdbBindings<Classes, HeadOf<Target>, B>
|
|
291
|
-
): OutputRuleChain<Rels, Flatten<P & ParamsOf<Target>>, Classes>
|
|
292
|
-
find<const F extends FindShape>(entries: F & CheckFind<F>): RuleValue<RowOfFind<F>, P>
|
|
260
|
+
): InteriorRuleChain<Rels, Flatten<P & CondParamsShape<C>>, Classes>
|
|
261
|
+
interior<const B extends Readonly<Record<string, AnyVar>>>(
|
|
262
|
+
name: string,
|
|
263
|
+
bindings: B & CheckInteriorBindings<B>
|
|
264
|
+
): InteriorRuleChain<Rels, P, Classes>
|
|
265
|
+
find<const F extends FindShape>(entries: F & CheckRecFind<F>): RuleValue<RowOfFind<F>, P, HeadRecordOf<Classes, F>>
|
|
293
266
|
}
|
|
294
267
|
|
|
295
|
-
/** The rule builder a
|
|
296
|
-
interface RecRuleScope<Rels extends SchemaRelations,
|
|
297
|
-
extends TermOps {
|
|
268
|
+
/** The rule builder a `recursive("reach", { base, rec })` arm receives. */
|
|
269
|
+
interface RecRuleScope<Rels extends SchemaRelations, Classes extends SchemaClasses = SchemaClasses> extends TermOps {
|
|
298
270
|
match<R extends QueryRelation<Rels>, const B extends MatchShape<MatchFields<R>>>(
|
|
299
271
|
relation: R,
|
|
300
272
|
bindings: B & CheckBindings<Classes, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>
|
|
301
|
-
): RecRuleChain<Rels,
|
|
273
|
+
): RecRuleChain<Rels, BindParamsShape<MatchFields<R>, B>, Classes>
|
|
274
|
+
interior<const B extends Readonly<Record<string, AnyVar>>>(
|
|
275
|
+
name: string,
|
|
276
|
+
bindings: B & CheckInteriorBindings<B>
|
|
277
|
+
): RecRuleChain<Rels, Record<never, never>, Classes>
|
|
302
278
|
}
|
|
303
279
|
|
|
304
280
|
/**
|
|
305
|
-
* The chain of a recursive
|
|
306
|
-
*
|
|
307
|
-
* and the measure are unrepresentable in a
|
|
281
|
+
* The chain of a recursive arm. `.interior("reach", …)` is the self-atom
|
|
282
|
+
* on rec arms (and a prior interior on either list). `find` takes bound
|
|
283
|
+
* variables only — aggregates and the measure are unrepresentable in a
|
|
284
|
+
* recursive head.
|
|
308
285
|
*/
|
|
309
286
|
interface RecRuleChain<
|
|
310
287
|
Rels extends SchemaRelations,
|
|
311
|
-
Self extends string,
|
|
312
288
|
P extends ParamsRecord,
|
|
313
289
|
Classes extends SchemaClasses = SchemaClasses
|
|
314
290
|
> {
|
|
315
291
|
match<R extends QueryRelation<Rels>, const B extends MatchShape<MatchFields<R>>>(
|
|
316
292
|
relation: R,
|
|
317
293
|
bindings: B & CheckBindings<Classes, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>
|
|
318
|
-
): RecRuleChain<Rels,
|
|
294
|
+
): RecRuleChain<Rels, Flatten<P & BindParamsShape<MatchFields<R>, B>>, Classes>
|
|
319
295
|
where<const C extends AnyCond>(
|
|
320
296
|
cond: CheckCond<Classes, C> & C
|
|
321
|
-
): RecRuleChain<Rels,
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
): RecRuleChain<Rels, Self, P, Classes>
|
|
327
|
-
/** The recursive head: a `find` record of bound variables only; the value carries the head's classed slots for `idb` pairing. */
|
|
297
|
+
): RecRuleChain<Rels, Flatten<P & CondParamsShape<C>>, Classes>
|
|
298
|
+
interior<const B extends Readonly<Record<string, AnyVar>>>(
|
|
299
|
+
name: string,
|
|
300
|
+
bindings: B & CheckInteriorBindings<B>
|
|
301
|
+
): RecRuleChain<Rels, P, Classes>
|
|
328
302
|
find<const F extends FindShape>(entries: F & CheckRecFind<F>): RuleValue<RowOfFind<F>, P, HeadRecordOf<Classes, F>>
|
|
329
303
|
}
|
|
330
304
|
|
|
331
305
|
/** A query's runtime description — everything lowering, the wire marshal, and answer decode read. */
|
|
332
306
|
interface QueryData {
|
|
333
|
-
/**
|
|
334
|
-
readonly
|
|
335
|
-
/** The
|
|
307
|
+
/** Named interiors in declaration order (DAG). */
|
|
308
|
+
readonly interiors: readonly InteriorData[]
|
|
309
|
+
/** The optional linear rec. */
|
|
310
|
+
readonly rec: RecData | null
|
|
311
|
+
/** The main rules in written order (multiple rules = set union). */
|
|
336
312
|
readonly rules: readonly RuleData[]
|
|
337
313
|
/** The head columns (every rule derives the same head; written order = answer column order). */
|
|
338
314
|
readonly finds: readonly FindColumn[]
|
|
339
|
-
/** The registered params in first-use order across the
|
|
315
|
+
/** The registered params in first-use order across the query walk (= dense `ParamId`s). */
|
|
340
316
|
readonly params: readonly ParamEntry[]
|
|
341
317
|
}
|
|
342
318
|
|
|
@@ -357,6 +333,10 @@ interface Query<
|
|
|
357
333
|
rule<RV extends AnyRuleValue>(
|
|
358
334
|
build: (r: QueryRuleScope<Rels, Classes>) => RV
|
|
359
335
|
): Query<Rels, Row | RowOf<RV>, Flatten<Params & ParamsOf<RV>>, Classes>
|
|
336
|
+
/** Construction error: interiors precede main rules. Uncallable after `.rule()`. */
|
|
337
|
+
interior(name: string, ...builds: never[]): never
|
|
338
|
+
/** Construction error: recursive precedes main rules. Uncallable after `.rule()`. */
|
|
339
|
+
recursive(name: string, arms: never): never
|
|
360
340
|
readonly [inferred]?: { readonly row: Row; readonly params: Params }
|
|
361
341
|
}
|
|
362
342
|
|
|
@@ -372,18 +352,43 @@ type QueryRow<Q extends AnyQuery> = RowOf<Q>
|
|
|
372
352
|
/** Extracts a query value's inferred execute-params type. */
|
|
373
353
|
type QueryParams<Q extends AnyQuery> = ParamsOf<Q>
|
|
374
354
|
|
|
375
|
-
/**
|
|
376
|
-
|
|
355
|
+
/**
|
|
356
|
+
* The entry value of `query(S)`: interiors, then optional recursive, then
|
|
357
|
+
* the first `.rule` mints the query. `interior` / `recursive` exist only
|
|
358
|
+
* while `Rec` is `null`; `.recursive()` moves the type parameter.
|
|
359
|
+
*/
|
|
360
|
+
type QueryStart<
|
|
361
|
+
Rels extends SchemaRelations,
|
|
362
|
+
Classes extends SchemaClasses = SchemaClasses,
|
|
363
|
+
P extends ParamsRecord = Record<never, never>,
|
|
364
|
+
Rec extends RecData | null = null
|
|
365
|
+
> = {
|
|
377
366
|
rule<RV extends AnyRuleValue>(
|
|
378
367
|
build: (r: QueryRuleScope<Rels, Classes>) => RV
|
|
379
|
-
): Query<Rels, RowOf<RV>, ParamsOf<RV
|
|
380
|
-
}
|
|
368
|
+
): Query<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes>
|
|
369
|
+
} & (Rec extends null
|
|
370
|
+
? {
|
|
371
|
+
interior<const Builds extends readonly InteriorBuild<Rels, Classes>[]>(
|
|
372
|
+
name: string,
|
|
373
|
+
...builds: Builds
|
|
374
|
+
): QueryStart<Rels, Classes, Flatten<P & BuildsParams<Builds>>, null>
|
|
375
|
+
recursive<
|
|
376
|
+
const Base extends readonly RecBuild<Rels, Classes>[],
|
|
377
|
+
const Step extends readonly RecBuild<Rels, Classes>[]
|
|
378
|
+
>(
|
|
379
|
+
name: string,
|
|
380
|
+
arms: { readonly base: Base; readonly rec: Step }
|
|
381
|
+
): QueryStart<Rels, Classes, Flatten<P & BuildsParams<Base> & BuildsParams<Step>>, RecData>
|
|
382
|
+
}
|
|
383
|
+
: {
|
|
384
|
+
interior: never
|
|
385
|
+
recursive: never
|
|
386
|
+
})
|
|
381
387
|
|
|
382
388
|
/** The frozen constructor vocabulary every rule builder spreads. */
|
|
383
389
|
const termOps: TermOps = Object.freeze({
|
|
384
390
|
param: makeParam,
|
|
385
391
|
inSet: makeSetParam,
|
|
386
|
-
maskParam: makeMaskParam,
|
|
387
392
|
duration: makeDuration,
|
|
388
393
|
eq,
|
|
389
394
|
ne,
|
|
@@ -397,12 +402,9 @@ const termOps: TermOps = Object.freeze({
|
|
|
397
402
|
or,
|
|
398
403
|
not,
|
|
399
404
|
count,
|
|
400
|
-
countDistinct,
|
|
401
405
|
sum,
|
|
402
406
|
min,
|
|
403
407
|
max,
|
|
404
|
-
argMax,
|
|
405
|
-
argMin,
|
|
406
408
|
pack
|
|
407
409
|
})
|
|
408
410
|
|
|
@@ -558,10 +560,6 @@ function resolveBindings(
|
|
|
558
560
|
)
|
|
559
561
|
break
|
|
560
562
|
}
|
|
561
|
-
case "maskParam":
|
|
562
|
-
throw errors.new(
|
|
563
|
-
`${label}.${fieldName}: an Allen-mask param is not a field-typed value — masks live in allen() conditions only`
|
|
564
|
-
)
|
|
565
563
|
case "duration":
|
|
566
564
|
throw errors.new(
|
|
567
565
|
`${label}.${fieldName}: the measure is not a field-typed value — it lives in comparisons and find entries`
|
|
@@ -607,7 +605,7 @@ function advanceMatch(
|
|
|
607
605
|
}
|
|
608
606
|
|
|
609
607
|
/** Resolves one comparison side to its runtime term (variables and the measure ride by reference). */
|
|
610
|
-
function cmpTermDataOf(
|
|
608
|
+
function cmpTermDataOf(value: unknown): CmpTermData {
|
|
611
609
|
if (isTerm(value)) {
|
|
612
610
|
switch (value[term]) {
|
|
613
611
|
case "var":
|
|
@@ -618,8 +616,6 @@ function cmpTermDataOf(op: string, value: unknown): CmpTermData {
|
|
|
618
616
|
return Object.freeze({ kind: "setParam" as const, name: value.name })
|
|
619
617
|
case "duration":
|
|
620
618
|
return Object.freeze({ kind: "measure" as const, ref: value.over })
|
|
621
|
-
case "maskParam":
|
|
622
|
-
throw errors.new(`${op}: an Allen-mask param is not a comparison term — masks live in allen()'s mask position`)
|
|
623
619
|
}
|
|
624
620
|
}
|
|
625
621
|
return Object.freeze({ kind: "literal" as const, value })
|
|
@@ -656,31 +652,23 @@ function sideUses(op: CmpKind, side: CmpTermData, sibling: CmpTermData, uses: Pa
|
|
|
656
652
|
/** Lowers one condition VALUE to its runtime data, recording param uses. */
|
|
657
653
|
function condDataOf(cond: AnyCond, uses: ParamUse[]): CondData {
|
|
658
654
|
if (cond.cond === "cmp") {
|
|
659
|
-
const lhs = cmpTermDataOf(cond.
|
|
660
|
-
const rhs = cmpTermDataOf(cond.
|
|
655
|
+
const lhs = cmpTermDataOf(cond.lhs)
|
|
656
|
+
const rhs = cmpTermDataOf(cond.rhs)
|
|
661
657
|
sideUses(cond.op, lhs, rhs, uses)
|
|
662
658
|
sideUses(cond.op, rhs, lhs, uses)
|
|
663
|
-
let mask: MaskData | undefined
|
|
664
659
|
if (cond.op === "allen") {
|
|
665
660
|
const maskValue = cond.mask
|
|
666
|
-
if (typeof maskValue
|
|
667
|
-
|
|
668
|
-
} else if (isTerm(maskValue) && maskValue[term] === "maskParam") {
|
|
669
|
-
mask = Object.freeze({ kind: "param" as const, name: maskValue.name })
|
|
670
|
-
uses.push(
|
|
671
|
-
Object.freeze({
|
|
672
|
-
name: maskValue.name,
|
|
673
|
-
shape: "mask" as const,
|
|
674
|
-
anchor: undefined,
|
|
675
|
-
op: "allen" as const,
|
|
676
|
-
members: undefined
|
|
677
|
-
})
|
|
678
|
-
)
|
|
679
|
-
} else {
|
|
680
|
-
throw errors.new("allen: the mask position takes a 13-bit mask number or a maskParam")
|
|
661
|
+
if (typeof maskValue !== "number") {
|
|
662
|
+
throw errors.new("allen: the mask position takes a 13-bit mask number built from the ALLEN constants")
|
|
681
663
|
}
|
|
664
|
+
return Object.freeze({
|
|
665
|
+
kind: "cmp" as const,
|
|
666
|
+
op: { kind: "allen" as const, mask: maskValue },
|
|
667
|
+
lhs,
|
|
668
|
+
rhs
|
|
669
|
+
})
|
|
682
670
|
}
|
|
683
|
-
return Object.freeze({ kind: "cmp" as const, op: cond.op
|
|
671
|
+
return Object.freeze({ kind: "cmp" as const, op: { kind: cond.op }, lhs, rhs })
|
|
684
672
|
}
|
|
685
673
|
if (cond.cond === "tree") {
|
|
686
674
|
const children = cond.children.map(function lowerChild(child) {
|
|
@@ -698,13 +686,13 @@ function advanceWhere(context: ChainContext, state: RuleBuildState, cond: AnyCon
|
|
|
698
686
|
if (typeof cond !== "object" || cond === null || !("cond" in cond)) {
|
|
699
687
|
throw errors.new("where() takes a comparison, an and()/or() tree, or a negated atom")
|
|
700
688
|
}
|
|
701
|
-
if (cond.cond === "
|
|
689
|
+
if (cond.cond === "notInterior") {
|
|
702
690
|
const bindings: Readonly<Record<string, unknown>> = Object.fromEntries(
|
|
703
691
|
Object.entries(cond.bindings ?? {}).filter(function defined([, value]) {
|
|
704
692
|
return value !== undefined
|
|
705
693
|
})
|
|
706
694
|
)
|
|
707
|
-
return
|
|
695
|
+
return notInteriorAdvance(context, state, cond.name, bindings)
|
|
708
696
|
}
|
|
709
697
|
if (cond.cond === "not") {
|
|
710
698
|
const relation: MatchOwner = cond.relation
|
|
@@ -730,19 +718,19 @@ function advanceWhere(context: ChainContext, state: RuleBuildState, cond: AnyCon
|
|
|
730
718
|
}
|
|
731
719
|
|
|
732
720
|
/**
|
|
733
|
-
* Extends a rule state with one
|
|
734
|
-
* vars validated at completion). A POSITIVE
|
|
735
|
-
* occurrence exactly as the engine represents it (`check_atoms` walks
|
|
721
|
+
* Extends a rule state with one interior atom (a named record over head keys;
|
|
722
|
+
* vars validated at completion). A POSITIVE interior atom is a positive
|
|
723
|
+
* occurrence exactly as the engine represents it (`check_atoms` walks Interior
|
|
736
724
|
* and Edb in one loop), so its variables GROUND: they enter the rule's
|
|
737
725
|
* boundness set, may ride the head, and satisfy negation safety — the
|
|
738
|
-
*
|
|
726
|
+
* interior-only identity projection of a finished table is spellable with no
|
|
739
727
|
* re-grounding join. A NEGATED one binds nothing, only rejects.
|
|
740
728
|
*/
|
|
741
|
-
function
|
|
729
|
+
function advanceInterior(
|
|
742
730
|
state: RuleBuildState,
|
|
743
|
-
|
|
731
|
+
target: DerivedTable,
|
|
744
732
|
bindings: Readonly<Record<string, unknown>>,
|
|
745
|
-
|
|
733
|
+
kind: "interior" | "negatedInterior"
|
|
746
734
|
): RuleBuildState {
|
|
747
735
|
const resolved: Array<{ readonly key: string; readonly ref: AnyVar }> = []
|
|
748
736
|
for (const [key, value] of Object.entries(bindings)) {
|
|
@@ -751,31 +739,26 @@ function advanceIdb(
|
|
|
751
739
|
}
|
|
752
740
|
if (!isTerm(value) || value[term] !== "var") {
|
|
753
741
|
throw errors.new(
|
|
754
|
-
`
|
|
742
|
+
`interior ${target.name}: position ${key} takes a variable — bind literals and params through where()/match()`
|
|
755
743
|
)
|
|
756
744
|
}
|
|
757
745
|
resolved.push(Object.freeze({ key, ref: value }))
|
|
758
746
|
}
|
|
759
747
|
const bound = new Set(state.bound)
|
|
760
|
-
if (
|
|
748
|
+
if (kind === "interior") {
|
|
761
749
|
for (const binding of resolved) {
|
|
762
750
|
bound.add(binding.ref)
|
|
763
751
|
}
|
|
764
752
|
}
|
|
765
753
|
return Object.freeze({
|
|
766
|
-
items: Object.freeze([
|
|
767
|
-
...state.items,
|
|
768
|
-
Object.freeze({ kind: "idb" as const, rec, bindings: Object.freeze(resolved), negated })
|
|
769
|
-
]),
|
|
754
|
+
items: Object.freeze([...state.items, Object.freeze({ kind, target, bindings: Object.freeze(resolved) })]),
|
|
770
755
|
bound,
|
|
771
756
|
paramUses: state.paramUses
|
|
772
757
|
})
|
|
773
758
|
}
|
|
774
759
|
|
|
775
760
|
/** Narrows a find entry to an aggregate value. */
|
|
776
|
-
function isAggregateEntry(
|
|
777
|
-
value: unknown
|
|
778
|
-
): value is { readonly agg: string; readonly over: unknown; readonly key: unknown } {
|
|
761
|
+
function isAggregateEntry(value: unknown): value is { readonly agg: string; readonly over: unknown } {
|
|
779
762
|
return typeof value === "object" && value !== null && "agg" in value
|
|
780
763
|
}
|
|
781
764
|
|
|
@@ -788,16 +771,11 @@ function asVarTerm(context: string, value: unknown): AnyVar {
|
|
|
788
771
|
}
|
|
789
772
|
|
|
790
773
|
/** Classifies one aggregate find entry into its runtime data (variables ride by reference). */
|
|
791
|
-
function aggDataOf(
|
|
792
|
-
name: string,
|
|
793
|
-
entry: { readonly agg: string; readonly over: unknown; readonly key: unknown }
|
|
794
|
-
): AggData {
|
|
774
|
+
function aggDataOf(name: string, entry: { readonly agg: string; readonly over: unknown }): AggData {
|
|
795
775
|
const over = entry.over
|
|
796
776
|
switch (entry.agg) {
|
|
797
777
|
case "count":
|
|
798
778
|
return Object.freeze({ op: "count" as const })
|
|
799
|
-
case "countDistinct":
|
|
800
|
-
return Object.freeze({ op: "countDistinct" as const, over: asVarTerm(`find ${name} (countDistinct)`, over) })
|
|
801
779
|
case "sum":
|
|
802
780
|
case "min":
|
|
803
781
|
case "max": {
|
|
@@ -809,14 +787,6 @@ function aggDataOf(
|
|
|
809
787
|
}
|
|
810
788
|
throw errors.new(`find ${name} (${entry.agg}): takes a variable or r.duration(v)`)
|
|
811
789
|
}
|
|
812
|
-
case "argMax":
|
|
813
|
-
case "argMin":
|
|
814
|
-
return Object.freeze({
|
|
815
|
-
op: "arg" as const,
|
|
816
|
-
direction: entry.agg,
|
|
817
|
-
over: asVarTerm(`find ${name} (${entry.agg})`, over),
|
|
818
|
-
key: asVarTerm(`find ${name} (${entry.agg} key)`, entry.key)
|
|
819
|
-
})
|
|
820
790
|
case "pack":
|
|
821
791
|
return Object.freeze({ op: "pack" as const, over: asVarTerm(`find ${name} (pack)`, over) })
|
|
822
792
|
default:
|
|
@@ -903,17 +873,14 @@ function assertNotClosed(where: string, position: string, ref: AnyVar): void {
|
|
|
903
873
|
|
|
904
874
|
/**
|
|
905
875
|
* The classed mint slot one answer column's VALUES flow from: a projected
|
|
906
|
-
* variable's mint slot
|
|
907
|
-
*
|
|
876
|
+
* variable's mint slot. Counts, folds, `pack` and the measure derive
|
|
877
|
+
* numbers/intervals, so they resolve no slot.
|
|
908
878
|
*/
|
|
909
879
|
function findColumnSlotOf(context: ChainContext, column: FindColumn): ClassedField | undefined {
|
|
910
880
|
const entry = column.entry
|
|
911
881
|
if (entry.kind === "var") {
|
|
912
882
|
return mintSlotOf(context, entry.over)
|
|
913
883
|
}
|
|
914
|
-
if (entry.kind === "aggregate" && entry.agg.op === "arg") {
|
|
915
|
-
return mintSlotOf(context, entry.agg.over)
|
|
916
|
-
}
|
|
917
884
|
return undefined
|
|
918
885
|
}
|
|
919
886
|
|
|
@@ -934,9 +901,6 @@ function validateColumn(context: ChainContext, bound: ReadonlySet<AnyVar>, colum
|
|
|
934
901
|
switch (agg.op) {
|
|
935
902
|
case "count":
|
|
936
903
|
return
|
|
937
|
-
case "countDistinct":
|
|
938
|
-
assertBound(where, bound, agg.over)
|
|
939
|
-
return
|
|
940
904
|
case "fold": {
|
|
941
905
|
if ("duration" in agg.over) {
|
|
942
906
|
assertBound(where, bound, agg.over.duration)
|
|
@@ -947,12 +911,6 @@ function validateColumn(context: ChainContext, bound: ReadonlySet<AnyVar>, colum
|
|
|
947
911
|
assertNotClosed(where, `the ${agg.fold} input`, agg.over)
|
|
948
912
|
return
|
|
949
913
|
}
|
|
950
|
-
case "arg": {
|
|
951
|
-
assertBound(where, bound, agg.over)
|
|
952
|
-
assertBound(where, bound, agg.key)
|
|
953
|
-
assertNotClosed(where, `the ${agg.direction} key`, agg.key)
|
|
954
|
-
return
|
|
955
|
-
}
|
|
956
914
|
case "pack":
|
|
957
915
|
assertBound(where, bound, agg.over)
|
|
958
916
|
assertInterval(where, agg.over)
|
|
@@ -972,8 +930,8 @@ function validateCond(context: ChainContext, bound: ReadonlySet<AnyVar>, cond: C
|
|
|
972
930
|
if (side.kind === "var") {
|
|
973
931
|
assertBound(label, bound, side.ref)
|
|
974
932
|
const roster = rosterOf(side.ref.field)
|
|
975
|
-
if (isOrderOp(cond.op) && roster !== undefined) {
|
|
976
|
-
throw closedOrderError(label, `the ${cond.op} side ${side.ref.label}`, roster.name)
|
|
933
|
+
if (isOrderOp(cond.op.kind) && roster !== undefined) {
|
|
934
|
+
throw closedOrderError(label, `the ${cond.op.kind} side ${side.ref.label}`, roster.name)
|
|
977
935
|
}
|
|
978
936
|
}
|
|
979
937
|
if (side.kind === "measure") {
|
|
@@ -981,14 +939,14 @@ function validateCond(context: ChainContext, bound: ReadonlySet<AnyVar>, cond: C
|
|
|
981
939
|
assertInterval(label, side.ref)
|
|
982
940
|
}
|
|
983
941
|
}
|
|
984
|
-
if ((cond.op === "eq" || cond.op === "ne") && cond.lhs.kind === "var" && cond.rhs.kind === "var") {
|
|
942
|
+
if ((cond.op.kind === "eq" || cond.op.kind === "ne") && cond.lhs.kind === "var" && cond.rhs.kind === "var") {
|
|
985
943
|
assertBound(label, bound, cond.lhs.ref)
|
|
986
944
|
assertBound(label, bound, cond.rhs.ref)
|
|
987
945
|
const lhs = mintSlotOf(context, cond.lhs.ref)
|
|
988
946
|
const rhs = mintSlotOf(context, cond.rhs.ref)
|
|
989
947
|
if (!fieldJoins(lhs, rhs)) {
|
|
990
948
|
throw errors.new(
|
|
991
|
-
`${label}: ${cond.op}(${cond.lhs.ref.label}, ${cond.rhs.ref.label}) unifies domain-unequal fields — ${cond.lhs.ref.label} bound at ${renderFieldKind(lhs)}, ${cond.rhs.ref.label} at ${renderFieldKind(rhs)} (a var joins only class-equal slots; bare pairs only with bare)`
|
|
949
|
+
`${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)`
|
|
992
950
|
)
|
|
993
951
|
}
|
|
994
952
|
}
|
|
@@ -1000,29 +958,27 @@ function validateCond(context: ChainContext, bound: ReadonlySet<AnyVar>, cond: C
|
|
|
1000
958
|
}
|
|
1001
959
|
|
|
1002
960
|
/**
|
|
1003
|
-
* Validates one
|
|
961
|
+
* Validates one interior item: every head column of the table is bound exactly
|
|
1004
962
|
* once (a missing or extra key is a pointed error) and each variable joins
|
|
1005
|
-
* its head column's classed slot. A POSITIVE
|
|
963
|
+
* its head column's classed slot. A POSITIVE interior atom GROUNDS its
|
|
1006
964
|
* variables (a positive occurrence, exactly the engine's representation),
|
|
1007
965
|
* so no boundness precondition exists; a NEGATED one binds nothing — its
|
|
1008
966
|
* variables must be positively bound elsewhere in the rule, the same
|
|
1009
|
-
* safety rule as EDB negation. When the
|
|
1010
|
-
* (`
|
|
1011
|
-
* head.
|
|
967
|
+
* safety rule as EDB negation. When the table's own first rule is in flight
|
|
968
|
+
* (`finds` empty), the completing rule's OWN find columns ARE the head.
|
|
1012
969
|
*/
|
|
1013
|
-
function
|
|
970
|
+
function validateInterior(
|
|
1014
971
|
context: ChainContext,
|
|
1015
972
|
bound: ReadonlySet<AnyVar>,
|
|
1016
973
|
item: {
|
|
1017
|
-
readonly
|
|
974
|
+
readonly kind: "interior" | "negatedInterior"
|
|
975
|
+
readonly target: DerivedTable
|
|
1018
976
|
readonly bindings: ReadonlyArray<{ readonly key: string; readonly ref: AnyVar }>
|
|
1019
|
-
readonly negated: boolean
|
|
1020
977
|
},
|
|
1021
978
|
columns: readonly FindColumn[]
|
|
1022
979
|
): void {
|
|
1023
980
|
const label = contextLabel(context)
|
|
1024
|
-
const
|
|
1025
|
-
const headColumns = head !== undefined ? head.finds : columns
|
|
981
|
+
const headColumns = item.target.finds.length > 0 ? item.target.finds : columns
|
|
1026
982
|
const headNames = headColumns.map(function nameOf(column) {
|
|
1027
983
|
return column.name
|
|
1028
984
|
})
|
|
@@ -1032,21 +988,21 @@ function validateIdb(
|
|
|
1032
988
|
for (const key of keys) {
|
|
1033
989
|
if (!headNames.includes(key)) {
|
|
1034
990
|
throw errors.new(
|
|
1035
|
-
`${label}:
|
|
991
|
+
`${label}: interior ${item.target.name} binds ${key}, not a head column of ${item.target.name} (head columns: ${headNames.join(", ")})`
|
|
1036
992
|
)
|
|
1037
993
|
}
|
|
1038
994
|
}
|
|
1039
995
|
for (const name of headNames) {
|
|
1040
996
|
if (!keys.includes(name)) {
|
|
1041
997
|
throw errors.new(
|
|
1042
|
-
`${label}:
|
|
998
|
+
`${label}: interior ${item.target.name} omits the head column ${name} — an interior join binds every head column of ${item.target.name}`
|
|
1043
999
|
)
|
|
1044
1000
|
}
|
|
1045
1001
|
}
|
|
1046
1002
|
for (const binding of item.bindings) {
|
|
1047
|
-
if (item.
|
|
1003
|
+
if (item.kind === "negatedInterior" && !bound.has(binding.ref)) {
|
|
1048
1004
|
throw errors.new(
|
|
1049
|
-
`${label}: negated
|
|
1005
|
+
`${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)`
|
|
1050
1006
|
)
|
|
1051
1007
|
}
|
|
1052
1008
|
const headColumn = headColumns.find(function byName(column) {
|
|
@@ -1058,7 +1014,7 @@ function validateIdb(
|
|
|
1058
1014
|
const mint = mintSlotOf(context, binding.ref)
|
|
1059
1015
|
if (!fieldJoins(headColumn.slot, mint)) {
|
|
1060
1016
|
throw errors.new(
|
|
1061
|
-
`${label}:
|
|
1017
|
+
`${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`
|
|
1062
1018
|
)
|
|
1063
1019
|
}
|
|
1064
1020
|
}
|
|
@@ -1067,7 +1023,7 @@ function validateIdb(
|
|
|
1067
1023
|
/**
|
|
1068
1024
|
* Completes one rule: enriches the find columns (declaration-order-safe
|
|
1069
1025
|
* keys, boundness validated, each column's classed slot and closed slice
|
|
1070
|
-
* resolved), then walks the body walls — negated-atom boundness safety,
|
|
1026
|
+
* resolved), then walks the body walls — negated-atom boundness safety, interior
|
|
1071
1027
|
* head pairing, and condition validation.
|
|
1072
1028
|
*/
|
|
1073
1029
|
function completeRule(context: ChainContext, state: RuleBuildState, rawColumns: readonly FindColumn[]): RuleData {
|
|
@@ -1091,8 +1047,8 @@ function completeRule(context: ChainContext, state: RuleBuildState, rawColumns:
|
|
|
1091
1047
|
}
|
|
1092
1048
|
}
|
|
1093
1049
|
}
|
|
1094
|
-
if (item.kind === "
|
|
1095
|
-
|
|
1050
|
+
if (item.kind === "interior" || item.kind === "negatedInterior") {
|
|
1051
|
+
validateInterior(context, state.bound, item, columns)
|
|
1096
1052
|
}
|
|
1097
1053
|
if (item.kind === "cond") {
|
|
1098
1054
|
validateCond(context, state.bound, item.cond)
|
|
@@ -1113,100 +1069,116 @@ function makeRuleValue<Row, P extends ParamsRecord>(rule: RuleData): RuleValue<R
|
|
|
1113
1069
|
interface RawChain {
|
|
1114
1070
|
match(relation: MatchOwner, bindings: Readonly<Record<string, unknown>>): RawChain
|
|
1115
1071
|
where(cond: AnyCond): RawChain
|
|
1116
|
-
|
|
1072
|
+
interior(name: string, bindings: Readonly<Record<string, unknown>>): RawChain
|
|
1117
1073
|
find(entries: Readonly<Record<string, unknown>>): RuleValue<never, never>
|
|
1118
1074
|
}
|
|
1119
1075
|
|
|
1120
1076
|
/** The runtime rule-builder shape beneath every typed scope. */
|
|
1121
1077
|
interface RawScope extends TermOps {
|
|
1122
1078
|
match(relation: MatchOwner, bindings: Readonly<Record<string, unknown>>): RawChain
|
|
1123
|
-
|
|
1079
|
+
interior(name: string, bindings: Readonly<Record<string, unknown>>): RawChain
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
/** The declared derived tables a chain may name. */
|
|
1083
|
+
interface DerivedEnv {
|
|
1084
|
+
readonly interiors: readonly InteriorData[]
|
|
1085
|
+
readonly rec: RecHandle | RecHead | RecData | null
|
|
1124
1086
|
}
|
|
1125
1087
|
|
|
1126
1088
|
/** Which rule family a chain builds — plus the schema's runtime class map and theory value (the join judge's authority). */
|
|
1127
|
-
type ChainContext = { readonly classes: SchemaClasses; readonly theory: AnySchema } &
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1089
|
+
type ChainContext = { readonly classes: SchemaClasses; readonly theory: AnySchema } & DerivedEnv &
|
|
1090
|
+
(
|
|
1091
|
+
| { readonly kind: "query" }
|
|
1092
|
+
| { readonly kind: "interior"; readonly self: string }
|
|
1093
|
+
| { readonly kind: "rec-base"; readonly self: RecHandle }
|
|
1094
|
+
| { readonly kind: "rec-arm"; readonly self: RecHead }
|
|
1095
|
+
)
|
|
1132
1096
|
|
|
1133
1097
|
/** The diagnostic label of a chain context. */
|
|
1134
1098
|
function contextLabel(context: ChainContext): string {
|
|
1135
1099
|
switch (context.kind) {
|
|
1136
1100
|
case "query":
|
|
1137
1101
|
return "query rule"
|
|
1138
|
-
case "
|
|
1139
|
-
return `
|
|
1140
|
-
case "
|
|
1141
|
-
return
|
|
1102
|
+
case "interior":
|
|
1103
|
+
return `interior ${context.self} rule`
|
|
1104
|
+
case "rec-base":
|
|
1105
|
+
return `recursive ${context.self.name} base`
|
|
1106
|
+
case "rec-arm":
|
|
1107
|
+
return `recursive ${context.self.name} rec`
|
|
1142
1108
|
}
|
|
1143
1109
|
}
|
|
1144
1110
|
|
|
1145
|
-
/**
|
|
1146
|
-
function
|
|
1147
|
-
context
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
)
|
|
1152
|
-
if (context.kind === "query") {
|
|
1153
|
-
throw errors.new("idb is a program construct — declare recs and outputs through program(), never a plain query()")
|
|
1154
|
-
}
|
|
1155
|
-
if (context.kind === "rec") {
|
|
1156
|
-
if (target.data !== context.self) {
|
|
1111
|
+
/** Resolves a derived-table name against the context's visible tables. */
|
|
1112
|
+
function lookupDerived(context: ChainContext, name: string): DerivedTable {
|
|
1113
|
+
const interior = context.interiors.find(function byName(candidate) {
|
|
1114
|
+
return candidate.name === name
|
|
1115
|
+
})
|
|
1116
|
+
if (interior !== undefined) {
|
|
1117
|
+
if (context.kind === "interior" && name === context.self) {
|
|
1157
1118
|
throw errors.new(
|
|
1158
|
-
`
|
|
1119
|
+
`interior ${name}: an interior does not read itself — declaration order is topological (a self-read is InteriorNotPrior)`
|
|
1159
1120
|
)
|
|
1160
1121
|
}
|
|
1161
|
-
return
|
|
1122
|
+
return interior
|
|
1162
1123
|
}
|
|
1163
|
-
if (
|
|
1164
|
-
|
|
1165
|
-
`
|
|
1166
|
-
|
|
1124
|
+
if (context.rec !== null && context.rec.name === name) {
|
|
1125
|
+
if (context.kind === "interior") {
|
|
1126
|
+
throw errors.new(`interior ${context.self}: interiors cannot read the rec — this cut's interiors are a prefix`)
|
|
1127
|
+
}
|
|
1128
|
+
if (context.kind === "rec-base") {
|
|
1129
|
+
throw errors.new(
|
|
1130
|
+
`recursive ${context.rec.name}: a base arm does not read the rec — self-atoms belong on rec arms`
|
|
1131
|
+
)
|
|
1132
|
+
}
|
|
1133
|
+
if (!("finds" in context.rec)) {
|
|
1134
|
+
throw errors.new(`recursive ${context.rec.name}: rec arms resolve the rec head after base arms seal it`)
|
|
1135
|
+
}
|
|
1136
|
+
return context.rec
|
|
1167
1137
|
}
|
|
1168
|
-
|
|
1138
|
+
throw errors.new(`${contextLabel(context)}: no derived table named ${name} is in scope`)
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
/** Validates and records one interior atom per the context's cut. */
|
|
1142
|
+
function interiorAdvance(
|
|
1143
|
+
context: ChainContext,
|
|
1144
|
+
state: RuleBuildState,
|
|
1145
|
+
name: string,
|
|
1146
|
+
bindings: Readonly<Record<string, unknown>>
|
|
1147
|
+
): RuleBuildState {
|
|
1148
|
+
return advanceInterior(state, lookupDerived(context, name), bindings, "interior")
|
|
1169
1149
|
}
|
|
1170
1150
|
|
|
1171
1151
|
/**
|
|
1172
|
-
* Validates and records one NEGATED finished-
|
|
1173
|
-
*
|
|
1174
|
-
*
|
|
1175
|
-
* only negation *through* a cycle, which the rec-context refusal here
|
|
1176
|
-
* makes unwritable).
|
|
1152
|
+
* Validates and records one NEGATED finished-table atom — main and interior
|
|
1153
|
+
* rules: a finished set is a set. Rec bodies refuse every negation
|
|
1154
|
+
* (`NegationInRec` — self is the wall; EDB / earlier-interior is this-cut).
|
|
1177
1155
|
*/
|
|
1178
|
-
function
|
|
1156
|
+
function notInteriorAdvance(
|
|
1179
1157
|
context: ChainContext,
|
|
1180
1158
|
state: RuleBuildState,
|
|
1181
|
-
|
|
1159
|
+
name: string,
|
|
1182
1160
|
bindings: Readonly<Record<string, unknown>>
|
|
1183
1161
|
): RuleBuildState {
|
|
1184
|
-
if (context.kind === "
|
|
1185
|
-
throw errors.new("idb is a program construct — declare recs and outputs through program(), never a plain query()")
|
|
1186
|
-
}
|
|
1187
|
-
if (context.kind === "rec") {
|
|
1162
|
+
if (context.kind === "rec-base" || context.kind === "rec-arm") {
|
|
1188
1163
|
throw errors.new(
|
|
1189
|
-
`
|
|
1164
|
+
`recursive ${context.self.name}: a recursive 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`
|
|
1190
1165
|
)
|
|
1191
1166
|
}
|
|
1192
|
-
|
|
1193
|
-
throw errors.new(
|
|
1194
|
-
`idb ${target.name}: the rec was declared by a different program — rec identity is the membership rule`
|
|
1195
|
-
)
|
|
1196
|
-
}
|
|
1197
|
-
return advanceIdb(state, target.data, bindings, true)
|
|
1167
|
+
return advanceInterior(state, lookupDerived(context, name), bindings, "negatedInterior")
|
|
1198
1168
|
}
|
|
1199
1169
|
|
|
1200
|
-
/** Classifies one find record per the context (
|
|
1170
|
+
/** Classifies one find record per the context (interior and rec heads project bound variables only). */
|
|
1201
1171
|
function findColumns(context: ChainContext, entries: Readonly<Record<string, unknown>>): FindColumn[] {
|
|
1202
1172
|
const columns: FindColumn[] = []
|
|
1173
|
+
const derivedHead = context.kind !== "query"
|
|
1203
1174
|
for (const [name, entry] of Object.entries(entries)) {
|
|
1204
1175
|
if (entry === undefined) {
|
|
1205
1176
|
continue
|
|
1206
1177
|
}
|
|
1207
|
-
if (
|
|
1178
|
+
if (derivedHead && !(isTerm(entry) && entry[term] === "var")) {
|
|
1179
|
+
const who = context.kind === "interior" ? `interior ${context.self}` : `recursive ${context.self.name}`
|
|
1208
1180
|
throw errors.new(
|
|
1209
|
-
|
|
1181
|
+
`${who}: a recursive head projects bound variables only — aggregates and the measure read finished sets (unwritable here)`
|
|
1210
1182
|
)
|
|
1211
1183
|
}
|
|
1212
1184
|
columns.push(findColumnOf(name, entry))
|
|
@@ -1223,8 +1195,8 @@ function makeRawChain(context: ChainContext, state: RuleBuildState): RawChain {
|
|
|
1223
1195
|
where(cond) {
|
|
1224
1196
|
return makeRawChain(context, advanceWhere(context, state, cond))
|
|
1225
1197
|
},
|
|
1226
|
-
|
|
1227
|
-
return makeRawChain(context,
|
|
1198
|
+
interior(name, bindings) {
|
|
1199
|
+
return makeRawChain(context, interiorAdvance(context, state, name, bindings))
|
|
1228
1200
|
},
|
|
1229
1201
|
find(entries) {
|
|
1230
1202
|
return makeRuleValue<never, never>(completeRule(context, state, findColumns(context, entries)))
|
|
@@ -1241,8 +1213,8 @@ function makeRawScope(context: ChainContext): RawScope {
|
|
|
1241
1213
|
match(relation, bindings) {
|
|
1242
1214
|
return makeRawChain(context, advanceMatch(context, EMPTY_RULE, relation, bindings))
|
|
1243
1215
|
},
|
|
1244
|
-
|
|
1245
|
-
return makeRawChain(context,
|
|
1216
|
+
interior(name, bindings) {
|
|
1217
|
+
return makeRawChain(context, interiorAdvance(context, EMPTY_RULE, name, bindings))
|
|
1246
1218
|
}
|
|
1247
1219
|
}
|
|
1248
1220
|
Object.freeze(scope)
|
|
@@ -1264,32 +1236,68 @@ function isTypedScope<S>(scope: RawScope): scope is RawScope & S {
|
|
|
1264
1236
|
|
|
1265
1237
|
/** Builds one query-rule builder (the typed face of the raw builder). */
|
|
1266
1238
|
function makeQueryRuleScope<Rels extends SchemaRelations, Classes extends SchemaClasses>(
|
|
1267
|
-
theory: AnySchema
|
|
1239
|
+
theory: AnySchema,
|
|
1240
|
+
env: DerivedEnv
|
|
1268
1241
|
): QueryRuleScope<Rels, Classes> {
|
|
1269
|
-
const raw = makeRawScope({ kind: "query", classes: theory.classes, theory })
|
|
1242
|
+
const raw = makeRawScope({ kind: "query", classes: theory.classes, theory, ...env })
|
|
1270
1243
|
if (!isTypedScope<QueryRuleScope<Rels, Classes>>(raw)) {
|
|
1271
1244
|
throw errors.new("query rule builder construction incomplete")
|
|
1272
1245
|
}
|
|
1273
1246
|
return raw
|
|
1274
1247
|
}
|
|
1275
1248
|
|
|
1276
|
-
/** Builds one
|
|
1277
|
-
function
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1249
|
+
/** Builds one interior-rule builder. */
|
|
1250
|
+
function makeInteriorRuleScope<Rels extends SchemaRelations, Classes extends SchemaClasses>(
|
|
1251
|
+
theory: AnySchema,
|
|
1252
|
+
env: DerivedEnv,
|
|
1253
|
+
self: string
|
|
1254
|
+
): InteriorRuleScope<Rels, Classes> {
|
|
1255
|
+
const raw = makeRawScope({ kind: "interior", self, classes: theory.classes, theory, ...env })
|
|
1256
|
+
if (!isTypedScope<InteriorRuleScope<Rels, Classes>>(raw)) {
|
|
1257
|
+
throw errors.new("interior rule builder construction incomplete")
|
|
1283
1258
|
}
|
|
1284
1259
|
return raw
|
|
1285
1260
|
}
|
|
1286
1261
|
|
|
1287
|
-
/**
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1262
|
+
/** Builds one rec-arm builder. */
|
|
1263
|
+
function makeRecRuleScope<Rels extends SchemaRelations, Classes extends SchemaClasses>(
|
|
1264
|
+
theory: AnySchema,
|
|
1265
|
+
env: DerivedEnv,
|
|
1266
|
+
self: RecHandle,
|
|
1267
|
+
kind: "rec-base"
|
|
1268
|
+
): RecRuleScope<Rels, Classes>
|
|
1269
|
+
function makeRecRuleScope<Rels extends SchemaRelations, Classes extends SchemaClasses>(
|
|
1270
|
+
theory: AnySchema,
|
|
1271
|
+
env: DerivedEnv,
|
|
1272
|
+
self: RecHead,
|
|
1273
|
+
kind: "rec-arm"
|
|
1274
|
+
): RecRuleScope<Rels, Classes>
|
|
1275
|
+
function makeRecRuleScope<Rels extends SchemaRelations, Classes extends SchemaClasses>(
|
|
1276
|
+
theory: AnySchema,
|
|
1277
|
+
env: DerivedEnv,
|
|
1278
|
+
self: RecHandle | RecHead,
|
|
1279
|
+
kind: "rec-base" | "rec-arm"
|
|
1280
|
+
): RecRuleScope<Rels, Classes> {
|
|
1281
|
+
const raw =
|
|
1282
|
+
kind === "rec-base"
|
|
1283
|
+
? makeRawScope({
|
|
1284
|
+
kind: "rec-base",
|
|
1285
|
+
self: self as RecHandle,
|
|
1286
|
+
classes: theory.classes,
|
|
1287
|
+
theory,
|
|
1288
|
+
...env
|
|
1289
|
+
})
|
|
1290
|
+
: makeRawScope({
|
|
1291
|
+
kind: "rec-arm",
|
|
1292
|
+
self: self as RecHead,
|
|
1293
|
+
classes: theory.classes,
|
|
1294
|
+
theory,
|
|
1295
|
+
...env
|
|
1296
|
+
})
|
|
1297
|
+
if (!isTypedScope<RecRuleScope<Rels, Classes>>(raw)) {
|
|
1298
|
+
throw errors.new("recursive rule builder construction incomplete")
|
|
1299
|
+
}
|
|
1300
|
+
return raw
|
|
1293
1301
|
}
|
|
1294
1302
|
|
|
1295
1303
|
/** Renders one head column's closed slice for the rule-alignment check's diagnostics. */
|
|
@@ -1307,9 +1315,6 @@ function headSignature(column: FindColumn): string {
|
|
|
1307
1315
|
if (agg.op === "fold") {
|
|
1308
1316
|
return `${column.name}:${agg.fold}`
|
|
1309
1317
|
}
|
|
1310
|
-
if (agg.op === "arg") {
|
|
1311
|
-
return `${column.name}:${agg.direction}`
|
|
1312
|
-
}
|
|
1313
1318
|
return `${column.name}:${agg.op}`
|
|
1314
1319
|
}
|
|
1315
1320
|
|
|
@@ -1324,12 +1329,22 @@ function renderParamAnchor(roster: ClosedRoster | undefined): string {
|
|
|
1324
1329
|
}
|
|
1325
1330
|
|
|
1326
1331
|
/**
|
|
1327
|
-
* Folds every rule's param uses (
|
|
1328
|
-
*
|
|
1329
|
-
* use mints the dense `ParamId`, the first
|
|
1330
|
-
* wire, and one name keeps one shape AND one
|
|
1332
|
+
* Folds every rule's param uses (interiors in declaration order, then rec
|
|
1333
|
+
* base, then rec arms, then main — exactly the lowering walk) into the
|
|
1334
|
+
* query's registry: first use mints the dense `ParamId`, the first
|
|
1335
|
+
* FIELD-ANCHORED use types the wire, and one name keeps one shape AND one
|
|
1336
|
+
* closedness. The orderable ban needs no registry arm: an order use always
|
|
1337
|
+
* anchors its SIBLING's domain (the no-variable-side spelling is refused
|
|
1338
|
+
* at the comparison constructor), so a closed-anchored param under an
|
|
1339
|
+
* order op dies at the one-domain wall here — and an order use whose
|
|
1340
|
+
* sibling is itself closed-bound dies at the comparison's own var-side
|
|
1341
|
+
* wall first.
|
|
1331
1342
|
*/
|
|
1332
|
-
function paramRegistryOf(
|
|
1343
|
+
function paramRegistryOf(
|
|
1344
|
+
interiors: readonly InteriorData[],
|
|
1345
|
+
rec: RecData | null,
|
|
1346
|
+
rules: readonly RuleData[]
|
|
1347
|
+
): readonly ParamEntry[] {
|
|
1333
1348
|
const order: string[] = []
|
|
1334
1349
|
const byName = new Map<
|
|
1335
1350
|
string,
|
|
@@ -1338,7 +1353,6 @@ function paramRegistryOf(recs: readonly RecData[], rules: readonly RuleData[]):
|
|
|
1338
1353
|
anchor: ParamEntry["anchor"]
|
|
1339
1354
|
op: ParamEntry["op"]
|
|
1340
1355
|
members: readonly string[] | undefined
|
|
1341
|
-
orderOp: "lt" | "le" | "gt" | "ge" | "pointIn" | undefined
|
|
1342
1356
|
}
|
|
1343
1357
|
>()
|
|
1344
1358
|
function fold(uses: readonly ParamUse[]): void {
|
|
@@ -1350,8 +1364,7 @@ function paramRegistryOf(recs: readonly RecData[], rules: readonly RuleData[]):
|
|
|
1350
1364
|
shape: use.shape,
|
|
1351
1365
|
anchor: use.anchor,
|
|
1352
1366
|
op: use.op,
|
|
1353
|
-
members: use.members
|
|
1354
|
-
orderOp: isOrderOp(use.op) ? use.op : undefined
|
|
1367
|
+
members: use.members
|
|
1355
1368
|
})
|
|
1356
1369
|
continue
|
|
1357
1370
|
}
|
|
@@ -1378,13 +1391,18 @@ function paramRegistryOf(recs: readonly RecData[], rules: readonly RuleData[]):
|
|
|
1378
1391
|
existing.anchor = use.anchor
|
|
1379
1392
|
existing.op = use.op
|
|
1380
1393
|
}
|
|
1381
|
-
if (existing.orderOp === undefined && isOrderOp(use.op)) {
|
|
1382
|
-
existing.orderOp = use.op
|
|
1383
|
-
}
|
|
1384
1394
|
}
|
|
1385
1395
|
}
|
|
1386
|
-
for (const
|
|
1387
|
-
for (const rule of
|
|
1396
|
+
for (const interior of interiors) {
|
|
1397
|
+
for (const rule of interior.rules) {
|
|
1398
|
+
fold(rule.paramUses)
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
if (rec !== null) {
|
|
1402
|
+
for (const rule of rec.base) {
|
|
1403
|
+
fold(rule.paramUses)
|
|
1404
|
+
}
|
|
1405
|
+
for (const rule of rec.rec) {
|
|
1388
1406
|
fold(rule.paramUses)
|
|
1389
1407
|
}
|
|
1390
1408
|
}
|
|
@@ -1397,10 +1415,6 @@ function paramRegistryOf(recs: readonly RecData[], rules: readonly RuleData[]):
|
|
|
1397
1415
|
if (entry === undefined) {
|
|
1398
1416
|
throw errors.new(`query param ${name} lost its registry entry`)
|
|
1399
1417
|
}
|
|
1400
|
-
const anchorRoster = anchorRosterOf(entry.anchor)
|
|
1401
|
-
if (entry.orderOp !== undefined && anchorRoster !== undefined) {
|
|
1402
|
-
throw closedOrderError(`query param ${name}`, `its ${entry.orderOp} use's anchor`, anchorRoster.name)
|
|
1403
|
-
}
|
|
1404
1418
|
/**
|
|
1405
1419
|
* A membership array's handle names are program constants, so the
|
|
1406
1420
|
* entry stores the resolved IMAGE: each name rides the one
|
|
@@ -1434,114 +1448,252 @@ interface RawQuery {
|
|
|
1434
1448
|
readonly schema: AnySchema
|
|
1435
1449
|
readonly data: QueryData
|
|
1436
1450
|
rule(build: (r: RawScope) => RuleValue<never, never>): RawQuery
|
|
1451
|
+
interior(name: string, ...builds: never[]): never
|
|
1452
|
+
recursive(name: string, arms: never): never
|
|
1437
1453
|
}
|
|
1438
1454
|
|
|
1439
|
-
/**
|
|
1440
|
-
|
|
1441
|
-
* derive the SAME head (name and aggregate shape, position for position —
|
|
1442
|
-
* the decode labels and the engine's alignment rule agree), and the param
|
|
1443
|
-
* registry folds in program-walk order.
|
|
1444
|
-
*/
|
|
1445
|
-
function makeRawQuery(theory: AnySchema, recs: readonly RecData[], rules: readonly RuleData[]): RawQuery {
|
|
1455
|
+
/** Asserts every rule in a list derives the same head (name, aggregate shape, closed slice, class). */
|
|
1456
|
+
function assertAlignedHeads(label: string, rules: readonly RuleData[]): void {
|
|
1446
1457
|
const first = rules[0]
|
|
1447
1458
|
if (first === undefined) {
|
|
1448
|
-
throw errors.new(
|
|
1459
|
+
throw errors.new(`${label} needs at least one rule`)
|
|
1449
1460
|
}
|
|
1450
1461
|
const signature = first.finds.map(headSignature).join(", ")
|
|
1451
1462
|
rules.forEach(function verifyHead(rule, index) {
|
|
1452
1463
|
const candidate = rule.finds.map(headSignature).join(", ")
|
|
1453
1464
|
if (candidate !== signature) {
|
|
1454
1465
|
throw errors.new(
|
|
1455
|
-
`every rule of
|
|
1466
|
+
`every rule of ${label} derives the same head — rule 0 finds (${signature}), rule ${index} finds (${candidate})`
|
|
1456
1467
|
)
|
|
1457
1468
|
}
|
|
1458
|
-
// The closed slice is part of the head too: one answer column decodes
|
|
1459
|
-
// through one roster, so a union whose rules bind a column at different
|
|
1460
|
-
// vocabularies (or one closed, one bare) is refused pointed.
|
|
1461
1469
|
rule.finds.forEach(function verifyClosedSlice(column, position) {
|
|
1462
1470
|
const lead = first.finds[position]
|
|
1463
1471
|
if (lead !== undefined && column.closed !== lead.closed) {
|
|
1464
1472
|
throw errors.new(
|
|
1465
|
-
`every rule of
|
|
1473
|
+
`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)`
|
|
1466
1474
|
)
|
|
1467
1475
|
}
|
|
1468
|
-
// The law-class wall on the union head: one answer column is one
|
|
1469
|
-
// value space, so the classed mint slot each rule binds the column
|
|
1470
|
-
// at must join across rules — the SAME fieldJoins judgment every
|
|
1471
|
-
// join/eq/negated-atom position enforces (the SDK holds it because
|
|
1472
|
-
// the wire IR carries no domains).
|
|
1473
1476
|
if (lead === undefined) {
|
|
1474
1477
|
return
|
|
1475
1478
|
}
|
|
1476
1479
|
if (lead.slot !== undefined && column.slot !== undefined && !fieldJoins(lead.slot, column.slot)) {
|
|
1477
1480
|
throw errors.new(
|
|
1478
|
-
`every rule of
|
|
1481
|
+
`every rule of ${label} derives the same head — the head column ${lead.name} is bound at ${renderFieldKind(lead.slot)} in rule 0 but at ${renderFieldKind(column.slot)} in rule ${index} (a head column joins only class-equal slots; bare pairs only with bare)`
|
|
1479
1482
|
)
|
|
1480
1483
|
}
|
|
1481
1484
|
})
|
|
1482
1485
|
})
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
function afterMainError(what: string): Error {
|
|
1489
|
+
return errors.new(
|
|
1490
|
+
`query: ${what} after a main rule is unwritable — declaration order is interiors, then rec, then main`
|
|
1491
|
+
)
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
/**
|
|
1495
|
+
* Assembles the runtime query value over completed rules: every rule must
|
|
1496
|
+
* derive the SAME head (name and aggregate shape, position for position —
|
|
1497
|
+
* the decode labels and the engine's alignment rule agree), and the param
|
|
1498
|
+
* registry folds in query-walk order.
|
|
1499
|
+
*/
|
|
1500
|
+
function makeRawQuery(
|
|
1501
|
+
theory: AnySchema,
|
|
1502
|
+
interiors: readonly InteriorData[],
|
|
1503
|
+
rec: RecData | null,
|
|
1504
|
+
rules: readonly RuleData[]
|
|
1505
|
+
): RawQuery {
|
|
1506
|
+
assertAlignedHeads("a query", rules)
|
|
1507
|
+
const first = rules[0]
|
|
1508
|
+
if (first === undefined) {
|
|
1509
|
+
throw errors.new("a query needs at least one rule")
|
|
1510
|
+
}
|
|
1511
|
+
const env: DerivedEnv = { interiors, rec }
|
|
1483
1512
|
const data: QueryData = Object.freeze({
|
|
1484
|
-
|
|
1513
|
+
interiors: Object.freeze([...interiors]),
|
|
1514
|
+
rec,
|
|
1485
1515
|
rules: Object.freeze([...rules]),
|
|
1486
1516
|
finds: first.finds,
|
|
1487
|
-
params: paramRegistryOf(
|
|
1517
|
+
params: paramRegistryOf(interiors, rec, rules)
|
|
1488
1518
|
})
|
|
1489
1519
|
const value: RawQuery = {
|
|
1490
1520
|
schema: theory,
|
|
1491
1521
|
data,
|
|
1492
1522
|
rule(build) {
|
|
1493
|
-
const built = build(makeRawScope({ kind: "query", classes: theory.classes, theory }))
|
|
1494
|
-
return makeRawQuery(theory,
|
|
1523
|
+
const built = build(makeRawScope({ kind: "query", classes: theory.classes, theory, ...env }))
|
|
1524
|
+
return makeRawQuery(theory, interiors, rec, [...rules, built.rule])
|
|
1525
|
+
},
|
|
1526
|
+
interior() {
|
|
1527
|
+
throw afterMainError("interior")
|
|
1528
|
+
},
|
|
1529
|
+
recursive() {
|
|
1530
|
+
throw afterMainError("recursive")
|
|
1495
1531
|
}
|
|
1496
1532
|
}
|
|
1497
1533
|
Object.freeze(value)
|
|
1498
1534
|
return value
|
|
1499
1535
|
}
|
|
1500
1536
|
|
|
1501
|
-
/**
|
|
1502
|
-
* The query values' trusted admission seam (the {@link isTypedScope} pattern):
|
|
1503
|
-
* the checkable fact — the value was assembled over the identical theory —
|
|
1504
|
-
* is verified before the raw value is admitted at its typed face.
|
|
1505
|
-
*/
|
|
1506
|
-
function isQueryValue<Rels extends SchemaRelations, Row, P extends ParamsRecord, Classes extends SchemaClasses>(
|
|
1507
|
-
theory: Schema<Rels, Classes>,
|
|
1508
|
-
value: RawQuery
|
|
1509
|
-
): value is RawQuery & Query<Rels, Row, P, Classes> {
|
|
1510
|
-
return value.schema === theory
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
|
-
/** Assembles one typed query value (rules already completed). */
|
|
1514
1537
|
function makeQuery<Rels extends SchemaRelations, Row, P extends ParamsRecord, Classes extends SchemaClasses>(
|
|
1515
1538
|
theory: Schema<Rels, Classes>,
|
|
1516
|
-
|
|
1539
|
+
interiors: readonly InteriorData[],
|
|
1540
|
+
rec: RecData | null,
|
|
1517
1541
|
rules: readonly RuleData[]
|
|
1518
1542
|
): Query<Rels, Row, P, Classes> {
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1543
|
+
return makeRawQuery(theory, interiors, rec, rules) as unknown as Query<Rels, Row, P, Classes>
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
/** Collects one Interior from its builders. */
|
|
1547
|
+
function collectInterior<Rels extends SchemaRelations, Classes extends SchemaClasses>(
|
|
1548
|
+
theory: Schema<Rels, Classes>,
|
|
1549
|
+
env: DerivedEnv,
|
|
1550
|
+
name: string,
|
|
1551
|
+
builds: readonly InteriorBuild<Rels, Classes>[]
|
|
1552
|
+
): InteriorData {
|
|
1553
|
+
if (builds.length === 0) {
|
|
1554
|
+
throw errors.new(`query: interior ${name} needs at least one rule`)
|
|
1522
1555
|
}
|
|
1523
|
-
|
|
1556
|
+
const rules = builds.map(function buildRule(buildOne) {
|
|
1557
|
+
return buildOne(makeInteriorRuleScope<Rels, Classes>(theory, env, name)).rule
|
|
1558
|
+
})
|
|
1559
|
+
assertAlignedHeads(`interior ${name}`, rules)
|
|
1560
|
+
const first = rules[0]
|
|
1561
|
+
if (first === undefined) {
|
|
1562
|
+
throw errors.new(`query: interior ${name} needs at least one rule`)
|
|
1563
|
+
}
|
|
1564
|
+
return Object.freeze({ name, finds: first.finds, rules: Object.freeze(rules) })
|
|
1524
1565
|
}
|
|
1525
1566
|
|
|
1526
|
-
/**
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1567
|
+
/** Collects the Rec from tagged base/rec builder arrays. */
|
|
1568
|
+
function collectRec<Rels extends SchemaRelations, Classes extends SchemaClasses>(
|
|
1569
|
+
theory: Schema<Rels, Classes>,
|
|
1570
|
+
interiors: readonly InteriorData[],
|
|
1571
|
+
name: string,
|
|
1572
|
+
baseBuilds: readonly RecBuild<Rels, Classes>[],
|
|
1573
|
+
recBuilds: readonly RecBuild<Rels, Classes>[]
|
|
1574
|
+
): RecData {
|
|
1575
|
+
if (baseBuilds.length === 0) {
|
|
1576
|
+
throw errors.new(`query: recursive ${name} has no base arms`)
|
|
1577
|
+
}
|
|
1578
|
+
if (recBuilds.length === 0) {
|
|
1579
|
+
throw errors.new(`query: recursive ${name} has no rec arms`)
|
|
1580
|
+
}
|
|
1581
|
+
const handle: RecHandle = Object.freeze({ name })
|
|
1582
|
+
const baseEnv: DerivedEnv = { interiors, rec: handle }
|
|
1583
|
+
const base = baseBuilds.map(function buildBase(buildOne) {
|
|
1584
|
+
return buildOne(makeRecRuleScope<Rels, Classes>(theory, baseEnv, handle, "rec-base")).rule
|
|
1585
|
+
})
|
|
1586
|
+
assertAlignedHeads(`recursive ${name}`, base)
|
|
1587
|
+
const first = base[0]
|
|
1588
|
+
if (first === undefined) {
|
|
1589
|
+
throw errors.new(`query: recursive ${name} has no base arms`)
|
|
1590
|
+
}
|
|
1591
|
+
const firstFind = first.finds[0]
|
|
1592
|
+
if (firstFind === undefined) {
|
|
1593
|
+
throw errors.new(`query: recursive ${name} has no head`)
|
|
1594
|
+
}
|
|
1595
|
+
const finds: RecHead["finds"] = [firstFind, ...first.finds.slice(1)]
|
|
1596
|
+
const head: RecHead = Object.freeze({ name, finds })
|
|
1597
|
+
const recEnv: DerivedEnv = { interiors, rec: head }
|
|
1598
|
+
const rec = recBuilds.map(function buildRec(buildOne) {
|
|
1599
|
+
return buildOne(makeRecRuleScope<Rels, Classes>(theory, recEnv, head, "rec-arm")).rule
|
|
1600
|
+
})
|
|
1601
|
+
assertAlignedHeads(`recursive ${name}`, [...base, ...rec])
|
|
1602
|
+
const firstRec = rec[0]
|
|
1603
|
+
if (firstRec === undefined) {
|
|
1604
|
+
throw errors.new(`query: recursive ${name} has no rec arms`)
|
|
1605
|
+
}
|
|
1606
|
+
const sealedBase: RecData["base"] = [first, ...base.slice(1)]
|
|
1607
|
+
const sealedRec: RecData["rec"] = [firstRec, ...rec.slice(1)]
|
|
1608
|
+
const recData: RecData = Object.freeze({
|
|
1609
|
+
name,
|
|
1610
|
+
finds,
|
|
1611
|
+
base: sealedBase,
|
|
1612
|
+
rec: sealedRec
|
|
1613
|
+
})
|
|
1614
|
+
return recData
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
/** Builds the query start (interiors, then optional rec, then the first main rule). */
|
|
1618
|
+
function makeQueryStart<
|
|
1619
|
+
Rels extends SchemaRelations,
|
|
1620
|
+
Classes extends SchemaClasses,
|
|
1621
|
+
P extends ParamsRecord,
|
|
1622
|
+
Rec extends RecData | null
|
|
1623
|
+
>(theory: Schema<Rels, Classes>, interiors: readonly InteriorData[], rec: Rec): QueryStart<Rels, Classes, P, Rec> {
|
|
1624
|
+
const env: DerivedEnv = { interiors, rec }
|
|
1625
|
+
const start = {
|
|
1626
|
+
interior<const Builds extends readonly InteriorBuild<Rels, Classes>[]>(
|
|
1627
|
+
name: string,
|
|
1628
|
+
...builds: Builds
|
|
1629
|
+
): QueryStart<Rels, Classes, Flatten<P & BuildsParams<Builds>>, null> {
|
|
1630
|
+
if (rec !== null) {
|
|
1631
|
+
throw errors.new(
|
|
1632
|
+
"query: interior after recursive is unwritable — declaration order is interiors, then rec, then main"
|
|
1633
|
+
)
|
|
1634
|
+
}
|
|
1635
|
+
if (
|
|
1636
|
+
interiors.some(function sameName(interior) {
|
|
1637
|
+
return interior.name === name
|
|
1638
|
+
})
|
|
1639
|
+
) {
|
|
1640
|
+
throw errors.new(`query: interior ${name} is already declared — names are unique`)
|
|
1641
|
+
}
|
|
1642
|
+
if (name.length === 0) {
|
|
1643
|
+
throw errors.new("query: an interior needs a name")
|
|
1644
|
+
}
|
|
1645
|
+
const data = collectInterior(theory, env, name, builds)
|
|
1646
|
+
return makeQueryStart<Rels, Classes, Flatten<P & BuildsParams<Builds>>, null>(theory, [...interiors, data], null)
|
|
1647
|
+
},
|
|
1648
|
+
recursive<
|
|
1649
|
+
const Base extends readonly RecBuild<Rels, Classes>[],
|
|
1650
|
+
const Step extends readonly RecBuild<Rels, Classes>[]
|
|
1651
|
+
>(
|
|
1652
|
+
name: string,
|
|
1653
|
+
arms: { readonly base: Base; readonly rec: Step }
|
|
1654
|
+
): QueryStart<Rels, Classes, Flatten<P & BuildsParams<Base> & BuildsParams<Step>>, RecData> {
|
|
1655
|
+
if (rec !== null) {
|
|
1656
|
+
throw errors.new("query: a second recursive is unwritable — this cut admits one linear rec")
|
|
1657
|
+
}
|
|
1658
|
+
if (
|
|
1659
|
+
interiors.some(function sameName(interior) {
|
|
1660
|
+
return interior.name === name
|
|
1661
|
+
})
|
|
1662
|
+
) {
|
|
1663
|
+
throw errors.new(`query: interior and recursive share the name ${name}`)
|
|
1664
|
+
}
|
|
1665
|
+
if (name.length === 0) {
|
|
1666
|
+
throw errors.new("query: recursive needs a name")
|
|
1667
|
+
}
|
|
1668
|
+
const data = collectRec(theory, interiors, name, arms.base, arms.rec)
|
|
1669
|
+
return makeQueryStart<Rels, Classes, Flatten<P & BuildsParams<Base> & BuildsParams<Step>>, RecData>(
|
|
1670
|
+
theory,
|
|
1671
|
+
interiors,
|
|
1672
|
+
data
|
|
1673
|
+
)
|
|
1674
|
+
},
|
|
1536
1675
|
rule<RV extends AnyRuleValue>(
|
|
1537
1676
|
build: (r: QueryRuleScope<Rels, Classes>) => RV
|
|
1538
|
-
): Query<Rels, RowOf<RV>, ParamsOf<RV
|
|
1539
|
-
const built = build(makeQueryRuleScope<Rels, Classes>(theory))
|
|
1540
|
-
return makeQuery<Rels, RowOf<RV>, ParamsOf<RV
|
|
1677
|
+
): Query<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes> {
|
|
1678
|
+
const built = build(makeQueryRuleScope<Rels, Classes>(theory, env))
|
|
1679
|
+
return makeQuery<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes>(theory, interiors, rec, [built.rule])
|
|
1541
1680
|
}
|
|
1542
1681
|
}
|
|
1543
1682
|
Object.freeze(start)
|
|
1544
|
-
return start
|
|
1683
|
+
return start as unknown as QueryStart<Rels, Classes, P, Rec>
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
/**
|
|
1687
|
+
* Opens a query over a schema: `query(S).rule(r => ...)`, optionally with
|
|
1688
|
+
* `interior` / `recursive` first. Each `.rule` adds one conjunctive rule;
|
|
1689
|
+
* multiple rules are the set union. The schema's law-computed class map and
|
|
1690
|
+
* theory value ride into every rule builder — the join walls compare
|
|
1691
|
+
* against the mint slots off it.
|
|
1692
|
+
*/
|
|
1693
|
+
function query<Rels extends SchemaRelations, Classes extends SchemaClasses>(
|
|
1694
|
+
theory: Schema<Rels, Classes>
|
|
1695
|
+
): QueryStart<Rels, Classes> {
|
|
1696
|
+
return makeQueryStart<Rels, Classes, Record<never, never>, null>(theory, [], null)
|
|
1545
1697
|
}
|
|
1546
1698
|
|
|
1547
1699
|
/**
|
|
@@ -1674,7 +1826,7 @@ function taggedCmpLiteral(
|
|
|
1674
1826
|
interface LowerContext {
|
|
1675
1827
|
readonly theory: AnySchema
|
|
1676
1828
|
readonly relationIds: ReadonlyMap<string, number>
|
|
1677
|
-
readonly
|
|
1829
|
+
readonly interiorIds: ReadonlyMap<string, number>
|
|
1678
1830
|
readonly paramIds: ReadonlyMap<string, number>
|
|
1679
1831
|
readonly params: ReadonlyMap<string, ParamEntry>
|
|
1680
1832
|
}
|
|
@@ -1755,35 +1907,34 @@ function lowerBindingTerm(ctx: LowerContext, context: string, binding: BindingEn
|
|
|
1755
1907
|
}
|
|
1756
1908
|
|
|
1757
1909
|
/**
|
|
1758
|
-
* Lowers one
|
|
1759
|
-
* head position i. Every head column of the
|
|
1910
|
+
* Lowers one interior atom: named bindings placed by HEAD order, `FieldId(i)` =
|
|
1911
|
+
* head position i. Every head column of the table must be bound (a missing key
|
|
1760
1912
|
* is refused pointed); the var-id assignment order is head order, so the
|
|
1761
1913
|
* first-use numbering matches the name-keyed edition exactly.
|
|
1762
1914
|
*/
|
|
1763
|
-
function
|
|
1915
|
+
function lowerInteriorAtom(
|
|
1764
1916
|
ctx: LowerContext,
|
|
1765
|
-
|
|
1917
|
+
target: DerivedTable,
|
|
1766
1918
|
bindings: ReadonlyArray<{ readonly key: string; readonly ref: AnyVar }>,
|
|
1767
1919
|
ids: VarIds
|
|
1768
1920
|
): AtomIr {
|
|
1769
|
-
const
|
|
1770
|
-
if (
|
|
1771
|
-
throw errors.new(`query lowering:
|
|
1921
|
+
const interior = ctx.interiorIds.get(target.name)
|
|
1922
|
+
if (interior === undefined) {
|
|
1923
|
+
throw errors.new(`query lowering: derived table ${target.name} was not declared on this query`)
|
|
1772
1924
|
}
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
throw errors.new(`query lowering: rec ${rec.name} has no rules`)
|
|
1925
|
+
if (target.finds.length === 0) {
|
|
1926
|
+
throw errors.new(`query lowering: derived table ${target.name} has no head`)
|
|
1776
1927
|
}
|
|
1777
|
-
const irBindings: Array<readonly [number, TermIr]> =
|
|
1928
|
+
const irBindings: Array<readonly [number, TermIr]> = target.finds.map(function lowerPosition(column, position) {
|
|
1778
1929
|
const binding = bindings.find(function byKey(candidate) {
|
|
1779
1930
|
return candidate.key === column.name
|
|
1780
1931
|
})
|
|
1781
1932
|
if (binding === undefined) {
|
|
1782
|
-
throw errors.new(`query lowering:
|
|
1933
|
+
throw errors.new(`query lowering: interior ${target.name} omits head column ${column.name}`)
|
|
1783
1934
|
}
|
|
1784
1935
|
return [position, { kind: "var", var: ids.of(binding.ref) } as const] as const
|
|
1785
1936
|
})
|
|
1786
|
-
return { source: { kind: "
|
|
1937
|
+
return { source: { kind: "interior", interior }, bindings: irBindings }
|
|
1787
1938
|
}
|
|
1788
1939
|
|
|
1789
1940
|
/** Lowers one comparison side; literals tag by the sibling's anchor (op-aware at `pointIn`). */
|
|
@@ -1825,25 +1976,17 @@ function cmpAnchorOf(ctx: LowerContext, sibling: CmpTermData): AnyField | "measu
|
|
|
1825
1976
|
|
|
1826
1977
|
/** Lowers one comparison. */
|
|
1827
1978
|
function lowerComparison(ctx: LowerContext, cmp: CmpData, ids: VarIds): ComparisonIr {
|
|
1828
|
-
if (cmp.op === "allen") {
|
|
1829
|
-
const maskData = cmp.mask
|
|
1830
|
-
if (maskData === undefined) {
|
|
1831
|
-
throw errors.new("query lowering: an allen comparison lost its mask")
|
|
1832
|
-
}
|
|
1833
|
-
const mask =
|
|
1834
|
-
maskData.kind === "literal"
|
|
1835
|
-
? { kind: "literal" as const, mask: maskData.mask }
|
|
1836
|
-
: { kind: "param" as const, param: paramIdOf(ctx, maskData.name) }
|
|
1979
|
+
if (cmp.op.kind === "allen") {
|
|
1837
1980
|
return {
|
|
1838
|
-
op: { kind: "allen", mask },
|
|
1981
|
+
op: { kind: "allen", mask: cmp.op.mask },
|
|
1839
1982
|
lhs: lowerCmpTerm(ctx, cmp.lhs, cmp.rhs, ids, "allen"),
|
|
1840
1983
|
rhs: lowerCmpTerm(ctx, cmp.rhs, cmp.lhs, ids, "allen")
|
|
1841
1984
|
}
|
|
1842
1985
|
}
|
|
1843
1986
|
return {
|
|
1844
|
-
op: { kind: cmp.op },
|
|
1845
|
-
lhs: lowerCmpTerm(ctx, cmp.lhs, cmp.rhs, ids, cmp.op),
|
|
1846
|
-
rhs: lowerCmpTerm(ctx, cmp.rhs, cmp.lhs, ids, cmp.op)
|
|
1987
|
+
op: { kind: cmp.op.kind },
|
|
1988
|
+
lhs: lowerCmpTerm(ctx, cmp.lhs, cmp.rhs, ids, cmp.op.kind),
|
|
1989
|
+
rhs: lowerCmpTerm(ctx, cmp.rhs, cmp.lhs, ids, cmp.op.kind)
|
|
1847
1990
|
}
|
|
1848
1991
|
}
|
|
1849
1992
|
|
|
@@ -1872,16 +2015,12 @@ function lowerFind(entry: FindEntryData, ids: VarIds): FindTermIr {
|
|
|
1872
2015
|
switch (agg.op) {
|
|
1873
2016
|
case "count":
|
|
1874
2017
|
return { kind: "aggregate", op: { kind: "count" } }
|
|
1875
|
-
case "countDistinct":
|
|
1876
|
-
return { kind: "aggregate", op: { kind: "countDistinct" }, over: ids.of(agg.over) }
|
|
1877
2018
|
case "fold": {
|
|
1878
2019
|
if ("duration" in agg.over) {
|
|
1879
2020
|
return { kind: "aggregateMeasure", op: { kind: agg.fold }, over: ids.of(agg.over.duration) }
|
|
1880
2021
|
}
|
|
1881
2022
|
return { kind: "aggregate", op: { kind: agg.fold }, over: ids.of(agg.over) }
|
|
1882
2023
|
}
|
|
1883
|
-
case "arg":
|
|
1884
|
-
return { kind: "aggregate", op: { kind: agg.direction, key: ids.of(agg.key) }, over: ids.of(agg.over) }
|
|
1885
2024
|
case "pack":
|
|
1886
2025
|
return { kind: "aggregate", op: { kind: "pack" }, over: ids.of(agg.over) }
|
|
1887
2026
|
}
|
|
@@ -1892,12 +2031,8 @@ function headOpOf(agg: AggData): HeadOpIr {
|
|
|
1892
2031
|
switch (agg.op) {
|
|
1893
2032
|
case "count":
|
|
1894
2033
|
return "count"
|
|
1895
|
-
case "countDistinct":
|
|
1896
|
-
return "countDistinct"
|
|
1897
2034
|
case "fold":
|
|
1898
2035
|
return agg.fold
|
|
1899
|
-
case "arg":
|
|
1900
|
-
return agg.direction
|
|
1901
2036
|
case "pack":
|
|
1902
2037
|
return "pack"
|
|
1903
2038
|
}
|
|
@@ -1928,9 +2063,12 @@ function lowerRule(ctx: LowerContext, rule: RuleData): RuleIr {
|
|
|
1928
2063
|
negated.push(lowerAtom(ctx, item.atom, ids))
|
|
1929
2064
|
break
|
|
1930
2065
|
}
|
|
1931
|
-
case "
|
|
1932
|
-
|
|
1933
|
-
|
|
2066
|
+
case "interior": {
|
|
2067
|
+
atoms.push(lowerInteriorAtom(ctx, item.target, item.bindings, ids))
|
|
2068
|
+
break
|
|
2069
|
+
}
|
|
2070
|
+
case "negatedInterior": {
|
|
2071
|
+
negated.push(lowerInteriorAtom(ctx, item.target, item.bindings, ids))
|
|
1934
2072
|
break
|
|
1935
2073
|
}
|
|
1936
2074
|
case "cond": {
|
|
@@ -1950,24 +2088,27 @@ function lowerRule(ctx: LowerContext, rule: RuleData): RuleIr {
|
|
|
1950
2088
|
}
|
|
1951
2089
|
|
|
1952
2090
|
/**
|
|
1953
|
-
* Lowers a query value to the bridge's `
|
|
1954
|
-
*
|
|
1955
|
-
*
|
|
2091
|
+
* Lowers a query value to the bridge's `QueryIr` — pure and stable: interiors
|
|
2092
|
+
* in declaration order, optional rec, then main. Every registered param must
|
|
2093
|
+
* carry a field anchor by now.
|
|
1956
2094
|
*/
|
|
1957
|
-
function lowerQuery(q: AnyQuery):
|
|
2095
|
+
function lowerQuery(q: AnyQuery): ParsedQuery {
|
|
1958
2096
|
const theory = q.schema
|
|
1959
2097
|
const relationIds = new Map<string, number>()
|
|
1960
2098
|
Object.keys(theory.relations).forEach(function assignOrdinal(name, index) {
|
|
1961
2099
|
relationIds.set(name, index)
|
|
1962
2100
|
})
|
|
1963
|
-
const
|
|
1964
|
-
q.data.
|
|
1965
|
-
|
|
2101
|
+
const interiorIds = new Map<string, number>()
|
|
2102
|
+
q.data.interiors.forEach(function assignInteriorId(interior, index) {
|
|
2103
|
+
interiorIds.set(interior.name, index)
|
|
1966
2104
|
})
|
|
2105
|
+
if (q.data.rec !== null) {
|
|
2106
|
+
interiorIds.set(q.data.rec.name, q.data.interiors.length)
|
|
2107
|
+
}
|
|
1967
2108
|
const paramIds = new Map<string, number>()
|
|
1968
2109
|
const params = new Map<string, ParamEntry>()
|
|
1969
2110
|
q.data.params.forEach(function assignParamId(entry, index) {
|
|
1970
|
-
if (entry.anchor === undefined
|
|
2111
|
+
if (entry.anchor === undefined) {
|
|
1971
2112
|
throw errors.new(
|
|
1972
2113
|
`query param ${entry.name} has no field-anchored use — bind it in an atom or compare it against a bound variable`
|
|
1973
2114
|
)
|
|
@@ -1975,26 +2116,33 @@ function lowerQuery(q: AnyQuery): ProgramIr {
|
|
|
1975
2116
|
paramIds.set(entry.name, index)
|
|
1976
2117
|
params.set(entry.name, entry)
|
|
1977
2118
|
})
|
|
1978
|
-
const ctx: LowerContext = { theory, relationIds,
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
2119
|
+
const ctx: LowerContext = { theory, relationIds, interiorIds, paramIds, params }
|
|
2120
|
+
return parseQueryIr({
|
|
2121
|
+
interiors: q.data.interiors.map(function lowerInterior(interior) {
|
|
2122
|
+
return {
|
|
2123
|
+
head: interior.finds.map(headTermOf),
|
|
2124
|
+
rules: interior.rules.map(function lowerInteriorRule(rule) {
|
|
2125
|
+
return lowerRule(ctx, rule)
|
|
2126
|
+
})
|
|
2127
|
+
}
|
|
2128
|
+
}),
|
|
2129
|
+
rec:
|
|
2130
|
+
q.data.rec === null
|
|
2131
|
+
? null
|
|
2132
|
+
: {
|
|
2133
|
+
head: q.data.rec.finds.map(headTermOf),
|
|
2134
|
+
base: q.data.rec.base.map(function lowerBase(rule) {
|
|
2135
|
+
return lowerRule(ctx, rule)
|
|
2136
|
+
}),
|
|
2137
|
+
rec: q.data.rec.rec.map(function lowerRecArm(rule) {
|
|
2138
|
+
return lowerRule(ctx, rule)
|
|
2139
|
+
})
|
|
2140
|
+
},
|
|
1992
2141
|
head: q.data.finds.map(headTermOf),
|
|
1993
|
-
rules: q.data.rules.map(function
|
|
2142
|
+
rules: q.data.rules.map(function lowerMainRule(rule) {
|
|
1994
2143
|
return lowerRule(ctx, rule)
|
|
1995
2144
|
})
|
|
1996
2145
|
})
|
|
1997
|
-
return { predicates, output: q.data.recs.length }
|
|
1998
2146
|
}
|
|
1999
2147
|
|
|
2000
2148
|
export type {
|
|
@@ -2002,10 +2150,9 @@ export type {
|
|
|
2002
2150
|
AnyRuleValue,
|
|
2003
2151
|
HeadOf,
|
|
2004
2152
|
HeadShape,
|
|
2005
|
-
|
|
2006
|
-
|
|
2153
|
+
InteriorRuleChain,
|
|
2154
|
+
InteriorRuleScope,
|
|
2007
2155
|
ParamsOf,
|
|
2008
|
-
ProgramState,
|
|
2009
2156
|
Query,
|
|
2010
2157
|
QueryData,
|
|
2011
2158
|
QueryParams,
|
|
@@ -2016,11 +2163,10 @@ export type {
|
|
|
2016
2163
|
QueryStart,
|
|
2017
2164
|
RawChain,
|
|
2018
2165
|
RawScope,
|
|
2019
|
-
RecRef,
|
|
2020
2166
|
RecRuleChain,
|
|
2021
2167
|
RecRuleScope,
|
|
2022
2168
|
RowOf,
|
|
2023
2169
|
RuleValue,
|
|
2024
2170
|
TermOps
|
|
2025
2171
|
}
|
|
2026
|
-
export { lowerQuery,
|
|
2172
|
+
export { lowerQuery, makeRawScope, query, taggedCmpLiteral, taggedLiteral }
|