@bjornpagen/bumbledb 0.2.0 → 0.3.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 +462 -406
- package/README.md +66 -31
- package/dist/closed.d.ts +121 -25
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +108 -42
- package/dist/closed.js.map +1 -1
- package/dist/db.d.ts +12 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +20 -5
- package/dist/db.js.map +1 -1
- package/dist/face.d.ts +100 -55
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +36 -10
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +50 -79
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +20 -53
- package/dist/fields.js.map +1 -1
- package/dist/index.d.ts +17 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -6
- package/dist/index.js.map +1 -1
- package/dist/law.d.ts +224 -0
- package/dist/law.d.ts.map +1 -0
- package/dist/law.js +224 -0
- package/dist/law.js.map +1 -0
- package/dist/lower.d.ts +17 -10
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +34 -23
- package/dist/lower.js.map +1 -1
- package/dist/native.d.ts +6 -2
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js.map +1 -1
- package/dist/query/atom.d.ts +88 -50
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +4 -1
- package/dist/query/atom.js.map +1 -1
- package/dist/query/lower.d.ts +71 -56
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +90 -43
- package/dist/query/lower.js.map +1 -1
- package/dist/query/predicate.d.ts +10 -9
- package/dist/query/predicate.d.ts.map +1 -1
- package/dist/query/predicate.js +2 -2
- package/dist/query/predicate.js.map +1 -1
- package/dist/query/scope.d.ts +76 -41
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +77 -30
- package/dist/query/scope.js.map +1 -1
- package/dist/query/select.d.ts +5 -5
- package/dist/query/select.d.ts.map +1 -1
- package/dist/relation.d.ts +21 -8
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +13 -7
- package/dist/relation.js.map +1 -1
- package/dist/schema.d.ts +41 -3
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +16 -2
- package/dist/schema.js.map +1 -1
- package/dist/spec.d.ts +7 -6
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js.map +1 -1
- package/dist/statements.d.ts +61 -31
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +22 -17
- package/dist/statements.js.map +1 -1
- package/package.json +2 -2
- package/src/closed.ts +243 -68
- package/src/db.ts +23 -5
- package/src/face.ts +162 -84
- package/src/fields.ts +57 -136
- package/src/index.ts +42 -16
- package/src/law.ts +519 -0
- package/src/lower.ts +36 -23
- package/src/native.ts +6 -2
- package/src/query/atom.ts +105 -58
- package/src/query/lower.ts +271 -139
- package/src/query/predicate.ts +43 -33
- package/src/query/scope.ts +125 -49
- package/src/query/select.ts +5 -5
- package/src/relation.ts +15 -9
- package/src/schema.ts +48 -7
- package/src/spec.ts +7 -6
- package/src/statements.ts +83 -43
package/src/query/lower.ts
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
import * as errors from "@superbuilders/errors"
|
|
29
29
|
import type { AnyField } from "#fields.ts"
|
|
30
30
|
import { assertDeclarationOrderKey } from "#fields.ts"
|
|
31
|
+
import type { ClassRecordOf, SchemaClasses } from "#law.ts"
|
|
31
32
|
import type {
|
|
32
33
|
AtomIr,
|
|
33
34
|
ComparisonIr,
|
|
@@ -56,6 +57,8 @@ import type {
|
|
|
56
57
|
CondData,
|
|
57
58
|
CondParamsShape,
|
|
58
59
|
MaskData,
|
|
60
|
+
MatchFields,
|
|
61
|
+
MatchOwner,
|
|
59
62
|
MatchShape,
|
|
60
63
|
ParamUse,
|
|
61
64
|
RecData,
|
|
@@ -66,7 +69,16 @@ import type {
|
|
|
66
69
|
TreeData
|
|
67
70
|
} from "#query/atom.ts"
|
|
68
71
|
import { allen, and, covers, eq, ge, gt, le, lt, ne, not, or, pointIn } from "#query/atom.ts"
|
|
69
|
-
import type {
|
|
72
|
+
import type {
|
|
73
|
+
ClassedField,
|
|
74
|
+
EnvShape,
|
|
75
|
+
Flatten,
|
|
76
|
+
InferredOf,
|
|
77
|
+
JoinOk,
|
|
78
|
+
ParamEntry,
|
|
79
|
+
ParamsRecord,
|
|
80
|
+
Var
|
|
81
|
+
} from "#query/scope.ts"
|
|
70
82
|
import {
|
|
71
83
|
fieldJoins,
|
|
72
84
|
inferred,
|
|
@@ -76,20 +88,27 @@ import {
|
|
|
76
88
|
makeParam,
|
|
77
89
|
makeSetParam,
|
|
78
90
|
makeVar,
|
|
91
|
+
makeVars,
|
|
79
92
|
renderFieldKind,
|
|
80
93
|
term
|
|
81
94
|
} from "#query/scope.ts"
|
|
82
95
|
import type { CheckNameSelect, CheckSelect, RowOfNameSelect, RowOfSelect, SelectEntry } from "#query/select.ts"
|
|
83
96
|
import { argMax, argMin, count, countDistinct, max, min, pack, sum } from "#query/select.ts"
|
|
84
|
-
import type {
|
|
97
|
+
import type { FieldsShape, RelationField } from "#relation.ts"
|
|
85
98
|
import type { AnySchema, Schema, SchemaRelations } from "#schema.ts"
|
|
86
99
|
|
|
87
|
-
/**
|
|
88
|
-
|
|
100
|
+
/**
|
|
101
|
+
* The matchable members of a schema's record — ordinary relations AND
|
|
102
|
+
* closed vocabularies (ψ query atoms: a closed atom is an ordinary EDB
|
|
103
|
+
* atom over the sealed extension; the ENGINE decides whether it folds to a
|
|
104
|
+
* plan-constant member set or joins the L1-resident virtual image — the
|
|
105
|
+
* SDK lowers pass-through and never knows which).
|
|
106
|
+
*/
|
|
107
|
+
type QueryRelation<Rels extends SchemaRelations> = Extract<Rels[keyof Rels], MatchOwner>
|
|
89
108
|
|
|
90
|
-
/** The environment after one bindings record: the incoming env plus every var the record binds. */
|
|
91
|
-
type EnvOfMatch<Env extends EnvShape, F extends FieldsShape, B> =
|
|
92
|
-
Flatten<Env & BindEnv<F, B>> extends infer E extends EnvShape ? E : never
|
|
109
|
+
/** The environment after one bindings record: the incoming env plus every var the record binds (as classed slots). */
|
|
110
|
+
type EnvOfMatch<Env extends EnvShape, F extends FieldsShape, CR, B> =
|
|
111
|
+
Flatten<Env & BindEnv<F, CR, B>> extends infer E extends EnvShape ? E : never
|
|
93
112
|
|
|
94
113
|
/** Reads an inferred-params carrier off a rec reference or rule value. */
|
|
95
114
|
type ParamsOf<T> = InferredOf<T> extends { readonly params: infer P extends ParamsRecord } ? P : Record<never, never>
|
|
@@ -98,12 +117,13 @@ type ParamsOf<T> = InferredOf<T> extends { readonly params: infer P extends Para
|
|
|
98
117
|
type RowOf<T> = InferredOf<T> extends { readonly row: infer R } ? R : never
|
|
99
118
|
|
|
100
119
|
/**
|
|
101
|
-
* A recursive predicate's HEAD signature as
|
|
102
|
-
* for position — carried on the rec
|
|
103
|
-
*
|
|
104
|
-
* query rule, or an unthreaded rec
|
|
120
|
+
* A recursive predicate's HEAD signature as classed slots (descriptor +
|
|
121
|
+
* law-computed class), position for position — carried on the rec
|
|
122
|
+
* reference so an `idb` join can be judged against it; `undefined` on
|
|
123
|
+
* values that carry no head (a plain query rule, or an unthreaded rec
|
|
124
|
+
* handle before its first rule).
|
|
105
125
|
*/
|
|
106
|
-
type HeadShape = readonly
|
|
126
|
+
type HeadShape = readonly ClassedField[] | undefined
|
|
107
127
|
|
|
108
128
|
/**
|
|
109
129
|
* One finished rule as a plain value: the runtime data plus the inferred
|
|
@@ -125,7 +145,7 @@ type HeadFieldsOf<Env extends EnvShape, S extends readonly string[]> = {
|
|
|
125
145
|
}
|
|
126
146
|
|
|
127
147
|
/** Reads an inferred-head carrier off a rule value or rec reference. */
|
|
128
|
-
type HeadOf<T> = InferredOf<T> extends { readonly head: infer H extends readonly
|
|
148
|
+
type HeadOf<T> = InferredOf<T> extends { readonly head: infer H extends readonly ClassedField[] } ? H : undefined
|
|
129
149
|
|
|
130
150
|
/**
|
|
131
151
|
* A recursive predicate REFERENCE — the shape `idb()` targets carry: the
|
|
@@ -142,11 +162,11 @@ interface RecRef<Name extends string, P extends ParamsRecord, Head extends HeadS
|
|
|
142
162
|
readonly [inferred]?: { readonly params: P; readonly head: Head }
|
|
143
163
|
}
|
|
144
164
|
|
|
145
|
-
/** One `idb` position's judgment: the var must be bound by a relation atom,
|
|
165
|
+
/** One `idb` position's judgment: the var must be bound by a relation atom, class-equal to the head slot when the head is carried. */
|
|
146
166
|
type IdbVarOk<Env extends EnvShape, T, F> =
|
|
147
167
|
T extends Var<infer N extends string>
|
|
148
168
|
? N extends keyof Env
|
|
149
|
-
? F extends
|
|
169
|
+
? F extends ClassedField
|
|
150
170
|
? JoinOk<Env[N], F>
|
|
151
171
|
: true
|
|
152
172
|
: false
|
|
@@ -156,12 +176,12 @@ type IdbVarOk<Env extends EnvShape, T, F> =
|
|
|
156
176
|
* The validated `idb` variable tuple: every var must already be bound by a
|
|
157
177
|
* relation atom of the rule; and when the target carries its head
|
|
158
178
|
* signature (the threaded rec handle), the tuple must match the head's
|
|
159
|
-
* arity and every position must be
|
|
179
|
+
* arity and every position must be class-equal to its head slot — the
|
|
160
180
|
* same wall `JoinOk` holds for EDB atoms. An unthreaded handle carries no
|
|
161
|
-
* head; its joins stay boundness-checked here and arity/
|
|
181
|
+
* head; its joins stay boundness-checked here and arity/class-judged at
|
|
162
182
|
* prepare (the engine's law stands behind both tiers).
|
|
163
183
|
*/
|
|
164
|
-
type CheckIdbVars<Env extends EnvShape, V, Head extends HeadShape = undefined> = Head extends readonly
|
|
184
|
+
type CheckIdbVars<Env extends EnvShape, V, Head extends HeadShape = undefined> = Head extends readonly ClassedField[]
|
|
165
185
|
? V extends readonly unknown[]
|
|
166
186
|
? V["length"] extends Head["length"]
|
|
167
187
|
? { readonly [I in keyof V]: IdbVarOk<Env, V[I], Head[I & keyof Head]> extends true ? V[I] : never }
|
|
@@ -178,6 +198,8 @@ type CheckIdbVars<Env extends EnvShape, V, Head extends HeadShape = undefined> =
|
|
|
178
198
|
interface TermOps {
|
|
179
199
|
/** Declares/names one variable: typed by the field it first binds; reuse joins. */
|
|
180
200
|
readonly var: typeof makeVar
|
|
201
|
+
/** Mints several variables at once — `const { service, w } = r.vars("service", "w")`: each name typed exactly; duplicates refuse. */
|
|
202
|
+
readonly vars: typeof makeVars
|
|
181
203
|
/** Names one scalar parameter: typed by its use; the key of the execute params object. */
|
|
182
204
|
readonly param: typeof makeParam
|
|
183
205
|
/** Names one ∈-set parameter (the IR's `ParamSet`): bound to a readonly array at execution. */
|
|
@@ -208,51 +230,77 @@ interface TermOps {
|
|
|
208
230
|
readonly pack: typeof pack
|
|
209
231
|
}
|
|
210
232
|
|
|
211
|
-
/** The rule builder a `query(S).rule(...)` callback receives: the ops plus the first atom. */
|
|
212
|
-
interface QueryRuleScope<Rels extends SchemaRelations> extends TermOps {
|
|
213
|
-
/** The first EDB atom of the rule: fields bind vars, params, ∈-sets, or bare literals; absence is the wildcard (same-named vars within the record join
|
|
214
|
-
match<R extends QueryRelation<Rels>, const B extends MatchShape<
|
|
233
|
+
/** The rule builder a `query(S).rule(...)` callback receives: the ops plus the first atom (`Classes` — the schema type's class map, the join judge's authority). */
|
|
234
|
+
interface QueryRuleScope<Rels extends SchemaRelations, Classes extends SchemaClasses = SchemaClasses> extends TermOps {
|
|
235
|
+
/** The first EDB atom of the rule: fields bind vars, params, ∈-sets, or bare literals; absence is the wildcard (same-named vars within the record join class-equal). */
|
|
236
|
+
match<R extends QueryRelation<Rels>, const B extends MatchShape<MatchFields<R>>>(
|
|
215
237
|
relation: R,
|
|
216
|
-
bindings: B & CheckBindings<Record<never, never>,
|
|
217
|
-
): QueryRuleChain<
|
|
238
|
+
bindings: B & CheckBindings<Record<never, never>, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>
|
|
239
|
+
): QueryRuleChain<
|
|
240
|
+
Rels,
|
|
241
|
+
EnvOfMatch<Record<never, never>, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>,
|
|
242
|
+
BindParamsShape<MatchFields<R>, B>,
|
|
243
|
+
Classes
|
|
244
|
+
>
|
|
218
245
|
}
|
|
219
246
|
|
|
220
247
|
/** The chain of a plain query rule: more atoms, residual predicates, then the head. */
|
|
221
|
-
interface QueryRuleChain<
|
|
222
|
-
|
|
223
|
-
|
|
248
|
+
interface QueryRuleChain<
|
|
249
|
+
Rels extends SchemaRelations,
|
|
250
|
+
Env extends EnvShape,
|
|
251
|
+
P extends ParamsRecord,
|
|
252
|
+
Classes extends SchemaClasses = SchemaClasses
|
|
253
|
+
> {
|
|
254
|
+
/** One more positive EDB atom — var reuse joins, class-equal by the environment check. */
|
|
255
|
+
match<R extends QueryRelation<Rels>, const B extends MatchShape<MatchFields<R>>>(
|
|
224
256
|
relation: R,
|
|
225
|
-
bindings: B & CheckBindings<Env,
|
|
226
|
-
): QueryRuleChain<
|
|
257
|
+
bindings: B & CheckBindings<Env, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>
|
|
258
|
+
): QueryRuleChain<
|
|
259
|
+
Rels,
|
|
260
|
+
EnvOfMatch<Env, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>,
|
|
261
|
+
Flatten<P & BindParamsShape<MatchFields<R>, B>>,
|
|
262
|
+
Classes
|
|
263
|
+
>
|
|
227
264
|
/** One residual predicate: a comparison, an `and`/`or` tree, or a negated atom (`r.not`). */
|
|
228
265
|
where<const C extends AnyCond>(
|
|
229
|
-
cond: CheckCond<Env, C> & C
|
|
230
|
-
): QueryRuleChain<Rels, Env, Flatten<P & CondParamsShape<Env, C
|
|
266
|
+
cond: CheckCond<Env, Classes, C> & C
|
|
267
|
+
): QueryRuleChain<Rels, Env, Flatten<P & CondParamsShape<Env, C>>, Classes>
|
|
231
268
|
/** The head projection: var names, the measure, and aggregates; written order = answer column order. */
|
|
232
269
|
select<const S extends readonly SelectEntry[]>(...entries: CheckSelect<Env, S> & S): RuleValue<RowOfSelect<Env, S>, P>
|
|
233
270
|
}
|
|
234
271
|
|
|
235
272
|
/** The rule builder an OUTPUT rule of a `program()` receives: a query rule plus finished-stratum `idb` atoms. */
|
|
236
|
-
interface OutputRuleScope<Rels extends SchemaRelations> extends TermOps {
|
|
237
|
-
match<R extends QueryRelation<Rels>, const B extends MatchShape<
|
|
273
|
+
interface OutputRuleScope<Rels extends SchemaRelations, Classes extends SchemaClasses = SchemaClasses> extends TermOps {
|
|
274
|
+
match<R extends QueryRelation<Rels>, const B extends MatchShape<MatchFields<R>>>(
|
|
238
275
|
relation: R,
|
|
239
|
-
bindings: B & CheckBindings<Record<never, never>,
|
|
276
|
+
bindings: B & CheckBindings<Record<never, never>, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>
|
|
240
277
|
): OutputRuleChain<
|
|
241
278
|
Rels,
|
|
242
|
-
EnvOfMatch<Record<never, never>,
|
|
243
|
-
BindParamsShape<
|
|
279
|
+
EnvOfMatch<Record<never, never>, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>,
|
|
280
|
+
BindParamsShape<MatchFields<R>, B>,
|
|
281
|
+
Classes
|
|
244
282
|
>
|
|
245
283
|
}
|
|
246
284
|
|
|
247
285
|
/** The chain of an output rule: atoms, predicates, `idb` joins over the program's recs, then the head. */
|
|
248
|
-
interface OutputRuleChain<
|
|
249
|
-
|
|
286
|
+
interface OutputRuleChain<
|
|
287
|
+
Rels extends SchemaRelations,
|
|
288
|
+
Env extends EnvShape,
|
|
289
|
+
P extends ParamsRecord,
|
|
290
|
+
Classes extends SchemaClasses = SchemaClasses
|
|
291
|
+
> {
|
|
292
|
+
match<R extends QueryRelation<Rels>, const B extends MatchShape<MatchFields<R>>>(
|
|
250
293
|
relation: R,
|
|
251
|
-
bindings: B & CheckBindings<Env,
|
|
252
|
-
): OutputRuleChain<
|
|
294
|
+
bindings: B & CheckBindings<Env, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>
|
|
295
|
+
): OutputRuleChain<
|
|
296
|
+
Rels,
|
|
297
|
+
EnvOfMatch<Env, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>,
|
|
298
|
+
Flatten<P & BindParamsShape<MatchFields<R>, B>>,
|
|
299
|
+
Classes
|
|
300
|
+
>
|
|
253
301
|
where<const C extends AnyCond>(
|
|
254
|
-
cond: CheckCond<Env, C> & C
|
|
255
|
-
): OutputRuleChain<Rels, Env, Flatten<P & CondParamsShape<Env, C
|
|
302
|
+
cond: CheckCond<Env, Classes, C> & C
|
|
303
|
+
): OutputRuleChain<Rels, Env, Flatten<P & CondParamsShape<Env, C>>, Classes>
|
|
256
304
|
/**
|
|
257
305
|
* One `idb` atom over a FINISHED stratum (any rec of this program): a
|
|
258
306
|
* positional join against the rec's head. An idb atom is a join
|
|
@@ -260,26 +308,28 @@ interface OutputRuleChain<Rels extends SchemaRelations, Env extends EnvShape, P
|
|
|
260
308
|
* the rule (the theory's own domain relation; the rec's answers are
|
|
261
309
|
* theory values, so the join is identity). Threading the rec value the
|
|
262
310
|
* last `.rule(...)` returned carries its rules' params into `Params`
|
|
263
|
-
* AND its head signature, so the join is arity- and
|
|
311
|
+
* AND its head signature, so the join is arity- and class-checked
|
|
264
312
|
* against the head at compile time.
|
|
265
313
|
*/
|
|
266
314
|
idb<Target extends RecRef<string, ParamsRecord>, const V extends readonly Var<string>[]>(
|
|
267
315
|
target: Target,
|
|
268
316
|
...vars: CheckIdbVars<Env, V, HeadOf<Target>> & V
|
|
269
|
-
): OutputRuleChain<Rels, Env, Flatten<P & ParamsOf<Target
|
|
317
|
+
): OutputRuleChain<Rels, Env, Flatten<P & ParamsOf<Target>>, Classes>
|
|
270
318
|
select<const S extends readonly SelectEntry[]>(...entries: CheckSelect<Env, S> & S): RuleValue<RowOfSelect<Env, S>, P>
|
|
271
319
|
}
|
|
272
320
|
|
|
273
321
|
/** The rule builder a RECURSIVE rule (`rec.rule(...)`) receives. */
|
|
274
|
-
interface RecRuleScope<Rels extends SchemaRelations, Self extends string
|
|
275
|
-
|
|
322
|
+
interface RecRuleScope<Rels extends SchemaRelations, Self extends string, Classes extends SchemaClasses = SchemaClasses>
|
|
323
|
+
extends TermOps {
|
|
324
|
+
match<R extends QueryRelation<Rels>, const B extends MatchShape<MatchFields<R>>>(
|
|
276
325
|
relation: R,
|
|
277
|
-
bindings: B & CheckBindings<Record<never, never>,
|
|
326
|
+
bindings: B & CheckBindings<Record<never, never>, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>
|
|
278
327
|
): RecRuleChain<
|
|
279
328
|
Rels,
|
|
280
329
|
Self,
|
|
281
|
-
EnvOfMatch<Record<never, never>,
|
|
282
|
-
BindParamsShape<
|
|
330
|
+
EnvOfMatch<Record<never, never>, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>,
|
|
331
|
+
BindParamsShape<MatchFields<R>, B>,
|
|
332
|
+
Classes
|
|
283
333
|
>
|
|
284
334
|
}
|
|
285
335
|
|
|
@@ -295,21 +345,28 @@ interface RecRuleChain<
|
|
|
295
345
|
Rels extends SchemaRelations,
|
|
296
346
|
Self extends string,
|
|
297
347
|
Env extends EnvShape,
|
|
298
|
-
P extends ParamsRecord
|
|
348
|
+
P extends ParamsRecord,
|
|
349
|
+
Classes extends SchemaClasses = SchemaClasses
|
|
299
350
|
> {
|
|
300
|
-
match<R extends QueryRelation<Rels>, const B extends MatchShape<
|
|
351
|
+
match<R extends QueryRelation<Rels>, const B extends MatchShape<MatchFields<R>>>(
|
|
301
352
|
relation: R,
|
|
302
|
-
bindings: B & CheckBindings<Env,
|
|
303
|
-
): RecRuleChain<
|
|
353
|
+
bindings: B & CheckBindings<Env, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>
|
|
354
|
+
): RecRuleChain<
|
|
355
|
+
Rels,
|
|
356
|
+
Self,
|
|
357
|
+
EnvOfMatch<Env, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>,
|
|
358
|
+
Flatten<P & BindParamsShape<MatchFields<R>, B>>,
|
|
359
|
+
Classes
|
|
360
|
+
>
|
|
304
361
|
where<const C extends AnyCond>(
|
|
305
|
-
cond: CheckCond<Env, C> & C
|
|
306
|
-
): RecRuleChain<Rels, Self, Env, Flatten<P & CondParamsShape<Env, C
|
|
307
|
-
/** The self-recursive atom: `idb(self, ...boundVars)` — only this rec's own reference is accepted (threaded, its head arity- and
|
|
362
|
+
cond: CheckCond<Env, Classes, C> & C
|
|
363
|
+
): RecRuleChain<Rels, Self, Env, Flatten<P & CondParamsShape<Env, C>>, Classes>
|
|
364
|
+
/** The self-recursive atom: `idb(self, ...boundVars)` — only this rec's own reference is accepted (threaded, its head arity- and class-checks the join). */
|
|
308
365
|
idb<Target extends RecRef<Self, ParamsRecord>, const V extends readonly Var<string>[]>(
|
|
309
366
|
target: Target,
|
|
310
367
|
...vars: CheckIdbVars<Env, V, HeadOf<Target>> & V
|
|
311
|
-
): RecRuleChain<Rels, Self, Env, P>
|
|
312
|
-
/** The recursive head: bound variable names only (the creation quarantine, restated for fixpoint topology); the value carries the head's
|
|
368
|
+
): RecRuleChain<Rels, Self, Env, P, Classes>
|
|
369
|
+
/** The recursive head: bound variable names only (the creation quarantine, restated for fixpoint topology); the value carries the head's classed slots for `idb` pairing. */
|
|
313
370
|
select<const S extends readonly string[]>(
|
|
314
371
|
...names: CheckNameSelect<Env, S> & S
|
|
315
372
|
): RuleValue<RowOfNameSelect<Env, S>, P, HeadFieldsOf<Env, S>>
|
|
@@ -333,13 +390,18 @@ interface QueryData {
|
|
|
333
390
|
* the rules use. Prepare with `db.prepare(q)`; nothing here touches an
|
|
334
391
|
* engine.
|
|
335
392
|
*/
|
|
336
|
-
interface Query<
|
|
337
|
-
|
|
393
|
+
interface Query<
|
|
394
|
+
Rels extends SchemaRelations,
|
|
395
|
+
Row,
|
|
396
|
+
Params extends ParamsRecord,
|
|
397
|
+
Classes extends SchemaClasses = SchemaClasses
|
|
398
|
+
> {
|
|
399
|
+
readonly schema: Schema<Rels, Classes>
|
|
338
400
|
readonly data: QueryData
|
|
339
401
|
/** One more rule — the query's answers are the SET UNION of its rules' answers; every rule derives the same head. */
|
|
340
402
|
rule<RV extends AnyRuleValue>(
|
|
341
|
-
build: (r: QueryRuleScope<Rels>) => RV
|
|
342
|
-
): Query<Rels, Row | RowOf<RV>, Flatten<Params & ParamsOf<RV
|
|
403
|
+
build: (r: QueryRuleScope<Rels, Classes>) => RV
|
|
404
|
+
): Query<Rels, Row | RowOf<RV>, Flatten<Params & ParamsOf<RV>>, Classes>
|
|
343
405
|
readonly [inferred]?: { readonly row: Row; readonly params: Params }
|
|
344
406
|
}
|
|
345
407
|
|
|
@@ -360,13 +422,16 @@ type QueryRow<Q extends AnyQuery> = RowOf<Q>
|
|
|
360
422
|
type QueryParams<Q extends AnyQuery> = ParamsOf<Q>
|
|
361
423
|
|
|
362
424
|
/** The entry value of `query(S)`: the first `.rule` mints the query. */
|
|
363
|
-
interface QueryStart<Rels extends SchemaRelations> {
|
|
364
|
-
rule<RV extends AnyRuleValue>(
|
|
425
|
+
interface QueryStart<Rels extends SchemaRelations, Classes extends SchemaClasses = SchemaClasses> {
|
|
426
|
+
rule<RV extends AnyRuleValue>(
|
|
427
|
+
build: (r: QueryRuleScope<Rels, Classes>) => RV
|
|
428
|
+
): Query<Rels, RowOf<RV>, ParamsOf<RV>, Classes>
|
|
365
429
|
}
|
|
366
430
|
|
|
367
431
|
/** The frozen constructor vocabulary every rule builder spreads. */
|
|
368
432
|
const termOps: TermOps = Object.freeze({
|
|
369
433
|
var: makeVar,
|
|
434
|
+
vars: makeVars,
|
|
370
435
|
param: makeParam,
|
|
371
436
|
inSet: makeSetParam,
|
|
372
437
|
maskParam: makeMaskParam,
|
|
@@ -396,7 +461,7 @@ const termOps: TermOps = Object.freeze({
|
|
|
396
461
|
/** One rule under construction: immutable — every chain step is a fresh state. */
|
|
397
462
|
interface RuleBuildState {
|
|
398
463
|
readonly items: readonly RuleItem[]
|
|
399
|
-
readonly varFields: Readonly<Record<string,
|
|
464
|
+
readonly varFields: Readonly<Record<string, ClassedField>>
|
|
400
465
|
readonly paramUses: readonly ParamUse[]
|
|
401
466
|
}
|
|
402
467
|
|
|
@@ -407,43 +472,68 @@ const EMPTY_RULE: RuleBuildState = Object.freeze({
|
|
|
407
472
|
paramUses: Object.freeze([])
|
|
408
473
|
})
|
|
409
474
|
|
|
410
|
-
/** One resolved bindings record: the atom entries, the vars it binds, and the params it uses. */
|
|
475
|
+
/** One resolved bindings record: the atom entries, the vars it binds (as classed slots), and the params it uses. */
|
|
411
476
|
interface ResolvedBindings {
|
|
412
477
|
readonly atom: AtomData
|
|
413
|
-
readonly vars: ReadonlyArray<{ readonly name: string; readonly
|
|
478
|
+
readonly vars: ReadonlyArray<{ readonly name: string; readonly slot: ClassedField }>
|
|
414
479
|
readonly uses: readonly ParamUse[]
|
|
415
480
|
}
|
|
416
481
|
|
|
417
482
|
/**
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
*
|
|
421
|
-
*
|
|
483
|
+
* The ordered matchable fields of an atom owner — the runtime twin of the
|
|
484
|
+
* type tier's `MatchFields`: a relation's declared fields; a closed
|
|
485
|
+
* relation's SEALED shape, the synthetic `id` (the value's own
|
|
486
|
+
* roster-carrying descriptor, by identity) at ordinal 0 and the declared
|
|
487
|
+
* payload columns at declared index + 1 (the sealed shift, mirroring
|
|
488
|
+
* `spec.rs`'s resolver — a `ClosedColumn` is structurally a
|
|
489
|
+
* {@link RelationField}). The lowering golden pins this mapping.
|
|
490
|
+
*/
|
|
491
|
+
function matchFieldsOf(owner: MatchOwner): readonly RelationField[] {
|
|
492
|
+
if ("axioms" in owner) {
|
|
493
|
+
return [Object.freeze({ name: "id", field: owner.id }), ...owner.data.columns]
|
|
494
|
+
}
|
|
495
|
+
return owner.data.fields
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Resolves a bindings record against an atom owner's matchable fields (a
|
|
500
|
+
* relation's declared fields; a closed relation's sealed id + columns), in
|
|
501
|
+
* the record's written order: terms classify by their runtime tag,
|
|
502
|
+
* everything else is a bare literal (typed by the FIELD at lowering — the
|
|
503
|
+
* membership typing rule included). Every bound field carries its
|
|
504
|
+
* law-computed class, read off the schema value's frozen class map — the
|
|
505
|
+
* runtime twin of the type tier's `SlotAt` lookups.
|
|
422
506
|
*/
|
|
423
507
|
function resolveBindings(
|
|
424
508
|
context: string,
|
|
425
|
-
relation:
|
|
426
|
-
bindings: Readonly<Record<string, unknown
|
|
509
|
+
relation: MatchOwner,
|
|
510
|
+
bindings: Readonly<Record<string, unknown>>,
|
|
511
|
+
classes: SchemaClasses
|
|
427
512
|
): ResolvedBindings {
|
|
428
513
|
const entries: BindingEntry[] = []
|
|
429
|
-
const vars: Array<{ readonly name: string; readonly
|
|
514
|
+
const vars: Array<{ readonly name: string; readonly slot: ClassedField }> = []
|
|
430
515
|
const uses: ParamUse[] = []
|
|
516
|
+
const relationClasses = classes[relation.name]
|
|
517
|
+
const ordered = matchFieldsOf(relation)
|
|
431
518
|
for (const [fieldName, value] of Object.entries(bindings)) {
|
|
432
519
|
if (value === undefined) {
|
|
433
520
|
continue
|
|
434
521
|
}
|
|
435
|
-
const declared =
|
|
522
|
+
const declared = ordered.find(function byName(candidate) {
|
|
436
523
|
return candidate.name === fieldName
|
|
437
524
|
})
|
|
438
525
|
if (declared === undefined) {
|
|
439
526
|
throw errors.new(`${context} has no field ${fieldName}`)
|
|
440
527
|
}
|
|
528
|
+
const fieldClass = relationClasses?.[fieldName]
|
|
441
529
|
let bound: BindingEntry["term"]
|
|
442
530
|
if (isTerm(value)) {
|
|
443
531
|
switch (value[term]) {
|
|
444
532
|
case "var": {
|
|
445
533
|
bound = Object.freeze({ kind: "var" as const, name: value.name })
|
|
446
|
-
vars.push(
|
|
534
|
+
vars.push(
|
|
535
|
+
Object.freeze({ name: value.name, slot: Object.freeze({ field: declared.field, class: fieldClass }) })
|
|
536
|
+
)
|
|
447
537
|
break
|
|
448
538
|
}
|
|
449
539
|
case "param": {
|
|
@@ -472,7 +562,7 @@ function resolveBindings(
|
|
|
472
562
|
} else {
|
|
473
563
|
bound = Object.freeze({ kind: "literal" as const, value })
|
|
474
564
|
}
|
|
475
|
-
entries.push(Object.freeze({ field: fieldName, data: declared.field, term: bound }))
|
|
565
|
+
entries.push(Object.freeze({ field: fieldName, data: declared.field, class: fieldClass, term: bound }))
|
|
476
566
|
}
|
|
477
567
|
return {
|
|
478
568
|
atom: Object.freeze({ relation, bindings: Object.freeze(entries) }),
|
|
@@ -484,24 +574,25 @@ function resolveBindings(
|
|
|
484
574
|
/**
|
|
485
575
|
* Extends a rule state with one positive atom. Vars bind on first
|
|
486
576
|
* occurrence; every LATER occurrence (a later atom's field or a same-record
|
|
487
|
-
* sibling) is a join and must be
|
|
488
|
-
* of the type tier's `JoinOk
|
|
489
|
-
* callers too.
|
|
577
|
+
* sibling) is a join and must be class-equal — the construction-time twin
|
|
578
|
+
* of the type tier's `JoinOk` (bare pairs only with bare), so the domain
|
|
579
|
+
* wall holds for untyped callers too.
|
|
490
580
|
*/
|
|
491
581
|
function advanceMatch(
|
|
492
582
|
state: RuleBuildState,
|
|
493
|
-
relation:
|
|
494
|
-
bindings: Readonly<Record<string, unknown
|
|
583
|
+
relation: MatchOwner,
|
|
584
|
+
bindings: Readonly<Record<string, unknown>>,
|
|
585
|
+
classes: SchemaClasses
|
|
495
586
|
): RuleBuildState {
|
|
496
|
-
const resolved = resolveBindings(`relation ${relation.name}`, relation, bindings)
|
|
497
|
-
const varFields: Record<string,
|
|
587
|
+
const resolved = resolveBindings(`relation ${relation.name}`, relation, bindings, classes)
|
|
588
|
+
const varFields: Record<string, ClassedField> = { ...state.varFields }
|
|
498
589
|
for (const bound of resolved.vars) {
|
|
499
590
|
const existing = varFields[bound.name]
|
|
500
591
|
if (existing === undefined) {
|
|
501
|
-
varFields[bound.name] = bound.
|
|
502
|
-
} else if (!fieldJoins(existing, bound.
|
|
592
|
+
varFields[bound.name] = bound.slot
|
|
593
|
+
} else if (!fieldJoins(existing, bound.slot)) {
|
|
503
594
|
throw errors.new(
|
|
504
|
-
`relation ${relation.name}: the variable ${bound.name} joins domain-unequal fields — first bound at ${renderFieldKind(existing)}, reused at ${renderFieldKind(bound.
|
|
595
|
+
`relation ${relation.name}: the variable ${bound.name} joins domain-unequal fields — first bound at ${renderFieldKind(existing)}, reused at ${renderFieldKind(bound.slot)} (a var joins only class-equal slots; bare pairs only with bare)`
|
|
505
596
|
)
|
|
506
597
|
}
|
|
507
598
|
}
|
|
@@ -541,7 +632,7 @@ function sideUses(
|
|
|
541
632
|
op: CmpKind,
|
|
542
633
|
side: CmpTermData,
|
|
543
634
|
sibling: CmpTermData,
|
|
544
|
-
varFields: Readonly<Record<string,
|
|
635
|
+
varFields: Readonly<Record<string, ClassedField>>,
|
|
545
636
|
uses: ParamUse[]
|
|
546
637
|
): void {
|
|
547
638
|
if (side.kind !== "param" && side.kind !== "setParam") {
|
|
@@ -549,7 +640,7 @@ function sideUses(
|
|
|
549
640
|
}
|
|
550
641
|
let anchor: AnyField | "measure" | undefined
|
|
551
642
|
if (sibling.kind === "var") {
|
|
552
|
-
anchor = varFields[sibling.name]
|
|
643
|
+
anchor = varFields[sibling.name]?.field
|
|
553
644
|
} else if (sibling.kind === "measure") {
|
|
554
645
|
anchor = "measure"
|
|
555
646
|
} else {
|
|
@@ -566,7 +657,7 @@ function sideUses(
|
|
|
566
657
|
}
|
|
567
658
|
|
|
568
659
|
/** Lowers one condition VALUE to its runtime data, recording param uses. */
|
|
569
|
-
function condDataOf(cond: AnyCond, varFields: Readonly<Record<string,
|
|
660
|
+
function condDataOf(cond: AnyCond, varFields: Readonly<Record<string, ClassedField>>, uses: ParamUse[]): CondData {
|
|
570
661
|
if (cond.cond === "cmp") {
|
|
571
662
|
const lhs = cmpTermDataOf(cond.op, cond.lhs)
|
|
572
663
|
const rhs = cmpTermDataOf(cond.op, cond.rhs)
|
|
@@ -602,18 +693,18 @@ function condDataOf(cond: AnyCond, varFields: Readonly<Record<string, AnyField>>
|
|
|
602
693
|
}
|
|
603
694
|
|
|
604
695
|
/** Extends a rule state with one `.where` item (a condition or a negated atom). */
|
|
605
|
-
function advanceWhere(state: RuleBuildState, cond: AnyCond): RuleBuildState {
|
|
696
|
+
function advanceWhere(state: RuleBuildState, cond: AnyCond, classes: SchemaClasses): RuleBuildState {
|
|
606
697
|
if (typeof cond !== "object" || cond === null || !("cond" in cond)) {
|
|
607
698
|
throw errors.new("where() takes a comparison, an and()/or() tree, or a negated atom")
|
|
608
699
|
}
|
|
609
700
|
if (cond.cond === "not") {
|
|
610
|
-
const relation:
|
|
701
|
+
const relation: MatchOwner = cond.relation
|
|
611
702
|
const bindings: Readonly<Record<string, unknown>> = Object.fromEntries(
|
|
612
703
|
Object.entries(cond.bindings ?? {}).filter(function defined([, value]) {
|
|
613
704
|
return value !== undefined
|
|
614
705
|
})
|
|
615
706
|
)
|
|
616
|
-
const resolved = resolveBindings(`negated relation ${relation.name}`, relation, bindings)
|
|
707
|
+
const resolved = resolveBindings(`negated relation ${relation.name}`, relation, bindings, classes)
|
|
617
708
|
return {
|
|
618
709
|
items: Object.freeze([...state.items, Object.freeze({ kind: "negated" as const, atom: resolved.atom })]),
|
|
619
710
|
varFields: state.varFields,
|
|
@@ -719,26 +810,34 @@ function aggregateColumnOf(entry: {
|
|
|
719
810
|
}
|
|
720
811
|
|
|
721
812
|
/** Requires a var name to be bound by a relation atom of the rule. */
|
|
722
|
-
function assertBound(context: string, varFields: Readonly<Record<string,
|
|
723
|
-
const
|
|
724
|
-
if (
|
|
813
|
+
function assertBound(context: string, varFields: Readonly<Record<string, ClassedField>>, name: string): ClassedField {
|
|
814
|
+
const slot = varFields[name]
|
|
815
|
+
if (slot === undefined) {
|
|
725
816
|
throw errors.new(`${context}: the variable ${name} is not bound by a relation atom of the rule`)
|
|
726
817
|
}
|
|
727
|
-
return
|
|
818
|
+
return slot
|
|
728
819
|
}
|
|
729
820
|
|
|
730
821
|
/** Requires a var name to be bound at an interval field (the measure's and pack's domain). */
|
|
731
|
-
function assertIntervalBound(context: string, varFields: Readonly<Record<string,
|
|
732
|
-
const
|
|
733
|
-
if (field.kind !== "interval") {
|
|
822
|
+
function assertIntervalBound(context: string, varFields: Readonly<Record<string, ClassedField>>, name: string): void {
|
|
823
|
+
const slot = assertBound(context, varFields, name)
|
|
824
|
+
if (slot.field.kind !== "interval") {
|
|
734
825
|
throw errors.new(
|
|
735
826
|
`${context}: ${name} is not interval-typed — the measure is defined over interval-typed variables only`
|
|
736
827
|
)
|
|
737
828
|
}
|
|
738
829
|
}
|
|
739
830
|
|
|
740
|
-
/**
|
|
741
|
-
|
|
831
|
+
/**
|
|
832
|
+
* Validates one condition's variable references against the rule's bound
|
|
833
|
+
* names — and, for `eq`/`ne` over two variables, holds the class wall: the
|
|
834
|
+
* unification IS a join, so the two slots must be class-equal exactly as a
|
|
835
|
+
* match-reuse join must be (the construction-time twin of the type tier's
|
|
836
|
+
* `EqOk` → `JoinOk`; bare pairs only with bare). The engine cannot backstop
|
|
837
|
+
* this one — the query IR carries no domains — so the wall lives here for
|
|
838
|
+
* untyped callers too.
|
|
839
|
+
*/
|
|
840
|
+
function validateCond(context: string, varFields: Readonly<Record<string, ClassedField>>, cond: CondData): void {
|
|
742
841
|
if (cond.kind === "cmp") {
|
|
743
842
|
for (const side of [cond.lhs, cond.rhs]) {
|
|
744
843
|
if (side.kind === "var") {
|
|
@@ -748,6 +847,15 @@ function validateCond(context: string, varFields: Readonly<Record<string, AnyFie
|
|
|
748
847
|
assertIntervalBound(context, varFields, side.name)
|
|
749
848
|
}
|
|
750
849
|
}
|
|
850
|
+
if ((cond.op === "eq" || cond.op === "ne") && cond.lhs.kind === "var" && cond.rhs.kind === "var") {
|
|
851
|
+
const lhs = assertBound(context, varFields, cond.lhs.name)
|
|
852
|
+
const rhs = assertBound(context, varFields, cond.rhs.name)
|
|
853
|
+
if (!fieldJoins(lhs, rhs)) {
|
|
854
|
+
throw errors.new(
|
|
855
|
+
`${context}: ${cond.op}(${cond.lhs.name}, ${cond.rhs.name}) unifies domain-unequal fields — ${cond.lhs.name} bound at ${renderFieldKind(lhs)}, ${cond.rhs.name} at ${renderFieldKind(rhs)} (a var joins only class-equal slots; bare pairs only with bare)`
|
|
856
|
+
)
|
|
857
|
+
}
|
|
858
|
+
}
|
|
751
859
|
return
|
|
752
860
|
}
|
|
753
861
|
for (const child of cond.children) {
|
|
@@ -756,7 +864,11 @@ function validateCond(context: string, varFields: Readonly<Record<string, AnyFie
|
|
|
756
864
|
}
|
|
757
865
|
|
|
758
866
|
/** Validates one select column's variable references. */
|
|
759
|
-
function validateColumn(
|
|
867
|
+
function validateColumn(
|
|
868
|
+
context: string,
|
|
869
|
+
varFields: Readonly<Record<string, ClassedField>>,
|
|
870
|
+
column: SelectColumn
|
|
871
|
+
): void {
|
|
760
872
|
const entry = column.entry
|
|
761
873
|
if (entry.kind === "var") {
|
|
762
874
|
assertBound(`${context} select ${column.name}`, varFields, entry.over)
|
|
@@ -821,9 +933,10 @@ function completeRule(context: string, state: RuleBuildState, columns: readonly
|
|
|
821
933
|
`${context}: negated ${item.atom.relation.name} atom binds the variable ${binding.term.name} at position ${binding.field}, but no positive atom of the rule binds it — a negated atom binds nothing, only rejects (the safety rule)`
|
|
822
934
|
)
|
|
823
935
|
}
|
|
824
|
-
|
|
936
|
+
const negatedSlot: ClassedField = { field: binding.data, class: binding.class }
|
|
937
|
+
if (!fieldJoins(bound, negatedSlot)) {
|
|
825
938
|
throw errors.new(
|
|
826
|
-
`${context}: negated ${item.atom.relation.name} atom reuses the variable ${binding.term.name} at ${binding.field} (${renderFieldKind(
|
|
939
|
+
`${context}: negated ${item.atom.relation.name} atom reuses the variable ${binding.term.name} at ${binding.field} (${renderFieldKind(negatedSlot)}), but the rule binds it at ${renderFieldKind(bound)} — a var joins only class-equal slots; bare pairs only with bare`
|
|
827
940
|
)
|
|
828
941
|
}
|
|
829
942
|
}
|
|
@@ -842,10 +955,10 @@ function completeRule(context: string, state: RuleBuildState, columns: readonly
|
|
|
842
955
|
if (column === undefined || column.entry.kind !== "var") {
|
|
843
956
|
return
|
|
844
957
|
}
|
|
845
|
-
const
|
|
846
|
-
if (
|
|
958
|
+
const headSlot = head?.varFields[column.entry.over]
|
|
959
|
+
if (headSlot !== undefined && !fieldJoins(headSlot, bound)) {
|
|
847
960
|
throw errors.new(
|
|
848
|
-
`${context}: idb ${item.rec.name} joins the variable ${name} (${renderFieldKind(bound)}) at head position ${position} (${column.name}: ${renderFieldKind(
|
|
961
|
+
`${context}: idb ${item.rec.name} joins the variable ${name} (${renderFieldKind(bound)}) at head position ${position} (${column.name}: ${renderFieldKind(headSlot)}) — a var joins only class-equal slots; bare pairs only with bare`
|
|
849
962
|
)
|
|
850
963
|
}
|
|
851
964
|
})
|
|
@@ -877,7 +990,7 @@ function makeRuleValue<Row, P extends ParamsRecord>(rule: RuleData): RuleValue<R
|
|
|
877
990
|
* (bound variable names only — the creation quarantine).
|
|
878
991
|
*/
|
|
879
992
|
interface RawChain {
|
|
880
|
-
match(relation:
|
|
993
|
+
match(relation: MatchOwner, bindings: Readonly<Record<string, unknown>>): RawChain
|
|
881
994
|
where(cond: AnyCond): RawChain
|
|
882
995
|
idb(target: RecRef<string, ParamsRecord>, ...vars: readonly Var<string>[]): RawChain
|
|
883
996
|
select(...entries: readonly SelectEntry[]): RuleValue<never, never>
|
|
@@ -885,14 +998,15 @@ interface RawChain {
|
|
|
885
998
|
|
|
886
999
|
/** The runtime rule-builder shape beneath every typed scope. */
|
|
887
1000
|
interface RawScope extends TermOps {
|
|
888
|
-
match(relation:
|
|
1001
|
+
match(relation: MatchOwner, bindings: Readonly<Record<string, unknown>>): RawChain
|
|
889
1002
|
}
|
|
890
1003
|
|
|
891
|
-
/** Which rule family a chain builds — gates `idb` and the recursive select. */
|
|
892
|
-
type ChainContext =
|
|
1004
|
+
/** Which rule family a chain builds — gates `idb` and the recursive select — plus the schema's runtime class map (the join judge's authority). */
|
|
1005
|
+
type ChainContext = { readonly classes: SchemaClasses } & (
|
|
893
1006
|
| { readonly kind: "query" }
|
|
894
1007
|
| { readonly kind: "rec"; readonly self: RecData }
|
|
895
1008
|
| { readonly kind: "output"; readonly program: ProgramState }
|
|
1009
|
+
)
|
|
896
1010
|
|
|
897
1011
|
/** The diagnostic label of a chain context. */
|
|
898
1012
|
function contextLabel(context: ChainContext): string {
|
|
@@ -948,10 +1062,10 @@ function selectColumns(context: ChainContext, entries: readonly SelectEntry[]):
|
|
|
948
1062
|
function makeRawChain(context: ChainContext, state: RuleBuildState): RawChain {
|
|
949
1063
|
const chain: RawChain = {
|
|
950
1064
|
match(relation, bindings) {
|
|
951
|
-
return makeRawChain(context, advanceMatch(state, relation, bindings))
|
|
1065
|
+
return makeRawChain(context, advanceMatch(state, relation, bindings, context.classes))
|
|
952
1066
|
},
|
|
953
1067
|
where(cond) {
|
|
954
|
-
return makeRawChain(context, advanceWhere(state, cond))
|
|
1068
|
+
return makeRawChain(context, advanceWhere(state, cond, context.classes))
|
|
955
1069
|
},
|
|
956
1070
|
idb(target, ...vars) {
|
|
957
1071
|
return makeRawChain(context, idbAdvance(context, state, target, vars))
|
|
@@ -969,7 +1083,7 @@ function makeRawScope(context: ChainContext): RawScope {
|
|
|
969
1083
|
const scope: RawScope = {
|
|
970
1084
|
...termOps,
|
|
971
1085
|
match(relation, bindings) {
|
|
972
|
-
return makeRawChain(context, advanceMatch(EMPTY_RULE, relation, bindings))
|
|
1086
|
+
return makeRawChain(context, advanceMatch(EMPTY_RULE, relation, bindings, context.classes))
|
|
973
1087
|
}
|
|
974
1088
|
}
|
|
975
1089
|
Object.freeze(scope)
|
|
@@ -990,26 +1104,31 @@ function isTypedScope<S>(scope: RawScope): scope is RawScope & S {
|
|
|
990
1104
|
}
|
|
991
1105
|
|
|
992
1106
|
/** Builds one query-rule builder (the typed face of the raw builder). */
|
|
993
|
-
function makeQueryRuleScope<Rels extends SchemaRelations>(
|
|
994
|
-
|
|
995
|
-
|
|
1107
|
+
function makeQueryRuleScope<Rels extends SchemaRelations, Classes extends SchemaClasses>(
|
|
1108
|
+
classes: SchemaClasses
|
|
1109
|
+
): QueryRuleScope<Rels, Classes> {
|
|
1110
|
+
const raw = makeRawScope({ kind: "query", classes })
|
|
1111
|
+
if (!isTypedScope<QueryRuleScope<Rels, Classes>>(raw)) {
|
|
996
1112
|
throw errors.new("query rule builder construction incomplete")
|
|
997
1113
|
}
|
|
998
1114
|
return raw
|
|
999
1115
|
}
|
|
1000
1116
|
|
|
1001
1117
|
/** Builds one output-rule builder over a program's recs. */
|
|
1002
|
-
function makeOutputRuleScope<Rels extends SchemaRelations
|
|
1003
|
-
|
|
1004
|
-
|
|
1118
|
+
function makeOutputRuleScope<Rels extends SchemaRelations, Classes extends SchemaClasses>(
|
|
1119
|
+
program: ProgramState
|
|
1120
|
+
): OutputRuleScope<Rels, Classes> {
|
|
1121
|
+
const raw = makeRawScope({ kind: "output", program, classes: program.classes })
|
|
1122
|
+
if (!isTypedScope<OutputRuleScope<Rels, Classes>>(raw)) {
|
|
1005
1123
|
throw errors.new("program output rule builder construction incomplete")
|
|
1006
1124
|
}
|
|
1007
1125
|
return raw
|
|
1008
1126
|
}
|
|
1009
1127
|
|
|
1010
|
-
/** One program's build-time registry: its recs in declaration order
|
|
1128
|
+
/** One program's build-time registry: its recs in declaration order (sealed when the output is declared) and the theory's class map. */
|
|
1011
1129
|
interface ProgramState {
|
|
1012
1130
|
readonly recs: RecData[]
|
|
1131
|
+
readonly classes: SchemaClasses
|
|
1013
1132
|
sealed: boolean
|
|
1014
1133
|
}
|
|
1015
1134
|
|
|
@@ -1113,7 +1232,7 @@ function makeRawQuery(theory: AnySchema, recs: readonly RecData[], rules: readon
|
|
|
1113
1232
|
schema: theory,
|
|
1114
1233
|
data,
|
|
1115
1234
|
rule(build) {
|
|
1116
|
-
const built = build(makeRawScope({ kind: "query" }))
|
|
1235
|
+
const built = build(makeRawScope({ kind: "query", classes: theory.classes }))
|
|
1117
1236
|
return makeRawQuery(theory, recs, [...rules, built.rule])
|
|
1118
1237
|
}
|
|
1119
1238
|
}
|
|
@@ -1126,21 +1245,21 @@ function makeRawQuery(theory: AnySchema, recs: readonly RecData[], rules: readon
|
|
|
1126
1245
|
* the checkable fact — the value was assembled over the identical theory —
|
|
1127
1246
|
* is verified before the raw value is admitted at its typed face.
|
|
1128
1247
|
*/
|
|
1129
|
-
function isQueryValue<Rels extends SchemaRelations, Row, P extends ParamsRecord>(
|
|
1130
|
-
theory: Schema<Rels>,
|
|
1248
|
+
function isQueryValue<Rels extends SchemaRelations, Row, P extends ParamsRecord, Classes extends SchemaClasses>(
|
|
1249
|
+
theory: Schema<Rels, Classes>,
|
|
1131
1250
|
value: RawQuery
|
|
1132
|
-
): value is RawQuery & Query<Rels, Row, P> {
|
|
1251
|
+
): value is RawQuery & Query<Rels, Row, P, Classes> {
|
|
1133
1252
|
return value.schema === theory
|
|
1134
1253
|
}
|
|
1135
1254
|
|
|
1136
1255
|
/** Assembles one typed query value (rules already completed). */
|
|
1137
|
-
function makeQuery<Rels extends SchemaRelations, Row, P extends ParamsRecord>(
|
|
1138
|
-
theory: Schema<Rels>,
|
|
1256
|
+
function makeQuery<Rels extends SchemaRelations, Row, P extends ParamsRecord, Classes extends SchemaClasses>(
|
|
1257
|
+
theory: Schema<Rels, Classes>,
|
|
1139
1258
|
recs: readonly RecData[],
|
|
1140
1259
|
rules: readonly RuleData[]
|
|
1141
|
-
): Query<Rels, Row, P> {
|
|
1260
|
+
): Query<Rels, Row, P, Classes> {
|
|
1142
1261
|
const raw = makeRawQuery(theory, recs, rules)
|
|
1143
|
-
if (!isQueryValue<Rels, Row, P>(theory, raw)) {
|
|
1262
|
+
if (!isQueryValue<Rels, Row, P, Classes>(theory, raw)) {
|
|
1144
1263
|
throw errors.new("query value construction incomplete")
|
|
1145
1264
|
}
|
|
1146
1265
|
return raw
|
|
@@ -1149,13 +1268,19 @@ function makeQuery<Rels extends SchemaRelations, Row, P extends ParamsRecord>(
|
|
|
1149
1268
|
/**
|
|
1150
1269
|
* Opens a query over a schema: `query(S).rule(r => ...)`. Each `.rule`
|
|
1151
1270
|
* adds one conjunctive rule; multiple rules are the set union (answers are
|
|
1152
|
-
* SETS — no order or limit exists anywhere; hosts sort).
|
|
1271
|
+
* SETS — no order or limit exists anywhere; hosts sort). The schema's
|
|
1272
|
+
* law-computed class map rides into every rule builder — the join walls
|
|
1273
|
+
* compare class names off it, at the type level and at construction alike.
|
|
1153
1274
|
*/
|
|
1154
|
-
function query<Rels extends SchemaRelations
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1275
|
+
function query<Rels extends SchemaRelations, Classes extends SchemaClasses>(
|
|
1276
|
+
theory: Schema<Rels, Classes>
|
|
1277
|
+
): QueryStart<Rels, Classes> {
|
|
1278
|
+
const start: QueryStart<Rels, Classes> = {
|
|
1279
|
+
rule<RV extends AnyRuleValue>(
|
|
1280
|
+
build: (r: QueryRuleScope<Rels, Classes>) => RV
|
|
1281
|
+
): Query<Rels, RowOf<RV>, ParamsOf<RV>, Classes> {
|
|
1282
|
+
const built = build(makeQueryRuleScope<Rels, Classes>(theory.classes))
|
|
1283
|
+
return makeQuery<Rels, RowOf<RV>, ParamsOf<RV>, Classes>(theory, [], [built.rule])
|
|
1159
1284
|
}
|
|
1160
1285
|
}
|
|
1161
1286
|
Object.freeze(start)
|
|
@@ -1348,7 +1473,13 @@ function paramIdOf(ctx: LowerContext, name: string): number {
|
|
|
1348
1473
|
return id
|
|
1349
1474
|
}
|
|
1350
1475
|
|
|
1351
|
-
/**
|
|
1476
|
+
/**
|
|
1477
|
+
* Lowers one EDB atom (either polarity). A CLOSED owner lowers through the
|
|
1478
|
+
* same edb source — its ordinal is its record-declaration slot exactly like
|
|
1479
|
+
* an ordinary relation's — with field ordinals over the SEALED shape: `id`
|
|
1480
|
+
* at 0, each payload column at its declared index + 1 (`matchFieldsOf`
|
|
1481
|
+
* carries the shift; the lowering golden pins it).
|
|
1482
|
+
*/
|
|
1352
1483
|
function lowerAtom(ctx: LowerContext, atom: AtomData, ids: VarIds): AtomIr {
|
|
1353
1484
|
const member = ctx.theory.relations[atom.relation.name]
|
|
1354
1485
|
if (member !== atom.relation) {
|
|
@@ -1360,8 +1491,9 @@ function lowerAtom(ctx: LowerContext, atom: AtomData, ids: VarIds): AtomIr {
|
|
|
1360
1491
|
if (relationId === undefined) {
|
|
1361
1492
|
throw errors.new(`query lowering: relation ${atom.relation.name} has no ordinal`)
|
|
1362
1493
|
}
|
|
1494
|
+
const ordered = matchFieldsOf(atom.relation)
|
|
1363
1495
|
const bindings: Array<readonly [number, TermIr]> = atom.bindings.map(function lowerBinding(binding) {
|
|
1364
|
-
const ordinal =
|
|
1496
|
+
const ordinal = ordered.findIndex(function byName(candidate) {
|
|
1365
1497
|
return candidate.name === binding.field
|
|
1366
1498
|
})
|
|
1367
1499
|
if (ordinal < 0) {
|
|
@@ -1436,7 +1568,7 @@ function lowerCmpTerm(
|
|
|
1436
1568
|
/** Resolves the anchor a comparison literal tags by: the sibling's field, the measure, or an anchored param. */
|
|
1437
1569
|
function cmpAnchorOf(ctx: LowerContext, rule: RuleData, sibling: CmpTermData): AnyField | "measure" | undefined {
|
|
1438
1570
|
if (sibling.kind === "var") {
|
|
1439
|
-
return rule.varFields[sibling.name]
|
|
1571
|
+
return rule.varFields[sibling.name]?.field
|
|
1440
1572
|
}
|
|
1441
1573
|
if (sibling.kind === "measure") {
|
|
1442
1574
|
return "measure"
|