@bjornpagen/bumbledb 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COOKBOOK.md +155 -136
- package/README.md +5 -9
- package/dist/db.d.ts +77 -109
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +121 -339
- package/dist/db.js.map +1 -1
- package/dist/index.d.ts +12 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -11
- package/dist/index.js.map +1 -1
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +8 -1
- package/dist/lower.js.map +1 -1
- package/dist/native.d.ts +77 -51
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js.map +1 -1
- package/dist/query/atom.d.ts +94 -112
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +8 -17
- package/dist/query/atom.js.map +1 -1
- package/dist/query/find.d.ts +18 -35
- package/dist/query/find.d.ts.map +1 -1
- package/dist/query/find.js +13 -32
- package/dist/query/find.js.map +1 -1
- package/dist/query/lower.d.ts +128 -124
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +366 -258
- package/dist/query/lower.js.map +1 -1
- package/dist/query/parse-ir.d.ts +12 -0
- package/dist/query/parse-ir.d.ts.map +1 -0
- package/dist/query/parse-ir.js +71 -0
- package/dist/query/parse-ir.js.map +1 -0
- package/dist/query/run.d.ts +2 -2
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +2 -13
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +4 -16
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +1 -6
- package/dist/query/scope.js.map +1 -1
- package/dist/schema.js +2 -2
- package/dist/schema.js.map +1 -1
- package/dist/statements.d.ts +8 -4
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +7 -9
- package/dist/statements.js.map +1 -1
- package/package.json +2 -2
- package/src/db.ts +182 -443
- package/src/index.ts +11 -24
- package/src/lower.ts +8 -1
- package/src/native.ts +79 -43
- package/src/query/atom.ts +105 -138
- package/src/query/find.ts +39 -80
- package/src/query/lower.ts +626 -434
- package/src/query/parse-ir.ts +82 -0
- package/src/query/run.ts +2 -14
- package/src/query/scope.ts +3 -21
- package/src/schema.ts +2 -2
- package/src/statements.ts +17 -13
- package/dist/order.d.ts +0 -87
- package/dist/order.d.ts.map +0 -1
- package/dist/order.js +0 -153
- package/dist/order.js.map +0 -1
- package/dist/query/predicate.d.ts +0 -91
- package/dist/query/predicate.d.ts.map +0 -1
- package/dist/query/predicate.js +0 -156
- package/dist/query/predicate.js.map +0 -1
- package/src/order.ts +0 -234
- package/src/query/predicate.ts +0 -269
package/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 rec 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,72 +254,80 @@ 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 `.reach("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
|
|
306
|
-
*
|
|
307
|
-
* and the measure are unrepresentable in a
|
|
281
|
+
* The chain of a rec 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
|
+
* rec 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
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
306
|
+
type QueryData =
|
|
307
|
+
| {
|
|
308
|
+
readonly kind: "cq"
|
|
309
|
+
/** Named interiors in declaration order (DAG). */
|
|
310
|
+
readonly interiors: readonly InteriorData[]
|
|
311
|
+
/** The main rules in written order (multiple rules = set union). */
|
|
312
|
+
readonly rules: readonly RuleData[]
|
|
313
|
+
/** The head columns (every rule derives the same head; written order = answer column order). */
|
|
314
|
+
readonly finds: readonly FindColumn[]
|
|
315
|
+
/** The registered params in first-use order across the query walk (= dense `ParamId`s). */
|
|
316
|
+
readonly params: readonly ParamEntry[]
|
|
317
|
+
}
|
|
318
|
+
| {
|
|
319
|
+
readonly kind: "reach"
|
|
320
|
+
/** Named interiors in declaration order (DAG). */
|
|
321
|
+
readonly interiors: readonly InteriorData[]
|
|
322
|
+
/** The linear rec (base and rec arms nonempty by type). */
|
|
323
|
+
readonly rec: RecData
|
|
324
|
+
/** The main rules in written order (multiple rules = set union). */
|
|
325
|
+
readonly rules: readonly RuleData[]
|
|
326
|
+
/** The head columns (every rule derives the same head; written order = answer column order). */
|
|
327
|
+
readonly finds: readonly FindColumn[]
|
|
328
|
+
/** The registered params in first-use order across the query walk (= dense `ParamId`s). */
|
|
329
|
+
readonly params: readonly ParamEntry[]
|
|
330
|
+
}
|
|
342
331
|
|
|
343
332
|
/**
|
|
344
333
|
* An inert query value. `Row` is the inferred answer-row object type;
|
|
@@ -357,6 +346,10 @@ interface Query<
|
|
|
357
346
|
rule<RV extends AnyRuleValue>(
|
|
358
347
|
build: (r: QueryRuleScope<Rels, Classes>) => RV
|
|
359
348
|
): Query<Rels, Row | RowOf<RV>, Flatten<Params & ParamsOf<RV>>, Classes>
|
|
349
|
+
/** Construction error: interiors precede main rules. Uncallable after `.rule()`. */
|
|
350
|
+
interior(name: string, ...builds: never[]): never
|
|
351
|
+
/** Construction error: reach precedes main rules. Uncallable after `.rule()`. */
|
|
352
|
+
reach(name: string, arms: never): never
|
|
360
353
|
readonly [inferred]?: { readonly row: Row; readonly params: Params }
|
|
361
354
|
}
|
|
362
355
|
|
|
@@ -372,18 +365,44 @@ type QueryRow<Q extends AnyQuery> = RowOf<Q>
|
|
|
372
365
|
/** Extracts a query value's inferred execute-params type. */
|
|
373
366
|
type QueryParams<Q extends AnyQuery> = ParamsOf<Q>
|
|
374
367
|
|
|
375
|
-
/**
|
|
376
|
-
|
|
368
|
+
/**
|
|
369
|
+
* The entry value of `query(S)`: interiors, then reach (moves to
|
|
370
|
+
* {@link QueryReachStart}), then the first `.rule` mints the query.
|
|
371
|
+
* `interior` / `reach` exist only on this CQ start.
|
|
372
|
+
*/
|
|
373
|
+
type QueryStart<
|
|
374
|
+
Rels extends SchemaRelations,
|
|
375
|
+
Classes extends SchemaClasses = SchemaClasses,
|
|
376
|
+
P extends ParamsRecord = Record<never, never>
|
|
377
|
+
> = {
|
|
377
378
|
rule<RV extends AnyRuleValue>(
|
|
378
379
|
build: (r: QueryRuleScope<Rels, Classes>) => RV
|
|
379
|
-
): Query<Rels, RowOf<RV>, ParamsOf<RV
|
|
380
|
+
): Query<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes>
|
|
381
|
+
interior<const Builds extends readonly InteriorBuild<Rels, Classes>[]>(
|
|
382
|
+
name: string,
|
|
383
|
+
...builds: Builds
|
|
384
|
+
): QueryStart<Rels, Classes, Flatten<P & BuildsParams<Builds>>>
|
|
385
|
+
reach<const Base extends readonly RecBuild<Rels, Classes>[], const Step extends readonly RecBuild<Rels, Classes>[]>(
|
|
386
|
+
name: string,
|
|
387
|
+
arms: { readonly base: Base; readonly rec: Step }
|
|
388
|
+
): QueryReachStart<Rels, Classes, Flatten<P & BuildsParams<Base> & BuildsParams<Step>>>
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** After `.reach()`: interior/reach are unrepresentable; only `.rule` remains. */
|
|
392
|
+
type QueryReachStart<
|
|
393
|
+
Rels extends SchemaRelations,
|
|
394
|
+
Classes extends SchemaClasses = SchemaClasses,
|
|
395
|
+
P extends ParamsRecord = Record<never, never>
|
|
396
|
+
> = {
|
|
397
|
+
rule<RV extends AnyRuleValue>(
|
|
398
|
+
build: (r: QueryRuleScope<Rels, Classes>) => RV
|
|
399
|
+
): Query<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes>
|
|
380
400
|
}
|
|
381
401
|
|
|
382
402
|
/** The frozen constructor vocabulary every rule builder spreads. */
|
|
383
403
|
const termOps: TermOps = Object.freeze({
|
|
384
404
|
param: makeParam,
|
|
385
405
|
inSet: makeSetParam,
|
|
386
|
-
maskParam: makeMaskParam,
|
|
387
406
|
duration: makeDuration,
|
|
388
407
|
eq,
|
|
389
408
|
ne,
|
|
@@ -397,12 +416,9 @@ const termOps: TermOps = Object.freeze({
|
|
|
397
416
|
or,
|
|
398
417
|
not,
|
|
399
418
|
count,
|
|
400
|
-
countDistinct,
|
|
401
419
|
sum,
|
|
402
420
|
min,
|
|
403
421
|
max,
|
|
404
|
-
argMax,
|
|
405
|
-
argMin,
|
|
406
422
|
pack
|
|
407
423
|
})
|
|
408
424
|
|
|
@@ -558,10 +574,6 @@ function resolveBindings(
|
|
|
558
574
|
)
|
|
559
575
|
break
|
|
560
576
|
}
|
|
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
577
|
case "duration":
|
|
566
578
|
throw errors.new(
|
|
567
579
|
`${label}.${fieldName}: the measure is not a field-typed value — it lives in comparisons and find entries`
|
|
@@ -607,7 +619,7 @@ function advanceMatch(
|
|
|
607
619
|
}
|
|
608
620
|
|
|
609
621
|
/** Resolves one comparison side to its runtime term (variables and the measure ride by reference). */
|
|
610
|
-
function cmpTermDataOf(
|
|
622
|
+
function cmpTermDataOf(value: unknown): CmpTermData {
|
|
611
623
|
if (isTerm(value)) {
|
|
612
624
|
switch (value[term]) {
|
|
613
625
|
case "var":
|
|
@@ -618,8 +630,6 @@ function cmpTermDataOf(op: string, value: unknown): CmpTermData {
|
|
|
618
630
|
return Object.freeze({ kind: "setParam" as const, name: value.name })
|
|
619
631
|
case "duration":
|
|
620
632
|
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
633
|
}
|
|
624
634
|
}
|
|
625
635
|
return Object.freeze({ kind: "literal" as const, value })
|
|
@@ -656,31 +666,23 @@ function sideUses(op: CmpKind, side: CmpTermData, sibling: CmpTermData, uses: Pa
|
|
|
656
666
|
/** Lowers one condition VALUE to its runtime data, recording param uses. */
|
|
657
667
|
function condDataOf(cond: AnyCond, uses: ParamUse[]): CondData {
|
|
658
668
|
if (cond.cond === "cmp") {
|
|
659
|
-
const lhs = cmpTermDataOf(cond.
|
|
660
|
-
const rhs = cmpTermDataOf(cond.
|
|
669
|
+
const lhs = cmpTermDataOf(cond.lhs)
|
|
670
|
+
const rhs = cmpTermDataOf(cond.rhs)
|
|
661
671
|
sideUses(cond.op, lhs, rhs, uses)
|
|
662
672
|
sideUses(cond.op, rhs, lhs, uses)
|
|
663
|
-
let mask: MaskData | undefined
|
|
664
673
|
if (cond.op === "allen") {
|
|
665
674
|
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")
|
|
675
|
+
if (typeof maskValue !== "number") {
|
|
676
|
+
throw errors.new("allen: the mask position takes a 13-bit mask number built from the ALLEN constants")
|
|
681
677
|
}
|
|
678
|
+
return Object.freeze({
|
|
679
|
+
kind: "cmp" as const,
|
|
680
|
+
op: { kind: "allen" as const, mask: maskValue },
|
|
681
|
+
lhs,
|
|
682
|
+
rhs
|
|
683
|
+
})
|
|
682
684
|
}
|
|
683
|
-
return Object.freeze({ kind: "cmp" as const, op: cond.op
|
|
685
|
+
return Object.freeze({ kind: "cmp" as const, op: { kind: cond.op }, lhs, rhs })
|
|
684
686
|
}
|
|
685
687
|
if (cond.cond === "tree") {
|
|
686
688
|
const children = cond.children.map(function lowerChild(child) {
|
|
@@ -698,13 +700,13 @@ function advanceWhere(context: ChainContext, state: RuleBuildState, cond: AnyCon
|
|
|
698
700
|
if (typeof cond !== "object" || cond === null || !("cond" in cond)) {
|
|
699
701
|
throw errors.new("where() takes a comparison, an and()/or() tree, or a negated atom")
|
|
700
702
|
}
|
|
701
|
-
if (cond.cond === "
|
|
703
|
+
if (cond.cond === "notInterior") {
|
|
702
704
|
const bindings: Readonly<Record<string, unknown>> = Object.fromEntries(
|
|
703
705
|
Object.entries(cond.bindings ?? {}).filter(function defined([, value]) {
|
|
704
706
|
return value !== undefined
|
|
705
707
|
})
|
|
706
708
|
)
|
|
707
|
-
return
|
|
709
|
+
return notInteriorAdvance(context, state, cond.name, bindings)
|
|
708
710
|
}
|
|
709
711
|
if (cond.cond === "not") {
|
|
710
712
|
const relation: MatchOwner = cond.relation
|
|
@@ -730,19 +732,19 @@ function advanceWhere(context: ChainContext, state: RuleBuildState, cond: AnyCon
|
|
|
730
732
|
}
|
|
731
733
|
|
|
732
734
|
/**
|
|
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
|
|
735
|
+
* Extends a rule state with one interior atom (a named record over head keys;
|
|
736
|
+
* vars validated at completion). A POSITIVE interior atom is a positive
|
|
737
|
+
* occurrence exactly as the engine represents it (`check_atoms` walks Interior
|
|
736
738
|
* and Edb in one loop), so its variables GROUND: they enter the rule's
|
|
737
739
|
* boundness set, may ride the head, and satisfy negation safety — the
|
|
738
|
-
*
|
|
740
|
+
* interior-only identity projection of a finished table is spellable with no
|
|
739
741
|
* re-grounding join. A NEGATED one binds nothing, only rejects.
|
|
740
742
|
*/
|
|
741
|
-
function
|
|
743
|
+
function advanceInterior(
|
|
742
744
|
state: RuleBuildState,
|
|
743
|
-
|
|
745
|
+
target: DerivedTable,
|
|
744
746
|
bindings: Readonly<Record<string, unknown>>,
|
|
745
|
-
|
|
747
|
+
kind: "interior" | "negatedInterior"
|
|
746
748
|
): RuleBuildState {
|
|
747
749
|
const resolved: Array<{ readonly key: string; readonly ref: AnyVar }> = []
|
|
748
750
|
for (const [key, value] of Object.entries(bindings)) {
|
|
@@ -751,31 +753,26 @@ function advanceIdb(
|
|
|
751
753
|
}
|
|
752
754
|
if (!isTerm(value) || value[term] !== "var") {
|
|
753
755
|
throw errors.new(
|
|
754
|
-
`
|
|
756
|
+
`interior ${target.name}: position ${key} takes a variable — bind literals and params through where()/match()`
|
|
755
757
|
)
|
|
756
758
|
}
|
|
757
759
|
resolved.push(Object.freeze({ key, ref: value }))
|
|
758
760
|
}
|
|
759
761
|
const bound = new Set(state.bound)
|
|
760
|
-
if (
|
|
762
|
+
if (kind === "interior") {
|
|
761
763
|
for (const binding of resolved) {
|
|
762
764
|
bound.add(binding.ref)
|
|
763
765
|
}
|
|
764
766
|
}
|
|
765
767
|
return Object.freeze({
|
|
766
|
-
items: Object.freeze([
|
|
767
|
-
...state.items,
|
|
768
|
-
Object.freeze({ kind: "idb" as const, rec, bindings: Object.freeze(resolved), negated })
|
|
769
|
-
]),
|
|
768
|
+
items: Object.freeze([...state.items, Object.freeze({ kind, target, bindings: Object.freeze(resolved) })]),
|
|
770
769
|
bound,
|
|
771
770
|
paramUses: state.paramUses
|
|
772
771
|
})
|
|
773
772
|
}
|
|
774
773
|
|
|
775
774
|
/** 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 } {
|
|
775
|
+
function isAggregateEntry(value: unknown): value is { readonly agg: string; readonly over: unknown } {
|
|
779
776
|
return typeof value === "object" && value !== null && "agg" in value
|
|
780
777
|
}
|
|
781
778
|
|
|
@@ -788,16 +785,11 @@ function asVarTerm(context: string, value: unknown): AnyVar {
|
|
|
788
785
|
}
|
|
789
786
|
|
|
790
787
|
/** 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 {
|
|
788
|
+
function aggDataOf(name: string, entry: { readonly agg: string; readonly over: unknown }): AggData {
|
|
795
789
|
const over = entry.over
|
|
796
790
|
switch (entry.agg) {
|
|
797
791
|
case "count":
|
|
798
792
|
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
793
|
case "sum":
|
|
802
794
|
case "min":
|
|
803
795
|
case "max": {
|
|
@@ -809,14 +801,6 @@ function aggDataOf(
|
|
|
809
801
|
}
|
|
810
802
|
throw errors.new(`find ${name} (${entry.agg}): takes a variable or r.duration(v)`)
|
|
811
803
|
}
|
|
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
804
|
case "pack":
|
|
821
805
|
return Object.freeze({ op: "pack" as const, over: asVarTerm(`find ${name} (pack)`, over) })
|
|
822
806
|
default:
|
|
@@ -903,17 +887,14 @@ function assertNotClosed(where: string, position: string, ref: AnyVar): void {
|
|
|
903
887
|
|
|
904
888
|
/**
|
|
905
889
|
* The classed mint slot one answer column's VALUES flow from: a projected
|
|
906
|
-
* variable's mint slot
|
|
907
|
-
*
|
|
890
|
+
* variable's mint slot. Counts, folds, `pack` and the measure derive
|
|
891
|
+
* numbers/intervals, so they resolve no slot.
|
|
908
892
|
*/
|
|
909
893
|
function findColumnSlotOf(context: ChainContext, column: FindColumn): ClassedField | undefined {
|
|
910
894
|
const entry = column.entry
|
|
911
895
|
if (entry.kind === "var") {
|
|
912
896
|
return mintSlotOf(context, entry.over)
|
|
913
897
|
}
|
|
914
|
-
if (entry.kind === "aggregate" && entry.agg.op === "arg") {
|
|
915
|
-
return mintSlotOf(context, entry.agg.over)
|
|
916
|
-
}
|
|
917
898
|
return undefined
|
|
918
899
|
}
|
|
919
900
|
|
|
@@ -934,9 +915,6 @@ function validateColumn(context: ChainContext, bound: ReadonlySet<AnyVar>, colum
|
|
|
934
915
|
switch (agg.op) {
|
|
935
916
|
case "count":
|
|
936
917
|
return
|
|
937
|
-
case "countDistinct":
|
|
938
|
-
assertBound(where, bound, agg.over)
|
|
939
|
-
return
|
|
940
918
|
case "fold": {
|
|
941
919
|
if ("duration" in agg.over) {
|
|
942
920
|
assertBound(where, bound, agg.over.duration)
|
|
@@ -947,12 +925,6 @@ function validateColumn(context: ChainContext, bound: ReadonlySet<AnyVar>, colum
|
|
|
947
925
|
assertNotClosed(where, `the ${agg.fold} input`, agg.over)
|
|
948
926
|
return
|
|
949
927
|
}
|
|
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
928
|
case "pack":
|
|
957
929
|
assertBound(where, bound, agg.over)
|
|
958
930
|
assertInterval(where, agg.over)
|
|
@@ -972,8 +944,8 @@ function validateCond(context: ChainContext, bound: ReadonlySet<AnyVar>, cond: C
|
|
|
972
944
|
if (side.kind === "var") {
|
|
973
945
|
assertBound(label, bound, side.ref)
|
|
974
946
|
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)
|
|
947
|
+
if (isOrderOp(cond.op.kind) && roster !== undefined) {
|
|
948
|
+
throw closedOrderError(label, `the ${cond.op.kind} side ${side.ref.label}`, roster.name)
|
|
977
949
|
}
|
|
978
950
|
}
|
|
979
951
|
if (side.kind === "measure") {
|
|
@@ -981,14 +953,14 @@ function validateCond(context: ChainContext, bound: ReadonlySet<AnyVar>, cond: C
|
|
|
981
953
|
assertInterval(label, side.ref)
|
|
982
954
|
}
|
|
983
955
|
}
|
|
984
|
-
if ((cond.op === "eq" || cond.op === "ne") && cond.lhs.kind === "var" && cond.rhs.kind === "var") {
|
|
956
|
+
if ((cond.op.kind === "eq" || cond.op.kind === "ne") && cond.lhs.kind === "var" && cond.rhs.kind === "var") {
|
|
985
957
|
assertBound(label, bound, cond.lhs.ref)
|
|
986
958
|
assertBound(label, bound, cond.rhs.ref)
|
|
987
959
|
const lhs = mintSlotOf(context, cond.lhs.ref)
|
|
988
960
|
const rhs = mintSlotOf(context, cond.rhs.ref)
|
|
989
961
|
if (!fieldJoins(lhs, rhs)) {
|
|
990
962
|
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)`
|
|
963
|
+
`${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
964
|
)
|
|
993
965
|
}
|
|
994
966
|
}
|
|
@@ -1000,29 +972,27 @@ function validateCond(context: ChainContext, bound: ReadonlySet<AnyVar>, cond: C
|
|
|
1000
972
|
}
|
|
1001
973
|
|
|
1002
974
|
/**
|
|
1003
|
-
* Validates one
|
|
975
|
+
* Validates one interior item: every head column of the table is bound exactly
|
|
1004
976
|
* once (a missing or extra key is a pointed error) and each variable joins
|
|
1005
|
-
* its head column's classed slot. A POSITIVE
|
|
977
|
+
* its head column's classed slot. A POSITIVE interior atom GROUNDS its
|
|
1006
978
|
* variables (a positive occurrence, exactly the engine's representation),
|
|
1007
979
|
* so no boundness precondition exists; a NEGATED one binds nothing — its
|
|
1008
980
|
* variables must be positively bound elsewhere in the rule, the same
|
|
1009
|
-
* safety rule as EDB negation. When the
|
|
1010
|
-
* (`
|
|
1011
|
-
* head.
|
|
981
|
+
* safety rule as EDB negation. When the table's own first rule is in flight
|
|
982
|
+
* (`finds` empty), the completing rule's OWN find columns ARE the head.
|
|
1012
983
|
*/
|
|
1013
|
-
function
|
|
984
|
+
function validateInterior(
|
|
1014
985
|
context: ChainContext,
|
|
1015
986
|
bound: ReadonlySet<AnyVar>,
|
|
1016
987
|
item: {
|
|
1017
|
-
readonly
|
|
988
|
+
readonly kind: "interior" | "negatedInterior"
|
|
989
|
+
readonly target: DerivedTable
|
|
1018
990
|
readonly bindings: ReadonlyArray<{ readonly key: string; readonly ref: AnyVar }>
|
|
1019
|
-
readonly negated: boolean
|
|
1020
991
|
},
|
|
1021
992
|
columns: readonly FindColumn[]
|
|
1022
993
|
): void {
|
|
1023
994
|
const label = contextLabel(context)
|
|
1024
|
-
const
|
|
1025
|
-
const headColumns = head !== undefined ? head.finds : columns
|
|
995
|
+
const headColumns = item.target.finds.length > 0 ? item.target.finds : columns
|
|
1026
996
|
const headNames = headColumns.map(function nameOf(column) {
|
|
1027
997
|
return column.name
|
|
1028
998
|
})
|
|
@@ -1032,21 +1002,21 @@ function validateIdb(
|
|
|
1032
1002
|
for (const key of keys) {
|
|
1033
1003
|
if (!headNames.includes(key)) {
|
|
1034
1004
|
throw errors.new(
|
|
1035
|
-
`${label}:
|
|
1005
|
+
`${label}: interior ${item.target.name} binds ${key}, not a head column of ${item.target.name} (head columns: ${headNames.join(", ")})`
|
|
1036
1006
|
)
|
|
1037
1007
|
}
|
|
1038
1008
|
}
|
|
1039
1009
|
for (const name of headNames) {
|
|
1040
1010
|
if (!keys.includes(name)) {
|
|
1041
1011
|
throw errors.new(
|
|
1042
|
-
`${label}:
|
|
1012
|
+
`${label}: interior ${item.target.name} omits the head column ${name} — an interior join binds every head column of ${item.target.name}`
|
|
1043
1013
|
)
|
|
1044
1014
|
}
|
|
1045
1015
|
}
|
|
1046
1016
|
for (const binding of item.bindings) {
|
|
1047
|
-
if (item.
|
|
1017
|
+
if (item.kind === "negatedInterior" && !bound.has(binding.ref)) {
|
|
1048
1018
|
throw errors.new(
|
|
1049
|
-
`${label}: negated
|
|
1019
|
+
`${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
1020
|
)
|
|
1051
1021
|
}
|
|
1052
1022
|
const headColumn = headColumns.find(function byName(column) {
|
|
@@ -1058,7 +1028,7 @@ function validateIdb(
|
|
|
1058
1028
|
const mint = mintSlotOf(context, binding.ref)
|
|
1059
1029
|
if (!fieldJoins(headColumn.slot, mint)) {
|
|
1060
1030
|
throw errors.new(
|
|
1061
|
-
`${label}:
|
|
1031
|
+
`${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
1032
|
)
|
|
1063
1033
|
}
|
|
1064
1034
|
}
|
|
@@ -1067,7 +1037,7 @@ function validateIdb(
|
|
|
1067
1037
|
/**
|
|
1068
1038
|
* Completes one rule: enriches the find columns (declaration-order-safe
|
|
1069
1039
|
* keys, boundness validated, each column's classed slot and closed slice
|
|
1070
|
-
* resolved), then walks the body walls — negated-atom boundness safety,
|
|
1040
|
+
* resolved), then walks the body walls — negated-atom boundness safety, interior
|
|
1071
1041
|
* head pairing, and condition validation.
|
|
1072
1042
|
*/
|
|
1073
1043
|
function completeRule(context: ChainContext, state: RuleBuildState, rawColumns: readonly FindColumn[]): RuleData {
|
|
@@ -1091,8 +1061,8 @@ function completeRule(context: ChainContext, state: RuleBuildState, rawColumns:
|
|
|
1091
1061
|
}
|
|
1092
1062
|
}
|
|
1093
1063
|
}
|
|
1094
|
-
if (item.kind === "
|
|
1095
|
-
|
|
1064
|
+
if (item.kind === "interior" || item.kind === "negatedInterior") {
|
|
1065
|
+
validateInterior(context, state.bound, item, columns)
|
|
1096
1066
|
}
|
|
1097
1067
|
if (item.kind === "cond") {
|
|
1098
1068
|
validateCond(context, state.bound, item.cond)
|
|
@@ -1113,100 +1083,119 @@ function makeRuleValue<Row, P extends ParamsRecord>(rule: RuleData): RuleValue<R
|
|
|
1113
1083
|
interface RawChain {
|
|
1114
1084
|
match(relation: MatchOwner, bindings: Readonly<Record<string, unknown>>): RawChain
|
|
1115
1085
|
where(cond: AnyCond): RawChain
|
|
1116
|
-
|
|
1086
|
+
interior(name: string, bindings: Readonly<Record<string, unknown>>): RawChain
|
|
1117
1087
|
find(entries: Readonly<Record<string, unknown>>): RuleValue<never, never>
|
|
1118
1088
|
}
|
|
1119
1089
|
|
|
1120
1090
|
/** The runtime rule-builder shape beneath every typed scope. */
|
|
1121
1091
|
interface RawScope extends TermOps {
|
|
1122
1092
|
match(relation: MatchOwner, bindings: Readonly<Record<string, unknown>>): RawChain
|
|
1123
|
-
|
|
1093
|
+
interior(name: string, bindings: Readonly<Record<string, unknown>>): RawChain
|
|
1124
1094
|
}
|
|
1125
1095
|
|
|
1096
|
+
/** The declared derived tables a chain may name. */
|
|
1097
|
+
type DerivedEnv =
|
|
1098
|
+
| { readonly interiors: readonly InteriorData[] }
|
|
1099
|
+
| { readonly interiors: readonly InteriorData[]; readonly rec: RecHandle | RecHead | RecData }
|
|
1100
|
+
|
|
1126
1101
|
/** 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
|
-
|
|
1102
|
+
type ChainContext = { readonly classes: SchemaClasses; readonly theory: AnySchema } & DerivedEnv &
|
|
1103
|
+
(
|
|
1104
|
+
| { readonly kind: "query" }
|
|
1105
|
+
| { readonly kind: "interior"; readonly self: string }
|
|
1106
|
+
| { readonly kind: "rec-base"; readonly self: RecHandle }
|
|
1107
|
+
| { readonly kind: "rec-arm"; readonly self: RecHead }
|
|
1108
|
+
)
|
|
1132
1109
|
|
|
1133
1110
|
/** The diagnostic label of a chain context. */
|
|
1134
1111
|
function contextLabel(context: ChainContext): string {
|
|
1135
1112
|
switch (context.kind) {
|
|
1136
1113
|
case "query":
|
|
1137
1114
|
return "query rule"
|
|
1138
|
-
case "
|
|
1139
|
-
return `
|
|
1140
|
-
case "
|
|
1141
|
-
return
|
|
1115
|
+
case "interior":
|
|
1116
|
+
return `interior ${context.self} rule`
|
|
1117
|
+
case "rec-base":
|
|
1118
|
+
return `rec ${context.self.name} base`
|
|
1119
|
+
case "rec-arm":
|
|
1120
|
+
return `rec ${context.self.name} rec`
|
|
1142
1121
|
}
|
|
1143
1122
|
}
|
|
1144
1123
|
|
|
1145
|
-
/**
|
|
1146
|
-
function
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
):
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
}
|
|
1155
|
-
if (
|
|
1156
|
-
if (
|
|
1124
|
+
/** RecHandle is name-only staging; RecHead/RecData carry the sealed finds. */
|
|
1125
|
+
function isRecHead(rec: RecHandle | RecHead | RecData): rec is RecHead {
|
|
1126
|
+
return Array.isArray((rec as RecHead).finds)
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
/** Resolves a derived-table name against the context's visible tables. */
|
|
1130
|
+
function lookupDerived(context: ChainContext, name: string): DerivedTable {
|
|
1131
|
+
const interior = context.interiors.find(function byName(candidate) {
|
|
1132
|
+
return candidate.name === name
|
|
1133
|
+
})
|
|
1134
|
+
if (interior !== undefined) {
|
|
1135
|
+
if (context.kind === "interior" && name === context.self) {
|
|
1157
1136
|
throw errors.new(
|
|
1158
|
-
`
|
|
1137
|
+
`interior ${name}: an interior does not read itself — declaration order is topological (a self-read is InteriorNotPrior)`
|
|
1159
1138
|
)
|
|
1160
1139
|
}
|
|
1161
|
-
return
|
|
1140
|
+
return interior
|
|
1162
1141
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1142
|
+
const rec = "rec" in context ? context.rec : undefined
|
|
1143
|
+
if (rec !== undefined && rec.name === name) {
|
|
1144
|
+
if (context.kind === "interior") {
|
|
1145
|
+
throw errors.new(`interior ${context.self}: interiors cannot read the rec — this cut's interiors are a prefix`)
|
|
1146
|
+
}
|
|
1147
|
+
if (context.kind === "rec-base") {
|
|
1148
|
+
throw errors.new(`rec ${rec.name}: a base arm does not read the rec — self-atoms belong on rec arms`)
|
|
1149
|
+
}
|
|
1150
|
+
if (!isRecHead(rec)) {
|
|
1151
|
+
throw errors.new(`rec ${rec.name}: rec arms resolve the rec head after base arms seal it`)
|
|
1152
|
+
}
|
|
1153
|
+
return rec
|
|
1167
1154
|
}
|
|
1168
|
-
|
|
1155
|
+
throw errors.new(`${contextLabel(context)}: no derived table named ${name} is in scope`)
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
/** Validates and records one interior atom per the context's cut. */
|
|
1159
|
+
function interiorAdvance(
|
|
1160
|
+
context: ChainContext,
|
|
1161
|
+
state: RuleBuildState,
|
|
1162
|
+
name: string,
|
|
1163
|
+
bindings: Readonly<Record<string, unknown>>
|
|
1164
|
+
): RuleBuildState {
|
|
1165
|
+
return advanceInterior(state, lookupDerived(context, name), bindings, "interior")
|
|
1169
1166
|
}
|
|
1170
1167
|
|
|
1171
1168
|
/**
|
|
1172
|
-
* Validates and records one NEGATED finished-
|
|
1173
|
-
*
|
|
1174
|
-
*
|
|
1175
|
-
* only negation *through* a cycle, which the rec-context refusal here
|
|
1176
|
-
* makes unwritable).
|
|
1169
|
+
* Validates and records one NEGATED finished-table atom — main and interior
|
|
1170
|
+
* rules: a finished set is a set. Rec bodies refuse every negation
|
|
1171
|
+
* (`NegationInRec` — self is the wall; EDB / earlier-interior is this-cut).
|
|
1177
1172
|
*/
|
|
1178
|
-
function
|
|
1173
|
+
function notInteriorAdvance(
|
|
1179
1174
|
context: ChainContext,
|
|
1180
1175
|
state: RuleBuildState,
|
|
1181
|
-
|
|
1176
|
+
name: string,
|
|
1182
1177
|
bindings: Readonly<Record<string, unknown>>
|
|
1183
1178
|
): 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") {
|
|
1188
|
-
throw errors.new(
|
|
1189
|
-
`rec ${context.self.name}: a recursive rule negates no stratum — self-negation is negation through the cycle (a finished set is what keeps the operator monotone), and a finished stratum's fold belongs in the output rules`
|
|
1190
|
-
)
|
|
1191
|
-
}
|
|
1192
|
-
if (!context.program.recs.includes(target.data)) {
|
|
1179
|
+
if (context.kind === "rec-base" || context.kind === "rec-arm") {
|
|
1193
1180
|
throw errors.new(
|
|
1194
|
-
`
|
|
1181
|
+
`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`
|
|
1195
1182
|
)
|
|
1196
1183
|
}
|
|
1197
|
-
return
|
|
1184
|
+
return advanceInterior(state, lookupDerived(context, name), bindings, "negatedInterior")
|
|
1198
1185
|
}
|
|
1199
1186
|
|
|
1200
|
-
/** Classifies one find record per the context (
|
|
1187
|
+
/** Classifies one find record per the context (interior and rec heads project bound variables only). */
|
|
1201
1188
|
function findColumns(context: ChainContext, entries: Readonly<Record<string, unknown>>): FindColumn[] {
|
|
1202
1189
|
const columns: FindColumn[] = []
|
|
1190
|
+
const derivedHead = context.kind !== "query"
|
|
1203
1191
|
for (const [name, entry] of Object.entries(entries)) {
|
|
1204
1192
|
if (entry === undefined) {
|
|
1205
1193
|
continue
|
|
1206
1194
|
}
|
|
1207
|
-
if (
|
|
1195
|
+
if (derivedHead && !(isTerm(entry) && entry[term] === "var")) {
|
|
1196
|
+
const who = context.kind === "interior" ? `interior ${context.self}` : `rec ${context.self.name}`
|
|
1208
1197
|
throw errors.new(
|
|
1209
|
-
|
|
1198
|
+
`${who}: a rec head projects bound variables only — aggregates and the measure read finished sets (unwritable here)`
|
|
1210
1199
|
)
|
|
1211
1200
|
}
|
|
1212
1201
|
columns.push(findColumnOf(name, entry))
|
|
@@ -1223,8 +1212,8 @@ function makeRawChain(context: ChainContext, state: RuleBuildState): RawChain {
|
|
|
1223
1212
|
where(cond) {
|
|
1224
1213
|
return makeRawChain(context, advanceWhere(context, state, cond))
|
|
1225
1214
|
},
|
|
1226
|
-
|
|
1227
|
-
return makeRawChain(context,
|
|
1215
|
+
interior(name, bindings) {
|
|
1216
|
+
return makeRawChain(context, interiorAdvance(context, state, name, bindings))
|
|
1228
1217
|
},
|
|
1229
1218
|
find(entries) {
|
|
1230
1219
|
return makeRuleValue<never, never>(completeRule(context, state, findColumns(context, entries)))
|
|
@@ -1241,8 +1230,8 @@ function makeRawScope(context: ChainContext): RawScope {
|
|
|
1241
1230
|
match(relation, bindings) {
|
|
1242
1231
|
return makeRawChain(context, advanceMatch(context, EMPTY_RULE, relation, bindings))
|
|
1243
1232
|
},
|
|
1244
|
-
|
|
1245
|
-
return makeRawChain(context,
|
|
1233
|
+
interior(name, bindings) {
|
|
1234
|
+
return makeRawChain(context, interiorAdvance(context, EMPTY_RULE, name, bindings))
|
|
1246
1235
|
}
|
|
1247
1236
|
}
|
|
1248
1237
|
Object.freeze(scope)
|
|
@@ -1264,32 +1253,68 @@ function isTypedScope<S>(scope: RawScope): scope is RawScope & S {
|
|
|
1264
1253
|
|
|
1265
1254
|
/** Builds one query-rule builder (the typed face of the raw builder). */
|
|
1266
1255
|
function makeQueryRuleScope<Rels extends SchemaRelations, Classes extends SchemaClasses>(
|
|
1267
|
-
theory: AnySchema
|
|
1256
|
+
theory: AnySchema,
|
|
1257
|
+
env: DerivedEnv
|
|
1268
1258
|
): QueryRuleScope<Rels, Classes> {
|
|
1269
|
-
const raw = makeRawScope({ kind: "query", classes: theory.classes, theory })
|
|
1259
|
+
const raw = makeRawScope({ kind: "query", classes: theory.classes, theory, ...env })
|
|
1270
1260
|
if (!isTypedScope<QueryRuleScope<Rels, Classes>>(raw)) {
|
|
1271
1261
|
throw errors.new("query rule builder construction incomplete")
|
|
1272
1262
|
}
|
|
1273
1263
|
return raw
|
|
1274
1264
|
}
|
|
1275
1265
|
|
|
1276
|
-
/** Builds one
|
|
1277
|
-
function
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1266
|
+
/** Builds one interior-rule builder. */
|
|
1267
|
+
function makeInteriorRuleScope<Rels extends SchemaRelations, Classes extends SchemaClasses>(
|
|
1268
|
+
theory: AnySchema,
|
|
1269
|
+
env: DerivedEnv,
|
|
1270
|
+
self: string
|
|
1271
|
+
): InteriorRuleScope<Rels, Classes> {
|
|
1272
|
+
const raw = makeRawScope({ kind: "interior", self, classes: theory.classes, theory, ...env })
|
|
1273
|
+
if (!isTypedScope<InteriorRuleScope<Rels, Classes>>(raw)) {
|
|
1274
|
+
throw errors.new("interior rule builder construction incomplete")
|
|
1283
1275
|
}
|
|
1284
1276
|
return raw
|
|
1285
1277
|
}
|
|
1286
1278
|
|
|
1287
|
-
/**
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1279
|
+
/** Builds one rec-arm builder. */
|
|
1280
|
+
function makeRecRuleScope<Rels extends SchemaRelations, Classes extends SchemaClasses>(
|
|
1281
|
+
theory: AnySchema,
|
|
1282
|
+
env: DerivedEnv,
|
|
1283
|
+
self: RecHandle,
|
|
1284
|
+
kind: "rec-base"
|
|
1285
|
+
): RecRuleScope<Rels, Classes>
|
|
1286
|
+
function makeRecRuleScope<Rels extends SchemaRelations, Classes extends SchemaClasses>(
|
|
1287
|
+
theory: AnySchema,
|
|
1288
|
+
env: DerivedEnv,
|
|
1289
|
+
self: RecHead,
|
|
1290
|
+
kind: "rec-arm"
|
|
1291
|
+
): RecRuleScope<Rels, Classes>
|
|
1292
|
+
function makeRecRuleScope<Rels extends SchemaRelations, Classes extends SchemaClasses>(
|
|
1293
|
+
theory: AnySchema,
|
|
1294
|
+
env: DerivedEnv,
|
|
1295
|
+
self: RecHandle | RecHead,
|
|
1296
|
+
kind: "rec-base" | "rec-arm"
|
|
1297
|
+
): RecRuleScope<Rels, Classes> {
|
|
1298
|
+
const raw =
|
|
1299
|
+
kind === "rec-base"
|
|
1300
|
+
? makeRawScope({
|
|
1301
|
+
kind: "rec-base",
|
|
1302
|
+
self: self as RecHandle,
|
|
1303
|
+
classes: theory.classes,
|
|
1304
|
+
theory,
|
|
1305
|
+
...env
|
|
1306
|
+
})
|
|
1307
|
+
: makeRawScope({
|
|
1308
|
+
kind: "rec-arm",
|
|
1309
|
+
self: self as RecHead,
|
|
1310
|
+
classes: theory.classes,
|
|
1311
|
+
theory,
|
|
1312
|
+
...env
|
|
1313
|
+
})
|
|
1314
|
+
if (!isTypedScope<RecRuleScope<Rels, Classes>>(raw)) {
|
|
1315
|
+
throw errors.new("rec rule builder construction incomplete")
|
|
1316
|
+
}
|
|
1317
|
+
return raw
|
|
1293
1318
|
}
|
|
1294
1319
|
|
|
1295
1320
|
/** Renders one head column's closed slice for the rule-alignment check's diagnostics. */
|
|
@@ -1307,9 +1332,6 @@ function headSignature(column: FindColumn): string {
|
|
|
1307
1332
|
if (agg.op === "fold") {
|
|
1308
1333
|
return `${column.name}:${agg.fold}`
|
|
1309
1334
|
}
|
|
1310
|
-
if (agg.op === "arg") {
|
|
1311
|
-
return `${column.name}:${agg.direction}`
|
|
1312
|
-
}
|
|
1313
1335
|
return `${column.name}:${agg.op}`
|
|
1314
1336
|
}
|
|
1315
1337
|
|
|
@@ -1324,17 +1346,22 @@ function renderParamAnchor(roster: ClosedRoster | undefined): string {
|
|
|
1324
1346
|
}
|
|
1325
1347
|
|
|
1326
1348
|
/**
|
|
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
|
|
1331
|
-
* ban needs no registry arm: an order use always
|
|
1332
|
-
* domain (the no-variable-side spelling is refused
|
|
1333
|
-
* constructor), so a closed-anchored param under an
|
|
1334
|
-
* one-domain wall here — and an order use whose
|
|
1335
|
-
* closed-bound dies at the comparison's own var-side
|
|
1349
|
+
* Folds every rule's param uses (interiors in declaration order, then rec
|
|
1350
|
+
* base, then rec arms, then main — exactly the lowering walk) into the
|
|
1351
|
+
* query's registry: first use mints the dense `ParamId`, the first
|
|
1352
|
+
* FIELD-ANCHORED use types the wire, and one name keeps one shape AND one
|
|
1353
|
+
* closedness. The orderable ban needs no registry arm: an order use always
|
|
1354
|
+
* anchors its SIBLING's domain (the no-variable-side spelling is refused
|
|
1355
|
+
* at the comparison constructor), so a closed-anchored param under an
|
|
1356
|
+
* order op dies at the one-domain wall here — and an order use whose
|
|
1357
|
+
* sibling is itself closed-bound dies at the comparison's own var-side
|
|
1358
|
+
* wall first.
|
|
1336
1359
|
*/
|
|
1337
|
-
function paramRegistryOf(
|
|
1360
|
+
function paramRegistryOf(
|
|
1361
|
+
interiors: readonly InteriorData[],
|
|
1362
|
+
rec: RecData | undefined,
|
|
1363
|
+
rules: readonly RuleData[]
|
|
1364
|
+
): readonly ParamEntry[] {
|
|
1338
1365
|
const order: string[] = []
|
|
1339
1366
|
const byName = new Map<
|
|
1340
1367
|
string,
|
|
@@ -1383,8 +1410,16 @@ function paramRegistryOf(recs: readonly RecData[], rules: readonly RuleData[]):
|
|
|
1383
1410
|
}
|
|
1384
1411
|
}
|
|
1385
1412
|
}
|
|
1386
|
-
for (const
|
|
1387
|
-
for (const rule of
|
|
1413
|
+
for (const interior of interiors) {
|
|
1414
|
+
for (const rule of interior.rules) {
|
|
1415
|
+
fold(rule.paramUses)
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
if (rec !== undefined) {
|
|
1419
|
+
for (const rule of rec.base) {
|
|
1420
|
+
fold(rule.paramUses)
|
|
1421
|
+
}
|
|
1422
|
+
for (const rule of rec.rec) {
|
|
1388
1423
|
fold(rule.paramUses)
|
|
1389
1424
|
}
|
|
1390
1425
|
}
|
|
@@ -1430,114 +1465,272 @@ interface RawQuery {
|
|
|
1430
1465
|
readonly schema: AnySchema
|
|
1431
1466
|
readonly data: QueryData
|
|
1432
1467
|
rule(build: (r: RawScope) => RuleValue<never, never>): RawQuery
|
|
1468
|
+
interior(name: string, ...builds: never[]): never
|
|
1469
|
+
reach(name: string, arms: never): never
|
|
1433
1470
|
}
|
|
1434
1471
|
|
|
1435
|
-
/**
|
|
1436
|
-
|
|
1437
|
-
* derive the SAME head (name and aggregate shape, position for position —
|
|
1438
|
-
* the decode labels and the engine's alignment rule agree), and the param
|
|
1439
|
-
* registry folds in program-walk order.
|
|
1440
|
-
*/
|
|
1441
|
-
function makeRawQuery(theory: AnySchema, recs: readonly RecData[], rules: readonly RuleData[]): RawQuery {
|
|
1472
|
+
/** Asserts every rule in a list derives the same head (name, aggregate shape, closed slice, class). */
|
|
1473
|
+
function assertAlignedHeads(label: string, rules: readonly RuleData[]): void {
|
|
1442
1474
|
const first = rules[0]
|
|
1443
1475
|
if (first === undefined) {
|
|
1444
|
-
throw errors.new(
|
|
1476
|
+
throw errors.new(`${label} needs at least one rule`)
|
|
1445
1477
|
}
|
|
1446
1478
|
const signature = first.finds.map(headSignature).join(", ")
|
|
1447
1479
|
rules.forEach(function verifyHead(rule, index) {
|
|
1448
1480
|
const candidate = rule.finds.map(headSignature).join(", ")
|
|
1449
1481
|
if (candidate !== signature) {
|
|
1450
1482
|
throw errors.new(
|
|
1451
|
-
`every rule of
|
|
1483
|
+
`every rule of ${label} derives the same head — rule 0 finds (${signature}), rule ${index} finds (${candidate})`
|
|
1452
1484
|
)
|
|
1453
1485
|
}
|
|
1454
|
-
// The closed slice is part of the head too: one answer column decodes
|
|
1455
|
-
// through one roster, so a union whose rules bind a column at different
|
|
1456
|
-
// vocabularies (or one closed, one bare) is refused pointed.
|
|
1457
1486
|
rule.finds.forEach(function verifyClosedSlice(column, position) {
|
|
1458
1487
|
const lead = first.finds[position]
|
|
1459
1488
|
if (lead !== undefined && column.closed !== lead.closed) {
|
|
1460
1489
|
throw errors.new(
|
|
1461
|
-
`every rule of
|
|
1490
|
+
`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)`
|
|
1462
1491
|
)
|
|
1463
1492
|
}
|
|
1464
|
-
// The law-class wall on the union head: one answer column is one
|
|
1465
|
-
// value space, so the classed mint slot each rule binds the column
|
|
1466
|
-
// at must join across rules — the SAME fieldJoins judgment every
|
|
1467
|
-
// join/eq/negated-atom position enforces (the SDK holds it because
|
|
1468
|
-
// the wire IR carries no domains).
|
|
1469
1493
|
if (lead === undefined) {
|
|
1470
1494
|
return
|
|
1471
1495
|
}
|
|
1472
1496
|
if (lead.slot !== undefined && column.slot !== undefined && !fieldJoins(lead.slot, column.slot)) {
|
|
1473
1497
|
throw errors.new(
|
|
1474
|
-
`every rule of
|
|
1498
|
+
`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)`
|
|
1475
1499
|
)
|
|
1476
1500
|
}
|
|
1477
1501
|
})
|
|
1478
1502
|
})
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
function afterMainError(what: string): Error {
|
|
1506
|
+
return errors.new(
|
|
1507
|
+
`query: ${what} after a main rule is unwritable — declaration order is interiors, then rec, then main`
|
|
1508
|
+
)
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
/**
|
|
1512
|
+
* Assembles the runtime query value over completed rules: every rule must
|
|
1513
|
+
* derive the SAME head (name and aggregate shape, position for position —
|
|
1514
|
+
* the decode labels and the engine's alignment rule agree), and the param
|
|
1515
|
+
* registry folds in query-walk order. CQ lowering does not mention rec;
|
|
1516
|
+
* Reach carries RecData by value.
|
|
1517
|
+
*/
|
|
1518
|
+
function makeRawQuery(
|
|
1519
|
+
theory: AnySchema,
|
|
1520
|
+
interiors: readonly InteriorData[],
|
|
1521
|
+
rec: RecData | undefined,
|
|
1522
|
+
rules: readonly RuleData[]
|
|
1523
|
+
): RawQuery {
|
|
1524
|
+
assertAlignedHeads("a query", rules)
|
|
1525
|
+
const first = rules[0]
|
|
1526
|
+
if (first === undefined) {
|
|
1527
|
+
throw errors.new("a query needs at least one rule")
|
|
1528
|
+
}
|
|
1529
|
+
const env: DerivedEnv = rec === undefined ? { interiors } : { interiors, rec }
|
|
1530
|
+
const frozenInteriors = Object.freeze([...interiors])
|
|
1531
|
+
const frozenRules = Object.freeze([...rules])
|
|
1532
|
+
const params = paramRegistryOf(interiors, rec, rules)
|
|
1533
|
+
const data: QueryData =
|
|
1534
|
+
rec === undefined
|
|
1535
|
+
? Object.freeze({
|
|
1536
|
+
kind: "cq" as const,
|
|
1537
|
+
interiors: frozenInteriors,
|
|
1538
|
+
rules: frozenRules,
|
|
1539
|
+
finds: first.finds,
|
|
1540
|
+
params
|
|
1541
|
+
})
|
|
1542
|
+
: Object.freeze({
|
|
1543
|
+
kind: "reach" as const,
|
|
1544
|
+
interiors: frozenInteriors,
|
|
1545
|
+
rec,
|
|
1546
|
+
rules: frozenRules,
|
|
1547
|
+
finds: first.finds,
|
|
1548
|
+
params
|
|
1549
|
+
})
|
|
1485
1550
|
const value: RawQuery = {
|
|
1486
1551
|
schema: theory,
|
|
1487
1552
|
data,
|
|
1488
1553
|
rule(build) {
|
|
1489
|
-
const built = build(makeRawScope({ kind: "query", classes: theory.classes, theory }))
|
|
1490
|
-
return makeRawQuery(theory,
|
|
1554
|
+
const built = build(makeRawScope({ kind: "query", classes: theory.classes, theory, ...env }))
|
|
1555
|
+
return makeRawQuery(theory, interiors, rec, [...rules, built.rule])
|
|
1556
|
+
},
|
|
1557
|
+
interior() {
|
|
1558
|
+
throw afterMainError("interior")
|
|
1559
|
+
},
|
|
1560
|
+
reach() {
|
|
1561
|
+
throw afterMainError("reach")
|
|
1491
1562
|
}
|
|
1492
1563
|
}
|
|
1493
1564
|
Object.freeze(value)
|
|
1494
1565
|
return value
|
|
1495
1566
|
}
|
|
1496
1567
|
|
|
1497
|
-
/**
|
|
1498
|
-
* The query values' trusted admission seam (the {@link isTypedScope} pattern):
|
|
1499
|
-
* the checkable fact — the value was assembled over the identical theory —
|
|
1500
|
-
* is verified before the raw value is admitted at its typed face.
|
|
1501
|
-
*/
|
|
1502
|
-
function isQueryValue<Rels extends SchemaRelations, Row, P extends ParamsRecord, Classes extends SchemaClasses>(
|
|
1503
|
-
theory: Schema<Rels, Classes>,
|
|
1504
|
-
value: RawQuery
|
|
1505
|
-
): value is RawQuery & Query<Rels, Row, P, Classes> {
|
|
1506
|
-
return value.schema === theory
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
|
-
/** Assembles one typed query value (rules already completed). */
|
|
1510
1568
|
function makeQuery<Rels extends SchemaRelations, Row, P extends ParamsRecord, Classes extends SchemaClasses>(
|
|
1511
1569
|
theory: Schema<Rels, Classes>,
|
|
1512
|
-
|
|
1570
|
+
interiors: readonly InteriorData[],
|
|
1571
|
+
rec: RecData | undefined,
|
|
1513
1572
|
rules: readonly RuleData[]
|
|
1514
1573
|
): Query<Rels, Row, P, Classes> {
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1574
|
+
return makeRawQuery(theory, interiors, rec, rules) as unknown as Query<Rels, Row, P, Classes>
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
/** Collects one Interior from its builders. */
|
|
1578
|
+
function collectInterior<Rels extends SchemaRelations, Classes extends SchemaClasses>(
|
|
1579
|
+
theory: Schema<Rels, Classes>,
|
|
1580
|
+
env: DerivedEnv,
|
|
1581
|
+
name: string,
|
|
1582
|
+
builds: readonly InteriorBuild<Rels, Classes>[]
|
|
1583
|
+
): InteriorData {
|
|
1584
|
+
if (builds.length === 0) {
|
|
1585
|
+
throw errors.new(`query: interior ${name} needs at least one rule`)
|
|
1518
1586
|
}
|
|
1519
|
-
|
|
1587
|
+
const rules = builds.map(function buildRule(buildOne) {
|
|
1588
|
+
return buildOne(makeInteriorRuleScope<Rels, Classes>(theory, env, name)).rule
|
|
1589
|
+
})
|
|
1590
|
+
assertAlignedHeads(`interior ${name}`, rules)
|
|
1591
|
+
const first = rules[0]
|
|
1592
|
+
if (first === undefined) {
|
|
1593
|
+
throw errors.new(`query: interior ${name} needs at least one rule`)
|
|
1594
|
+
}
|
|
1595
|
+
return Object.freeze({ name, finds: first.finds, rules: Object.freeze(rules) })
|
|
1520
1596
|
}
|
|
1521
1597
|
|
|
1522
|
-
/**
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1598
|
+
/** Collects the Rec from tagged base/rec builder arrays. */
|
|
1599
|
+
function collectRec<Rels extends SchemaRelations, Classes extends SchemaClasses>(
|
|
1600
|
+
theory: Schema<Rels, Classes>,
|
|
1601
|
+
interiors: readonly InteriorData[],
|
|
1602
|
+
name: string,
|
|
1603
|
+
baseBuilds: readonly RecBuild<Rels, Classes>[],
|
|
1604
|
+
recBuilds: readonly RecBuild<Rels, Classes>[]
|
|
1605
|
+
): RecData {
|
|
1606
|
+
if (baseBuilds.length === 0) {
|
|
1607
|
+
throw errors.new(`query: rec ${name} has no base arms`)
|
|
1608
|
+
}
|
|
1609
|
+
if (recBuilds.length === 0) {
|
|
1610
|
+
throw errors.new(`query: rec ${name} has no rec arms`)
|
|
1611
|
+
}
|
|
1612
|
+
const handle: RecHandle = Object.freeze({ name })
|
|
1613
|
+
const baseEnv: DerivedEnv = { interiors, rec: handle }
|
|
1614
|
+
const base = baseBuilds.map(function buildBase(buildOne) {
|
|
1615
|
+
return buildOne(makeRecRuleScope<Rels, Classes>(theory, baseEnv, handle, "rec-base")).rule
|
|
1616
|
+
})
|
|
1617
|
+
assertAlignedHeads(`rec ${name}`, base)
|
|
1618
|
+
const first = base[0]
|
|
1619
|
+
if (first === undefined) {
|
|
1620
|
+
throw errors.new(`query: rec ${name} has no base arms`)
|
|
1621
|
+
}
|
|
1622
|
+
const firstFind = first.finds[0]
|
|
1623
|
+
if (firstFind === undefined) {
|
|
1624
|
+
throw errors.new(`query: rec ${name} has no head`)
|
|
1625
|
+
}
|
|
1626
|
+
const finds: RecHead["finds"] = [firstFind, ...first.finds.slice(1)]
|
|
1627
|
+
const head: RecHead = Object.freeze({ name, finds })
|
|
1628
|
+
const recEnv: DerivedEnv = { interiors, rec: head }
|
|
1629
|
+
const rec = recBuilds.map(function buildRec(buildOne) {
|
|
1630
|
+
return buildOne(makeRecRuleScope<Rels, Classes>(theory, recEnv, head, "rec-arm")).rule
|
|
1631
|
+
})
|
|
1632
|
+
assertAlignedHeads(`rec ${name}`, [...base, ...rec])
|
|
1633
|
+
const firstRec = rec[0]
|
|
1634
|
+
if (firstRec === undefined) {
|
|
1635
|
+
throw errors.new(`query: rec ${name} has no rec arms`)
|
|
1636
|
+
}
|
|
1637
|
+
const sealedBase: RecData["base"] = [first, ...base.slice(1)]
|
|
1638
|
+
const sealedRec: RecData["rec"] = [firstRec, ...rec.slice(1)]
|
|
1639
|
+
const recData: RecData = Object.freeze({
|
|
1640
|
+
name,
|
|
1641
|
+
finds,
|
|
1642
|
+
base: sealedBase,
|
|
1643
|
+
rec: sealedRec
|
|
1644
|
+
})
|
|
1645
|
+
return recData
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
/** Builds the CQ query start (interiors, then reach or the first main rule). */
|
|
1649
|
+
function makeQueryStart<Rels extends SchemaRelations, Classes extends SchemaClasses, P extends ParamsRecord>(
|
|
1650
|
+
theory: Schema<Rels, Classes>,
|
|
1651
|
+
interiors: readonly InteriorData[]
|
|
1652
|
+
): QueryStart<Rels, Classes, P> {
|
|
1653
|
+
const env: DerivedEnv = { interiors }
|
|
1654
|
+
const start = {
|
|
1655
|
+
interior<const Builds extends readonly InteriorBuild<Rels, Classes>[]>(
|
|
1656
|
+
name: string,
|
|
1657
|
+
...builds: Builds
|
|
1658
|
+
): QueryStart<Rels, Classes, Flatten<P & BuildsParams<Builds>>> {
|
|
1659
|
+
if (
|
|
1660
|
+
interiors.some(function sameName(interior) {
|
|
1661
|
+
return interior.name === name
|
|
1662
|
+
})
|
|
1663
|
+
) {
|
|
1664
|
+
throw errors.new(`query: interior ${name} is already declared — names are unique`)
|
|
1665
|
+
}
|
|
1666
|
+
if (name.length === 0) {
|
|
1667
|
+
throw errors.new("query: an interior needs a name")
|
|
1668
|
+
}
|
|
1669
|
+
const data = collectInterior(theory, env, name, builds)
|
|
1670
|
+
return makeQueryStart<Rels, Classes, Flatten<P & BuildsParams<Builds>>>(theory, [...interiors, data])
|
|
1671
|
+
},
|
|
1672
|
+
reach<const Base extends readonly RecBuild<Rels, Classes>[], const Step extends readonly RecBuild<Rels, Classes>[]>(
|
|
1673
|
+
name: string,
|
|
1674
|
+
arms: { readonly base: Base; readonly rec: Step }
|
|
1675
|
+
): QueryReachStart<Rels, Classes, Flatten<P & BuildsParams<Base> & BuildsParams<Step>>> {
|
|
1676
|
+
if (
|
|
1677
|
+
interiors.some(function sameName(interior) {
|
|
1678
|
+
return interior.name === name
|
|
1679
|
+
})
|
|
1680
|
+
) {
|
|
1681
|
+
throw errors.new(`query: interior and rec share the name ${name}`)
|
|
1682
|
+
}
|
|
1683
|
+
if (name.length === 0) {
|
|
1684
|
+
throw errors.new("query: reach needs a name")
|
|
1685
|
+
}
|
|
1686
|
+
const data = collectRec(theory, interiors, name, arms.base, arms.rec)
|
|
1687
|
+
return makeQueryReachStart<Rels, Classes, Flatten<P & BuildsParams<Base> & BuildsParams<Step>>>(
|
|
1688
|
+
theory,
|
|
1689
|
+
interiors,
|
|
1690
|
+
data
|
|
1691
|
+
)
|
|
1692
|
+
},
|
|
1693
|
+
rule<RV extends AnyRuleValue>(
|
|
1694
|
+
build: (r: QueryRuleScope<Rels, Classes>) => RV
|
|
1695
|
+
): Query<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes> {
|
|
1696
|
+
const built = build(makeQueryRuleScope<Rels, Classes>(theory, env))
|
|
1697
|
+
return makeQuery<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes>(theory, interiors, undefined, [built.rule])
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
Object.freeze(start)
|
|
1701
|
+
return start as unknown as QueryStart<Rels, Classes, P>
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
/** Builds the Reach query start (rec sealed; only the first main rule remains). */
|
|
1705
|
+
function makeQueryReachStart<Rels extends SchemaRelations, Classes extends SchemaClasses, P extends ParamsRecord>(
|
|
1706
|
+
theory: Schema<Rels, Classes>,
|
|
1707
|
+
interiors: readonly InteriorData[],
|
|
1708
|
+
rec: RecData
|
|
1709
|
+
): QueryReachStart<Rels, Classes, P> {
|
|
1710
|
+
const env: DerivedEnv = { interiors, rec }
|
|
1711
|
+
const start = {
|
|
1532
1712
|
rule<RV extends AnyRuleValue>(
|
|
1533
1713
|
build: (r: QueryRuleScope<Rels, Classes>) => RV
|
|
1534
|
-
): Query<Rels, RowOf<RV>, ParamsOf<RV
|
|
1535
|
-
const built = build(makeQueryRuleScope<Rels, Classes>(theory))
|
|
1536
|
-
return makeQuery<Rels, RowOf<RV>, ParamsOf<RV
|
|
1714
|
+
): Query<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes> {
|
|
1715
|
+
const built = build(makeQueryRuleScope<Rels, Classes>(theory, env))
|
|
1716
|
+
return makeQuery<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes>(theory, interiors, rec, [built.rule])
|
|
1537
1717
|
}
|
|
1538
1718
|
}
|
|
1539
1719
|
Object.freeze(start)
|
|
1540
|
-
return start
|
|
1720
|
+
return start as unknown as QueryReachStart<Rels, Classes, P>
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
/**
|
|
1724
|
+
* Opens a query over a schema: `query(S).rule(r => ...)`, optionally with
|
|
1725
|
+
* `interior` / `reach` first. Each `.rule` adds one conjunctive rule;
|
|
1726
|
+
* multiple rules are the set union. The schema's law-computed class map and
|
|
1727
|
+
* theory value ride into every rule builder — the join walls compare
|
|
1728
|
+
* against the mint slots off it.
|
|
1729
|
+
*/
|
|
1730
|
+
function query<Rels extends SchemaRelations, Classes extends SchemaClasses>(
|
|
1731
|
+
theory: Schema<Rels, Classes>
|
|
1732
|
+
): QueryStart<Rels, Classes> {
|
|
1733
|
+
return makeQueryStart<Rels, Classes, Record<never, never>>(theory, [])
|
|
1541
1734
|
}
|
|
1542
1735
|
|
|
1543
1736
|
/**
|
|
@@ -1670,7 +1863,7 @@ function taggedCmpLiteral(
|
|
|
1670
1863
|
interface LowerContext {
|
|
1671
1864
|
readonly theory: AnySchema
|
|
1672
1865
|
readonly relationIds: ReadonlyMap<string, number>
|
|
1673
|
-
readonly
|
|
1866
|
+
readonly interiorIds: ReadonlyMap<string, number>
|
|
1674
1867
|
readonly paramIds: ReadonlyMap<string, number>
|
|
1675
1868
|
readonly params: ReadonlyMap<string, ParamEntry>
|
|
1676
1869
|
}
|
|
@@ -1751,35 +1944,34 @@ function lowerBindingTerm(ctx: LowerContext, context: string, binding: BindingEn
|
|
|
1751
1944
|
}
|
|
1752
1945
|
|
|
1753
1946
|
/**
|
|
1754
|
-
* Lowers one
|
|
1755
|
-
* head position i. Every head column of the
|
|
1947
|
+
* Lowers one interior atom: named bindings placed by HEAD order, `FieldId(i)` =
|
|
1948
|
+
* head position i. Every head column of the table must be bound (a missing key
|
|
1756
1949
|
* is refused pointed); the var-id assignment order is head order, so the
|
|
1757
1950
|
* first-use numbering matches the name-keyed edition exactly.
|
|
1758
1951
|
*/
|
|
1759
|
-
function
|
|
1952
|
+
function lowerInteriorAtom(
|
|
1760
1953
|
ctx: LowerContext,
|
|
1761
|
-
|
|
1954
|
+
target: DerivedTable,
|
|
1762
1955
|
bindings: ReadonlyArray<{ readonly key: string; readonly ref: AnyVar }>,
|
|
1763
1956
|
ids: VarIds
|
|
1764
1957
|
): AtomIr {
|
|
1765
|
-
const
|
|
1766
|
-
if (
|
|
1767
|
-
throw errors.new(`query lowering:
|
|
1958
|
+
const interior = ctx.interiorIds.get(target.name)
|
|
1959
|
+
if (interior === undefined) {
|
|
1960
|
+
throw errors.new(`query lowering: derived table ${target.name} was not declared on this query`)
|
|
1768
1961
|
}
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
throw errors.new(`query lowering: rec ${rec.name} has no rules`)
|
|
1962
|
+
if (target.finds.length === 0) {
|
|
1963
|
+
throw errors.new(`query lowering: derived table ${target.name} has no head`)
|
|
1772
1964
|
}
|
|
1773
|
-
const irBindings: Array<readonly [number, TermIr]> =
|
|
1965
|
+
const irBindings: Array<readonly [number, TermIr]> = target.finds.map(function lowerPosition(column, position) {
|
|
1774
1966
|
const binding = bindings.find(function byKey(candidate) {
|
|
1775
1967
|
return candidate.key === column.name
|
|
1776
1968
|
})
|
|
1777
1969
|
if (binding === undefined) {
|
|
1778
|
-
throw errors.new(`query lowering:
|
|
1970
|
+
throw errors.new(`query lowering: interior ${target.name} omits head column ${column.name}`)
|
|
1779
1971
|
}
|
|
1780
1972
|
return [position, { kind: "var", var: ids.of(binding.ref) } as const] as const
|
|
1781
1973
|
})
|
|
1782
|
-
return { source: { kind: "
|
|
1974
|
+
return { source: { kind: "interior", interior }, bindings: irBindings }
|
|
1783
1975
|
}
|
|
1784
1976
|
|
|
1785
1977
|
/** Lowers one comparison side; literals tag by the sibling's anchor (op-aware at `pointIn`). */
|
|
@@ -1821,25 +2013,17 @@ function cmpAnchorOf(ctx: LowerContext, sibling: CmpTermData): AnyField | "measu
|
|
|
1821
2013
|
|
|
1822
2014
|
/** Lowers one comparison. */
|
|
1823
2015
|
function lowerComparison(ctx: LowerContext, cmp: CmpData, ids: VarIds): ComparisonIr {
|
|
1824
|
-
if (cmp.op === "allen") {
|
|
1825
|
-
const maskData = cmp.mask
|
|
1826
|
-
if (maskData === undefined) {
|
|
1827
|
-
throw errors.new("query lowering: an allen comparison lost its mask")
|
|
1828
|
-
}
|
|
1829
|
-
const mask =
|
|
1830
|
-
maskData.kind === "literal"
|
|
1831
|
-
? { kind: "literal" as const, mask: maskData.mask }
|
|
1832
|
-
: { kind: "param" as const, param: paramIdOf(ctx, maskData.name) }
|
|
2016
|
+
if (cmp.op.kind === "allen") {
|
|
1833
2017
|
return {
|
|
1834
|
-
op: { kind: "allen", mask },
|
|
2018
|
+
op: { kind: "allen", mask: cmp.op.mask },
|
|
1835
2019
|
lhs: lowerCmpTerm(ctx, cmp.lhs, cmp.rhs, ids, "allen"),
|
|
1836
2020
|
rhs: lowerCmpTerm(ctx, cmp.rhs, cmp.lhs, ids, "allen")
|
|
1837
2021
|
}
|
|
1838
2022
|
}
|
|
1839
2023
|
return {
|
|
1840
|
-
op: { kind: cmp.op },
|
|
1841
|
-
lhs: lowerCmpTerm(ctx, cmp.lhs, cmp.rhs, ids, cmp.op),
|
|
1842
|
-
rhs: lowerCmpTerm(ctx, cmp.rhs, cmp.lhs, ids, cmp.op)
|
|
2024
|
+
op: { kind: cmp.op.kind },
|
|
2025
|
+
lhs: lowerCmpTerm(ctx, cmp.lhs, cmp.rhs, ids, cmp.op.kind),
|
|
2026
|
+
rhs: lowerCmpTerm(ctx, cmp.rhs, cmp.lhs, ids, cmp.op.kind)
|
|
1843
2027
|
}
|
|
1844
2028
|
}
|
|
1845
2029
|
|
|
@@ -1868,16 +2052,12 @@ function lowerFind(entry: FindEntryData, ids: VarIds): FindTermIr {
|
|
|
1868
2052
|
switch (agg.op) {
|
|
1869
2053
|
case "count":
|
|
1870
2054
|
return { kind: "aggregate", op: { kind: "count" } }
|
|
1871
|
-
case "countDistinct":
|
|
1872
|
-
return { kind: "aggregate", op: { kind: "countDistinct" }, over: ids.of(agg.over) }
|
|
1873
2055
|
case "fold": {
|
|
1874
2056
|
if ("duration" in agg.over) {
|
|
1875
2057
|
return { kind: "aggregateMeasure", op: { kind: agg.fold }, over: ids.of(agg.over.duration) }
|
|
1876
2058
|
}
|
|
1877
2059
|
return { kind: "aggregate", op: { kind: agg.fold }, over: ids.of(agg.over) }
|
|
1878
2060
|
}
|
|
1879
|
-
case "arg":
|
|
1880
|
-
return { kind: "aggregate", op: { kind: agg.direction, key: ids.of(agg.key) }, over: ids.of(agg.over) }
|
|
1881
2061
|
case "pack":
|
|
1882
2062
|
return { kind: "aggregate", op: { kind: "pack" }, over: ids.of(agg.over) }
|
|
1883
2063
|
}
|
|
@@ -1888,12 +2068,8 @@ function headOpOf(agg: AggData): HeadOpIr {
|
|
|
1888
2068
|
switch (agg.op) {
|
|
1889
2069
|
case "count":
|
|
1890
2070
|
return "count"
|
|
1891
|
-
case "countDistinct":
|
|
1892
|
-
return "countDistinct"
|
|
1893
2071
|
case "fold":
|
|
1894
2072
|
return agg.fold
|
|
1895
|
-
case "arg":
|
|
1896
|
-
return agg.direction
|
|
1897
2073
|
case "pack":
|
|
1898
2074
|
return "pack"
|
|
1899
2075
|
}
|
|
@@ -1924,9 +2100,12 @@ function lowerRule(ctx: LowerContext, rule: RuleData): RuleIr {
|
|
|
1924
2100
|
negated.push(lowerAtom(ctx, item.atom, ids))
|
|
1925
2101
|
break
|
|
1926
2102
|
}
|
|
1927
|
-
case "
|
|
1928
|
-
|
|
1929
|
-
|
|
2103
|
+
case "interior": {
|
|
2104
|
+
atoms.push(lowerInteriorAtom(ctx, item.target, item.bindings, ids))
|
|
2105
|
+
break
|
|
2106
|
+
}
|
|
2107
|
+
case "negatedInterior": {
|
|
2108
|
+
negated.push(lowerInteriorAtom(ctx, item.target, item.bindings, ids))
|
|
1930
2109
|
break
|
|
1931
2110
|
}
|
|
1932
2111
|
case "cond": {
|
|
@@ -1946,24 +2125,27 @@ function lowerRule(ctx: LowerContext, rule: RuleData): RuleIr {
|
|
|
1946
2125
|
}
|
|
1947
2126
|
|
|
1948
2127
|
/**
|
|
1949
|
-
* Lowers a query value to the bridge's `
|
|
1950
|
-
*
|
|
1951
|
-
*
|
|
2128
|
+
* Lowers a query value to the bridge's `QueryIr` — pure and stable:
|
|
2129
|
+
* interiors in declaration order, then CQ or Reach, then main. Every
|
|
2130
|
+
* registered param must carry a field anchor by now.
|
|
1952
2131
|
*/
|
|
1953
|
-
function lowerQuery(q: AnyQuery):
|
|
2132
|
+
function lowerQuery(q: AnyQuery): ParsedQuery {
|
|
1954
2133
|
const theory = q.schema
|
|
1955
2134
|
const relationIds = new Map<string, number>()
|
|
1956
2135
|
Object.keys(theory.relations).forEach(function assignOrdinal(name, index) {
|
|
1957
2136
|
relationIds.set(name, index)
|
|
1958
2137
|
})
|
|
1959
|
-
const
|
|
1960
|
-
q.data.
|
|
1961
|
-
|
|
2138
|
+
const interiorIds = new Map<string, number>()
|
|
2139
|
+
q.data.interiors.forEach(function assignInteriorId(interior, index) {
|
|
2140
|
+
interiorIds.set(interior.name, index)
|
|
1962
2141
|
})
|
|
2142
|
+
if (q.data.kind === "reach") {
|
|
2143
|
+
interiorIds.set(q.data.rec.name, q.data.interiors.length)
|
|
2144
|
+
}
|
|
1963
2145
|
const paramIds = new Map<string, number>()
|
|
1964
2146
|
const params = new Map<string, ParamEntry>()
|
|
1965
2147
|
q.data.params.forEach(function assignParamId(entry, index) {
|
|
1966
|
-
if (entry.anchor === undefined
|
|
2148
|
+
if (entry.anchor === undefined) {
|
|
1967
2149
|
throw errors.new(
|
|
1968
2150
|
`query param ${entry.name} has no field-anchored use — bind it in an atom or compare it against a bound variable`
|
|
1969
2151
|
)
|
|
@@ -1971,26 +2153,37 @@ function lowerQuery(q: AnyQuery): ProgramIr {
|
|
|
1971
2153
|
paramIds.set(entry.name, index)
|
|
1972
2154
|
params.set(entry.name, entry)
|
|
1973
2155
|
})
|
|
1974
|
-
const ctx: LowerContext = { theory, relationIds,
|
|
1975
|
-
const
|
|
1976
|
-
const head = rec.rules[0]
|
|
1977
|
-
if (head === undefined) {
|
|
1978
|
-
throw errors.new(`query lowering: rec ${rec.name} has no rules`)
|
|
1979
|
-
}
|
|
2156
|
+
const ctx: LowerContext = { theory, relationIds, interiorIds, paramIds, params }
|
|
2157
|
+
const interiors = q.data.interiors.map(function lowerInterior(interior) {
|
|
1980
2158
|
return {
|
|
1981
|
-
head:
|
|
1982
|
-
rules:
|
|
2159
|
+
head: interior.finds.map(headTermOf),
|
|
2160
|
+
rules: interior.rules.map(function lowerInteriorRule(rule) {
|
|
1983
2161
|
return lowerRule(ctx, rule)
|
|
1984
2162
|
})
|
|
1985
2163
|
}
|
|
1986
2164
|
})
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
2165
|
+
const head = q.data.finds.map(headTermOf)
|
|
2166
|
+
const rules = q.data.rules.map(function lowerMainRule(rule) {
|
|
2167
|
+
return lowerRule(ctx, rule)
|
|
2168
|
+
})
|
|
2169
|
+
if (q.data.kind === "cq") {
|
|
2170
|
+
return parseQueryIr({ kind: "cq", interiors, head, rules })
|
|
2171
|
+
}
|
|
2172
|
+
return parseQueryIr({
|
|
2173
|
+
kind: "reach",
|
|
2174
|
+
interiors,
|
|
2175
|
+
rec: {
|
|
2176
|
+
head: q.data.rec.finds.map(headTermOf),
|
|
2177
|
+
base: q.data.rec.base.map(function lowerBase(rule) {
|
|
2178
|
+
return lowerRule(ctx, rule)
|
|
2179
|
+
}),
|
|
2180
|
+
rec: q.data.rec.rec.map(function lowerRecArm(rule) {
|
|
2181
|
+
return lowerRule(ctx, rule)
|
|
2182
|
+
})
|
|
2183
|
+
},
|
|
2184
|
+
head,
|
|
2185
|
+
rules
|
|
1992
2186
|
})
|
|
1993
|
-
return { predicates, output: q.data.recs.length }
|
|
1994
2187
|
}
|
|
1995
2188
|
|
|
1996
2189
|
export type {
|
|
@@ -1998,13 +2191,13 @@ export type {
|
|
|
1998
2191
|
AnyRuleValue,
|
|
1999
2192
|
HeadOf,
|
|
2000
2193
|
HeadShape,
|
|
2001
|
-
|
|
2002
|
-
|
|
2194
|
+
InteriorRuleChain,
|
|
2195
|
+
InteriorRuleScope,
|
|
2003
2196
|
ParamsOf,
|
|
2004
|
-
ProgramState,
|
|
2005
2197
|
Query,
|
|
2006
2198
|
QueryData,
|
|
2007
2199
|
QueryParams,
|
|
2200
|
+
QueryReachStart,
|
|
2008
2201
|
QueryRelation,
|
|
2009
2202
|
QueryRow,
|
|
2010
2203
|
QueryRuleChain,
|
|
@@ -2012,11 +2205,10 @@ export type {
|
|
|
2012
2205
|
QueryStart,
|
|
2013
2206
|
RawChain,
|
|
2014
2207
|
RawScope,
|
|
2015
|
-
RecRef,
|
|
2016
2208
|
RecRuleChain,
|
|
2017
2209
|
RecRuleScope,
|
|
2018
2210
|
RowOf,
|
|
2019
2211
|
RuleValue,
|
|
2020
2212
|
TermOps
|
|
2021
2213
|
}
|
|
2022
|
-
export { lowerQuery,
|
|
2214
|
+
export { lowerQuery, makeRawScope, query, taggedCmpLiteral, taggedLiteral }
|