@bjornpagen/bumbledb 0.15.0 → 0.17.1
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 +33 -49
- package/README.md +3 -3
- package/dist/capacity.d.ts +24 -136
- package/dist/capacity.d.ts.map +1 -1
- package/dist/capacity.js +18 -40
- package/dist/capacity.js.map +1 -1
- package/dist/closed.d.ts +0 -156
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +0 -104
- package/dist/closed.js.map +1 -1
- package/dist/db.d.ts +7 -223
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +147 -396
- package/dist/db.js.map +1 -1
- package/dist/face.d.ts +0 -133
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +0 -33
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +1 -145
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +2 -91
- package/dist/fields.js.map +1 -1
- package/dist/index.d.ts +12 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -13
- package/dist/index.js.map +1 -1
- package/dist/law.d.ts +111 -93
- package/dist/law.d.ts.map +1 -1
- package/dist/law.js +23 -27
- package/dist/law.js.map +1 -1
- package/dist/lower.d.ts +9 -35
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +8 -53
- package/dist/lower.js.map +1 -1
- package/dist/marshal.d.ts +0 -65
- package/dist/marshal.d.ts.map +1 -1
- package/dist/marshal.js +0 -72
- package/dist/marshal.js.map +1 -1
- package/dist/native.d.ts +31 -289
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +15 -64
- package/dist/native.js.map +1 -1
- package/dist/query/atom.d.ts +10 -276
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +1 -96
- package/dist/query/atom.js.map +1 -1
- package/dist/query/find.d.ts +10 -76
- package/dist/query/find.d.ts.map +1 -1
- package/dist/query/find.js +0 -30
- package/dist/query/find.js.map +1 -1
- package/dist/query/lower.d.ts +58 -146
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +19 -256
- package/dist/query/lower.js.map +1 -1
- package/dist/query/parse-ir.d.ts +0 -7
- package/dist/query/parse-ir.d.ts.map +1 -1
- package/dist/query/parse-ir.js +1 -13
- package/dist/query/parse-ir.js.map +1 -1
- package/dist/query/run.d.ts +0 -36
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +0 -44
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +24 -180
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +2 -66
- package/dist/query/scope.js.map +1 -1
- package/dist/relation.d.ts +2 -50
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +2 -37
- package/dist/relation.js.map +1 -1
- package/dist/schema.d.ts +13 -63
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +118 -92
- package/dist/schema.js.map +1 -1
- package/dist/spec.d.ts +1 -140
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js +1 -68
- package/dist/spec.js.map +1 -1
- package/dist/statements.d.ts +6 -137
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +16 -119
- package/dist/statements.js.map +1 -1
- package/package.json +2 -2
- package/src/capacity.ts +26 -140
- package/src/closed.ts +5 -206
- package/src/db.ts +203 -692
- package/src/face.ts +0 -142
- package/src/fields.ts +4 -172
- package/src/index.ts +10 -15
- package/src/law.ts +201 -129
- package/src/lower.ts +8 -53
- package/src/marshal.ts +1 -85
- package/src/native.ts +58 -321
- package/src/query/atom.ts +26 -313
- package/src/query/find.ts +24 -110
- package/src/query/lower.ts +126 -377
- package/src/query/parse-ir.ts +1 -14
- package/src/query/run.ts +0 -45
- package/src/query/scope.ts +25 -186
- package/src/relation.ts +2 -66
- package/src/schema.ts +143 -122
- package/src/spec.ts +1 -160
- package/src/statements.ts +22 -174
package/src/query/lower.ts
CHANGED
|
@@ -1,36 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `query()` and the IR lowering, REFERENCE-IDENTITY edition. A query is
|
|
3
|
-
* built kysely-shaped — variables minted by {@link v} outside the rule and
|
|
4
|
-
* reused by REFERENCE to join:
|
|
5
|
-
*
|
|
6
|
-
* query(S).rule((r) => {
|
|
7
|
-
* const acct = v(Account)
|
|
8
|
-
* const h = v(Holder)
|
|
9
|
-
* return r
|
|
10
|
-
* .match(Account, { id: acct.id, holder: acct.holder })
|
|
11
|
-
* .match(Holder, { id: acct.holder })
|
|
12
|
-
* .where(r.eq(acct.holder, r.param("root")))
|
|
13
|
-
* .find({ account: acct.id, holder: acct.holder })
|
|
14
|
-
* })
|
|
15
|
-
*
|
|
16
|
-
* — and is an INERT value: `Query<Rels, Row, Params>` with `Row` inferred
|
|
17
|
-
* from each rule's `.find` RECORD (its keys ARE the answer columns) and
|
|
18
|
-
* `Params` inferred to be EXACTLY the params the rules use (params are typed
|
|
19
|
-
* BY USE; a param no rule uses never registers). Variable IDENTITY is the
|
|
20
|
-
* object reference: reusing one value across binding positions IS the join,
|
|
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
|
|
23
|
-
* declaration ordinal, variables by dense per-rule first-occurrence ids
|
|
24
|
-
* (keyed on the object REFERENCE — the discipline is unchanged, only the map
|
|
25
|
-
* key moved from name to reference), params by first-use order. Lowering is
|
|
26
|
-
* STABLE — the same query value lowers to deeply-equal IR every time, and
|
|
27
|
-
* two identically-written queries (fresh mints each) lower identically.
|
|
28
|
-
* Construction validates negation safety and boundness (typed by the var's
|
|
29
|
-
* label — object identity is invisible to the type tier, so these are
|
|
30
|
-
* construction-time walls); everything else (types, aggregate
|
|
31
|
-
* rosters, rule caps) is the ENGINE's judge, surfacing at prepare.
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
1
|
import * as errors from "@superbuilders/errors"
|
|
35
2
|
import { sealedFieldsOf } from "#closed.ts"
|
|
36
3
|
import type { AnyField, ClosedRoster } from "#fields.ts"
|
|
@@ -80,97 +47,60 @@ import { allen, and, eq, ge, gt, le, lt, ne, not, or, pointIn } from "#query/ato
|
|
|
80
47
|
import type { CheckFind, CheckRecFind, FindShape, HeadRecordOf, RowOfFind } from "#query/find.ts"
|
|
81
48
|
import { count, max, min, pack, sum } from "#query/find.ts"
|
|
82
49
|
import { parseQueryIr } from "#query/parse-ir.ts"
|
|
83
|
-
import type {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
50
|
+
import type {
|
|
51
|
+
AnyVar,
|
|
52
|
+
ClassedField,
|
|
53
|
+
ExactVars,
|
|
54
|
+
Flatten,
|
|
55
|
+
InferredOf,
|
|
56
|
+
ParamEntry,
|
|
57
|
+
ParamsRecord,
|
|
58
|
+
ShapeOf,
|
|
59
|
+
VarsOf
|
|
93
60
|
} from "#query/scope.ts"
|
|
61
|
+
import { fieldJoins, inferred, isTerm, makeParam, makeSetParam, renderFieldKind, term } from "#query/scope.ts"
|
|
94
62
|
import type { AnySchema, Schema, SchemaRelations } from "#schema.ts"
|
|
95
63
|
|
|
96
|
-
/**
|
|
97
|
-
* The matchable members of a schema's record — ordinary relations AND
|
|
98
|
-
* closed vocabularies (ψ query atoms; the ENGINE decides folding vs virtual
|
|
99
|
-
* image, the SDK lowers pass-through).
|
|
100
|
-
*/
|
|
101
64
|
type QueryRelation<Rels extends SchemaRelations> = Extract<Rels[keyof Rels], MatchOwner>
|
|
102
65
|
|
|
103
|
-
/** Reads an inferred-params carrier off a rec reference or rule value. */
|
|
104
66
|
type ParamsOf<T> = InferredOf<T> extends { readonly params: infer P extends ParamsRecord } ? P : Record<never, never>
|
|
105
67
|
|
|
106
|
-
/** Reads an inferred-row carrier off a rule value or query. */
|
|
107
68
|
type RowOf<T> = InferredOf<T> extends { readonly row: infer R } ? R : never
|
|
108
69
|
|
|
109
|
-
/**
|
|
110
|
-
* A derived table's HEAD signature as classed slots, keyed by column
|
|
111
|
-
* name; `undefined` on values that carry no head.
|
|
112
|
-
*/
|
|
113
70
|
type HeadShape = Readonly<Record<string, ClassedField>> | undefined
|
|
114
71
|
|
|
115
|
-
/**
|
|
116
|
-
* One finished rule as a plain value: the runtime data plus the inferred
|
|
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.
|
|
120
|
-
*/
|
|
121
72
|
interface RuleValue<Row, P extends ParamsRecord, Head extends HeadShape = undefined> {
|
|
122
73
|
readonly rule: RuleData
|
|
123
74
|
readonly [inferred]?: { readonly row: Row; readonly params: P; readonly head: Head }
|
|
124
75
|
}
|
|
125
76
|
|
|
126
|
-
/** Any finished rule value. */
|
|
127
77
|
type AnyRuleValue = RuleValue<unknown, ParamsRecord, HeadShape>
|
|
128
78
|
|
|
129
|
-
/** Reads an inferred-head carrier off a rule value or rec reference. */
|
|
130
79
|
type HeadOf<T> =
|
|
131
80
|
InferredOf<T> extends { readonly head: infer H extends Readonly<Record<string, ClassedField>> } ? H : undefined
|
|
132
81
|
|
|
133
|
-
/** One `.interior(name)` position's judgment: a variable. */
|
|
134
82
|
type InteriorBindingOk<V> = V extends AnyVar ? true : false
|
|
135
83
|
|
|
136
|
-
/**
|
|
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).
|
|
141
|
-
*/
|
|
142
84
|
type CheckInteriorBindings<B> = {
|
|
143
85
|
readonly [K in keyof B]: InteriorBindingOk<B[K]> extends true ? B[K] : never
|
|
144
86
|
}
|
|
145
87
|
|
|
146
|
-
/** One interior-rule builder function. */
|
|
147
88
|
type InteriorBuild<Rels extends SchemaRelations, Classes extends SchemaClasses = SchemaClasses> = (
|
|
148
89
|
r: InteriorRuleScope<Rels, Classes>
|
|
149
90
|
) => AnyRuleValue
|
|
150
91
|
|
|
151
|
-
/** One rec-arm builder function. */
|
|
152
92
|
type RecBuild<Rels extends SchemaRelations, Classes extends SchemaClasses = SchemaClasses> = (
|
|
153
93
|
r: RecRuleScope<Rels, Classes>
|
|
154
94
|
) => AnyRuleValue
|
|
155
95
|
|
|
156
|
-
/** A build function's rule value. */
|
|
157
96
|
type BuiltRule<F> = F extends (r: never) => infer RV ? RV : never
|
|
158
97
|
|
|
159
|
-
/** The intersected params record of a tuple of rule builds. */
|
|
160
98
|
type BuildsParams<Builds extends readonly ((r: never) => AnyRuleValue)[]> = ShapeOf<ParamsOf<BuiltRule<Builds[number]>>>
|
|
161
99
|
|
|
162
|
-
/**
|
|
163
|
-
* The term/predicate/aggregate constructor vocabulary every rule builder
|
|
164
|
-
* carries — pure value builders. Variables are minted by the free {@link v},
|
|
165
|
-
* outside the rule, and reused by reference; `r` no longer mints them.
|
|
166
|
-
*/
|
|
167
100
|
interface TermOps {
|
|
168
|
-
/** Names one scalar parameter: typed by its use; the key of the execute params object. */
|
|
169
101
|
readonly param: typeof makeParam
|
|
170
|
-
|
|
102
|
+
|
|
171
103
|
readonly inSet: typeof makeSetParam
|
|
172
|
-
/** The measure of an interval-typed variable: `|[s, e)| = e − s`, u64. */
|
|
173
|
-
readonly duration: typeof makeDuration
|
|
174
104
|
readonly eq: typeof eq
|
|
175
105
|
readonly ne: typeof ne
|
|
176
106
|
readonly lt: typeof lt
|
|
@@ -189,52 +119,82 @@ interface TermOps {
|
|
|
189
119
|
readonly pack: typeof pack
|
|
190
120
|
}
|
|
191
121
|
|
|
192
|
-
/** The rule builder a `query(S).rule(...)` callback receives (`Classes` — the join judge's authority). */
|
|
193
122
|
interface QueryRuleScope<Rels extends SchemaRelations, Classes extends SchemaClasses = SchemaClasses> extends TermOps {
|
|
194
|
-
/**
|
|
123
|
+
/**
|
|
124
|
+
* The FULL binding: every column of R bound to its own v(R) mint — the
|
|
125
|
+
* identity atom, stated as a signature so it holds for GENERIC R too
|
|
126
|
+
* (VarsOf unifies with itself by identity; the general form's deferred
|
|
127
|
+
* conditionals cannot). The mint invariant — a variable's mint slot IS its
|
|
128
|
+
* position slot, same owner, same column — discharges the join judgment by
|
|
129
|
+
* construction; an all-var record contributes no params, so the chain starts
|
|
130
|
+
* paramless. {@link ExactVars} maps a foreign key to `never`, so an
|
|
131
|
+
* aliased extra-key record falls to the general form's judgment.
|
|
132
|
+
*/
|
|
133
|
+
match<R extends QueryRelation<Rels>, B extends VarsOf<R>>(
|
|
134
|
+
relation: R,
|
|
135
|
+
bindings: B & ExactVars<R, B>
|
|
136
|
+
): QueryRuleChain<Rels, Record<never, never>, Classes>
|
|
137
|
+
|
|
195
138
|
match<R extends QueryRelation<Rels>, const B extends MatchShape<MatchFields<R>>>(
|
|
196
139
|
relation: R,
|
|
197
140
|
bindings: B & CheckBindings<Classes, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>
|
|
198
141
|
): 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
|
-
*/
|
|
142
|
+
|
|
205
143
|
interior<const B extends Readonly<Record<string, AnyVar>>>(
|
|
206
144
|
name: string,
|
|
207
145
|
bindings: B & CheckInteriorBindings<B>
|
|
208
146
|
): QueryRuleChain<Rels, Record<never, never>, Classes>
|
|
209
147
|
}
|
|
210
148
|
|
|
211
|
-
/** The chain of a plain query rule: more atoms, residual predicates, then the head. */
|
|
212
149
|
interface QueryRuleChain<
|
|
213
150
|
Rels extends SchemaRelations,
|
|
214
151
|
P extends ParamsRecord,
|
|
215
152
|
Classes extends SchemaClasses = SchemaClasses
|
|
216
153
|
> {
|
|
217
|
-
/**
|
|
154
|
+
/**
|
|
155
|
+
* The FULL binding: every column of R bound to its own v(R) mint — the
|
|
156
|
+
* identity atom, generic R included. The mint invariant (a variable's mint
|
|
157
|
+
* slot IS its position slot) discharges the join judgment by construction;
|
|
158
|
+
* an all-var record contributes no params — P rides through unchanged.
|
|
159
|
+
* {@link ExactVars} maps a foreign key to `never`, so an aliased
|
|
160
|
+
* extra-key record falls to the general form's judgment.
|
|
161
|
+
*/
|
|
162
|
+
match<R extends QueryRelation<Rels>, B extends VarsOf<R>>(
|
|
163
|
+
relation: R,
|
|
164
|
+
bindings: B & ExactVars<R, B>
|
|
165
|
+
): QueryRuleChain<Rels, P, Classes>
|
|
166
|
+
|
|
218
167
|
match<R extends QueryRelation<Rels>, const B extends MatchShape<MatchFields<R>>>(
|
|
219
168
|
relation: R,
|
|
220
169
|
bindings: B & CheckBindings<Classes, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>
|
|
221
170
|
): QueryRuleChain<Rels, Flatten<P & BindParamsShape<MatchFields<R>, B>>, Classes>
|
|
222
|
-
|
|
171
|
+
|
|
223
172
|
where<const C extends AnyCond>(
|
|
224
173
|
cond: CheckCond<Classes, C> & C
|
|
225
174
|
): QueryRuleChain<Rels, Flatten<P & CondParamsShape<C>>, Classes>
|
|
226
|
-
|
|
175
|
+
|
|
227
176
|
interior<const B extends Readonly<Record<string, AnyVar>>>(
|
|
228
177
|
name: string,
|
|
229
178
|
bindings: B & CheckInteriorBindings<B>
|
|
230
179
|
): QueryRuleChain<Rels, P, Classes>
|
|
231
|
-
|
|
180
|
+
|
|
232
181
|
find<const F extends FindShape>(entries: F & CheckFind<F>): RuleValue<RowOfFind<F>, P>
|
|
233
182
|
}
|
|
234
183
|
|
|
235
|
-
/** The rule builder an `interior("mid", ...)` callback receives. */
|
|
236
184
|
interface InteriorRuleScope<Rels extends SchemaRelations, Classes extends SchemaClasses = SchemaClasses>
|
|
237
185
|
extends TermOps {
|
|
186
|
+
/**
|
|
187
|
+
* The FULL binding: every column of R bound to its own v(R) mint — the
|
|
188
|
+
* identity atom, generic R included. The mint invariant (a variable's mint
|
|
189
|
+
* slot IS its position slot) discharges the join judgment by construction;
|
|
190
|
+
* an all-var record contributes no params, so the chain starts paramless.
|
|
191
|
+
* {@link ExactVars} maps a foreign key to `never`, so an aliased
|
|
192
|
+
* extra-key record falls to the general form's judgment.
|
|
193
|
+
*/
|
|
194
|
+
match<R extends QueryRelation<Rels>, B extends VarsOf<R>>(
|
|
195
|
+
relation: R,
|
|
196
|
+
bindings: B & ExactVars<R, B>
|
|
197
|
+
): InteriorRuleChain<Rels, Record<never, never>, Classes>
|
|
238
198
|
match<R extends QueryRelation<Rels>, const B extends MatchShape<MatchFields<R>>>(
|
|
239
199
|
relation: R,
|
|
240
200
|
bindings: B & CheckBindings<Classes, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>
|
|
@@ -245,12 +205,23 @@ interface InteriorRuleScope<Rels extends SchemaRelations, Classes extends Schema
|
|
|
245
205
|
): InteriorRuleChain<Rels, Record<never, never>, Classes>
|
|
246
206
|
}
|
|
247
207
|
|
|
248
|
-
/** The chain of an interior rule: bound-variable heads only. */
|
|
249
208
|
interface InteriorRuleChain<
|
|
250
209
|
Rels extends SchemaRelations,
|
|
251
210
|
P extends ParamsRecord,
|
|
252
211
|
Classes extends SchemaClasses = SchemaClasses
|
|
253
212
|
> {
|
|
213
|
+
/**
|
|
214
|
+
* The FULL binding: every column of R bound to its own v(R) mint — the
|
|
215
|
+
* identity atom, generic R included. The mint invariant (a variable's mint
|
|
216
|
+
* slot IS its position slot) discharges the join judgment by construction;
|
|
217
|
+
* an all-var record contributes no params — P rides through unchanged.
|
|
218
|
+
* {@link ExactVars} maps a foreign key to `never`, so an aliased
|
|
219
|
+
* extra-key record falls to the general form's judgment.
|
|
220
|
+
*/
|
|
221
|
+
match<R extends QueryRelation<Rels>, B extends VarsOf<R>>(
|
|
222
|
+
relation: R,
|
|
223
|
+
bindings: B & ExactVars<R, B>
|
|
224
|
+
): InteriorRuleChain<Rels, P, Classes>
|
|
254
225
|
match<R extends QueryRelation<Rels>, const B extends MatchShape<MatchFields<R>>>(
|
|
255
226
|
relation: R,
|
|
256
227
|
bindings: B & CheckBindings<Classes, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>
|
|
@@ -265,8 +236,19 @@ interface InteriorRuleChain<
|
|
|
265
236
|
find<const F extends FindShape>(entries: F & CheckRecFind<F>): RuleValue<RowOfFind<F>, P, HeadRecordOf<Classes, F>>
|
|
266
237
|
}
|
|
267
238
|
|
|
268
|
-
/** The rule builder a `.reach("reach", { base, rec })` arm receives. */
|
|
269
239
|
interface RecRuleScope<Rels extends SchemaRelations, Classes extends SchemaClasses = SchemaClasses> extends TermOps {
|
|
240
|
+
/**
|
|
241
|
+
* The FULL binding: every column of R bound to its own v(R) mint — the
|
|
242
|
+
* identity atom, generic R included. The mint invariant (a variable's mint
|
|
243
|
+
* slot IS its position slot) discharges the join judgment by construction;
|
|
244
|
+
* an all-var record contributes no params, so the chain starts paramless.
|
|
245
|
+
* {@link ExactVars} maps a foreign key to `never`, so an aliased
|
|
246
|
+
* extra-key record falls to the general form's judgment.
|
|
247
|
+
*/
|
|
248
|
+
match<R extends QueryRelation<Rels>, B extends VarsOf<R>>(
|
|
249
|
+
relation: R,
|
|
250
|
+
bindings: B & ExactVars<R, B>
|
|
251
|
+
): RecRuleChain<Rels, Record<never, never>, Classes>
|
|
270
252
|
match<R extends QueryRelation<Rels>, const B extends MatchShape<MatchFields<R>>>(
|
|
271
253
|
relation: R,
|
|
272
254
|
bindings: B & CheckBindings<Classes, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>
|
|
@@ -277,17 +259,23 @@ interface RecRuleScope<Rels extends SchemaRelations, Classes extends SchemaClass
|
|
|
277
259
|
): RecRuleChain<Rels, Record<never, never>, Classes>
|
|
278
260
|
}
|
|
279
261
|
|
|
280
|
-
/**
|
|
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.
|
|
285
|
-
*/
|
|
286
262
|
interface RecRuleChain<
|
|
287
263
|
Rels extends SchemaRelations,
|
|
288
264
|
P extends ParamsRecord,
|
|
289
265
|
Classes extends SchemaClasses = SchemaClasses
|
|
290
266
|
> {
|
|
267
|
+
/**
|
|
268
|
+
* The FULL binding: every column of R bound to its own v(R) mint — the
|
|
269
|
+
* identity atom, generic R included. The mint invariant (a variable's mint
|
|
270
|
+
* slot IS its position slot) discharges the join judgment by construction;
|
|
271
|
+
* an all-var record contributes no params — P rides through unchanged.
|
|
272
|
+
* {@link ExactVars} maps a foreign key to `never`, so an aliased
|
|
273
|
+
* extra-key record falls to the general form's judgment.
|
|
274
|
+
*/
|
|
275
|
+
match<R extends QueryRelation<Rels>, B extends VarsOf<R>>(
|
|
276
|
+
relation: R,
|
|
277
|
+
bindings: B & ExactVars<R, B>
|
|
278
|
+
): RecRuleChain<Rels, P, Classes>
|
|
291
279
|
match<R extends QueryRelation<Rels>, const B extends MatchShape<MatchFields<R>>>(
|
|
292
280
|
relation: R,
|
|
293
281
|
bindings: B & CheckBindings<Classes, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>
|
|
@@ -302,38 +290,32 @@ interface RecRuleChain<
|
|
|
302
290
|
find<const F extends FindShape>(entries: F & CheckRecFind<F>): RuleValue<RowOfFind<F>, P, HeadRecordOf<Classes, F>>
|
|
303
291
|
}
|
|
304
292
|
|
|
305
|
-
/** A query's runtime description — everything lowering, the wire marshal, and answer decode read. */
|
|
306
293
|
type QueryData =
|
|
307
294
|
| {
|
|
308
295
|
readonly kind: "cq"
|
|
309
|
-
|
|
296
|
+
|
|
310
297
|
readonly interiors: readonly InteriorData[]
|
|
311
|
-
|
|
298
|
+
|
|
312
299
|
readonly rules: readonly RuleData[]
|
|
313
|
-
|
|
300
|
+
|
|
314
301
|
readonly finds: readonly FindColumn[]
|
|
315
|
-
|
|
302
|
+
|
|
316
303
|
readonly params: readonly ParamEntry[]
|
|
317
304
|
}
|
|
318
305
|
| {
|
|
319
306
|
readonly kind: "reach"
|
|
320
|
-
|
|
307
|
+
|
|
321
308
|
readonly interiors: readonly InteriorData[]
|
|
322
|
-
|
|
309
|
+
|
|
323
310
|
readonly rec: RecData
|
|
324
|
-
|
|
311
|
+
|
|
325
312
|
readonly rules: readonly RuleData[]
|
|
326
|
-
|
|
313
|
+
|
|
327
314
|
readonly finds: readonly FindColumn[]
|
|
328
|
-
|
|
315
|
+
|
|
329
316
|
readonly params: readonly ParamEntry[]
|
|
330
317
|
}
|
|
331
318
|
|
|
332
|
-
/**
|
|
333
|
-
* An inert query value. `Row` is the inferred answer-row object type;
|
|
334
|
-
* `Params` the inferred execute-params object type — exactly the params the
|
|
335
|
-
* rules use. Prepare with `db.prepare(q)`.
|
|
336
|
-
*/
|
|
337
319
|
interface Query<
|
|
338
320
|
Rels extends SchemaRelations,
|
|
339
321
|
Row,
|
|
@@ -342,34 +324,26 @@ interface Query<
|
|
|
342
324
|
> {
|
|
343
325
|
readonly schema: Schema<Rels, Classes>
|
|
344
326
|
readonly data: QueryData
|
|
345
|
-
|
|
327
|
+
|
|
346
328
|
rule<RV extends AnyRuleValue>(
|
|
347
329
|
build: (r: QueryRuleScope<Rels, Classes>) => RV
|
|
348
330
|
): Query<Rels, Row | RowOf<RV>, Flatten<Params & ParamsOf<RV>>, Classes>
|
|
349
|
-
|
|
331
|
+
|
|
350
332
|
interior(name: string, ...builds: never[]): never
|
|
351
|
-
|
|
333
|
+
|
|
352
334
|
reach(name: string, arms: never): never
|
|
353
335
|
readonly [inferred]?: { readonly row: Row; readonly params: Params }
|
|
354
336
|
}
|
|
355
337
|
|
|
356
|
-
/** Any query value as lowering and the runtime consume it. */
|
|
357
338
|
interface AnyQuery {
|
|
358
339
|
readonly schema: AnySchema
|
|
359
340
|
readonly data: QueryData
|
|
360
341
|
}
|
|
361
342
|
|
|
362
|
-
/** Extracts a query value's inferred answer-row type. */
|
|
363
343
|
type QueryRow<Q extends AnyQuery> = RowOf<Q>
|
|
364
344
|
|
|
365
|
-
/** Extracts a query value's inferred execute-params type. */
|
|
366
345
|
type QueryParams<Q extends AnyQuery> = ParamsOf<Q>
|
|
367
346
|
|
|
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
347
|
type QueryStart<
|
|
374
348
|
Rels extends SchemaRelations,
|
|
375
349
|
Classes extends SchemaClasses = SchemaClasses,
|
|
@@ -388,7 +362,6 @@ type QueryStart<
|
|
|
388
362
|
): QueryReachStart<Rels, Classes, Flatten<P & BuildsParams<Base> & BuildsParams<Step>>>
|
|
389
363
|
}
|
|
390
364
|
|
|
391
|
-
/** After `.reach()`: interior/reach are unrepresentable; only `.rule` remains. */
|
|
392
365
|
type QueryReachStart<
|
|
393
366
|
Rels extends SchemaRelations,
|
|
394
367
|
Classes extends SchemaClasses = SchemaClasses,
|
|
@@ -399,11 +372,9 @@ type QueryReachStart<
|
|
|
399
372
|
): Query<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes>
|
|
400
373
|
}
|
|
401
374
|
|
|
402
|
-
/** The frozen constructor vocabulary every rule builder spreads. */
|
|
403
375
|
const termOps: TermOps = Object.freeze({
|
|
404
376
|
param: makeParam,
|
|
405
377
|
inSet: makeSetParam,
|
|
406
|
-
duration: makeDuration,
|
|
407
378
|
eq,
|
|
408
379
|
ne,
|
|
409
380
|
lt,
|
|
@@ -422,21 +393,18 @@ const termOps: TermOps = Object.freeze({
|
|
|
422
393
|
pack
|
|
423
394
|
})
|
|
424
395
|
|
|
425
|
-
/** One rule under construction: immutable — every chain step is a fresh state. Boundness rides the `bound` set of var references. */
|
|
426
396
|
interface RuleBuildState {
|
|
427
397
|
readonly items: readonly RuleItem[]
|
|
428
398
|
readonly bound: ReadonlySet<AnyVar>
|
|
429
399
|
readonly paramUses: readonly ParamUse[]
|
|
430
400
|
}
|
|
431
401
|
|
|
432
|
-
/** The empty rule state. */
|
|
433
402
|
const EMPTY_RULE: RuleBuildState = Object.freeze({
|
|
434
403
|
items: Object.freeze([]),
|
|
435
404
|
bound: new Set<AnyVar>(),
|
|
436
405
|
paramUses: Object.freeze([])
|
|
437
406
|
})
|
|
438
407
|
|
|
439
|
-
/** One resolved bindings record: the atom entries, the variable references it binds, and the params it uses. */
|
|
440
408
|
interface ResolvedBindings {
|
|
441
409
|
readonly atom: AtomData
|
|
442
410
|
readonly vars: readonly AnyVar[]
|
|
@@ -461,11 +429,6 @@ function mintSlotOf(context: ChainContext, ref: AnyVar): ClassedField {
|
|
|
461
429
|
return { field: ref.field, class: context.classes[ref.owner.name]?.[ref.column] }
|
|
462
430
|
}
|
|
463
431
|
|
|
464
|
-
/**
|
|
465
|
-
* Judges one membership ARRAY at a binding position — legal exactly at a
|
|
466
|
-
* CLOSED-reference field, holding ≥ 2 DISTINCT handle names. The returned
|
|
467
|
-
* name is CONTENT-ADDRESSED (vocabulary + the member SET).
|
|
468
|
-
*/
|
|
469
432
|
function membershipSet(
|
|
470
433
|
context: string,
|
|
471
434
|
field: AnyField,
|
|
@@ -502,14 +465,6 @@ function membershipSet(
|
|
|
502
465
|
return { name: `∈ ${roster.name} ${JSON.stringify(key)}`, members: Object.freeze(members) }
|
|
503
466
|
}
|
|
504
467
|
|
|
505
|
-
/**
|
|
506
|
-
* Resolves a bindings record against an atom owner's matchable fields, in
|
|
507
|
-
* the record's written order: terms classify by their runtime tag,
|
|
508
|
-
* everything else is a bare literal. Every VARIABLE binding judges
|
|
509
|
-
* `fieldJoins(mintSlot, positionSlot)` and throws on a class-unequal reuse
|
|
510
|
-
* (the runtime twin of `CheckBindings`); the bound refs are collected for
|
|
511
|
-
* the rule's boundness set.
|
|
512
|
-
*/
|
|
513
468
|
function resolveBindings(
|
|
514
469
|
context: ChainContext,
|
|
515
470
|
label: string,
|
|
@@ -574,10 +529,6 @@ function resolveBindings(
|
|
|
574
529
|
)
|
|
575
530
|
break
|
|
576
531
|
}
|
|
577
|
-
case "duration":
|
|
578
|
-
throw errors.new(
|
|
579
|
-
`${label}.${fieldName}: the measure is not a field-typed value — it lives in comparisons and find entries`
|
|
580
|
-
)
|
|
581
532
|
}
|
|
582
533
|
} else if (Array.isArray(value)) {
|
|
583
534
|
const set = membershipSet(`${label}.${fieldName}`, declared.field, value)
|
|
@@ -599,7 +550,6 @@ function resolveBindings(
|
|
|
599
550
|
return { atom: Object.freeze({ relation, bindings: Object.freeze(entries) }), vars, uses }
|
|
600
551
|
}
|
|
601
552
|
|
|
602
|
-
/** Extends a rule state with one positive atom; the bound variable references accumulate into the boundness set. */
|
|
603
553
|
function advanceMatch(
|
|
604
554
|
context: ChainContext,
|
|
605
555
|
state: RuleBuildState,
|
|
@@ -618,7 +568,6 @@ function advanceMatch(
|
|
|
618
568
|
})
|
|
619
569
|
}
|
|
620
570
|
|
|
621
|
-
/** Resolves one comparison side to its runtime term (variables and the measure ride by reference). */
|
|
622
571
|
function cmpTermDataOf(value: unknown): CmpTermData {
|
|
623
572
|
if (isTerm(value)) {
|
|
624
573
|
switch (value[term]) {
|
|
@@ -628,30 +577,16 @@ function cmpTermDataOf(value: unknown): CmpTermData {
|
|
|
628
577
|
return Object.freeze({ kind: "param" as const, name: value.name })
|
|
629
578
|
case "setParam":
|
|
630
579
|
return Object.freeze({ kind: "setParam" as const, name: value.name })
|
|
631
|
-
case "duration":
|
|
632
|
-
return Object.freeze({ kind: "measure" as const, ref: value.over })
|
|
633
580
|
}
|
|
634
581
|
}
|
|
635
582
|
return Object.freeze({ kind: "literal" as const, value })
|
|
636
583
|
}
|
|
637
584
|
|
|
638
|
-
/**
|
|
639
|
-
* One comparison side's contribution to the param census: a param/set side
|
|
640
|
-
* anchors to its SIBLING — a variable's field descriptor or the measure; an
|
|
641
|
-
* unanchorable use records with no anchor.
|
|
642
|
-
*/
|
|
643
585
|
function sideUses(op: CmpKind, side: CmpTermData, sibling: CmpTermData, uses: ParamUse[]): void {
|
|
644
586
|
if (side.kind !== "param" && side.kind !== "setParam") {
|
|
645
587
|
return
|
|
646
588
|
}
|
|
647
|
-
|
|
648
|
-
if (sibling.kind === "var") {
|
|
649
|
-
anchor = sibling.ref.field
|
|
650
|
-
} else if (sibling.kind === "measure") {
|
|
651
|
-
anchor = "measure"
|
|
652
|
-
} else {
|
|
653
|
-
anchor = undefined
|
|
654
|
-
}
|
|
589
|
+
const anchor = sibling.kind === "var" ? sibling.ref.field : undefined
|
|
655
590
|
uses.push(
|
|
656
591
|
Object.freeze({
|
|
657
592
|
name: side.name,
|
|
@@ -663,7 +598,6 @@ function sideUses(op: CmpKind, side: CmpTermData, sibling: CmpTermData, uses: Pa
|
|
|
663
598
|
)
|
|
664
599
|
}
|
|
665
600
|
|
|
666
|
-
/** Lowers one condition VALUE to its runtime data, recording param uses. */
|
|
667
601
|
function condDataOf(cond: AnyCond, uses: ParamUse[]): CondData {
|
|
668
602
|
if (cond.cond === "cmp") {
|
|
669
603
|
const lhs = cmpTermDataOf(cond.lhs)
|
|
@@ -695,7 +629,6 @@ function condDataOf(cond: AnyCond, uses: ParamUse[]): CondData {
|
|
|
695
629
|
)
|
|
696
630
|
}
|
|
697
631
|
|
|
698
|
-
/** Extends a rule state with one `.where` item (a condition or a negated atom). */
|
|
699
632
|
function advanceWhere(context: ChainContext, state: RuleBuildState, cond: AnyCond): RuleBuildState {
|
|
700
633
|
if (typeof cond !== "object" || cond === null || !("cond" in cond)) {
|
|
701
634
|
throw errors.new("where() takes a comparison, an and()/or() tree, or a negated atom")
|
|
@@ -731,15 +664,6 @@ function advanceWhere(context: ChainContext, state: RuleBuildState, cond: AnyCon
|
|
|
731
664
|
})
|
|
732
665
|
}
|
|
733
666
|
|
|
734
|
-
/**
|
|
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
|
|
738
|
-
* and Edb in one loop), so its variables GROUND: they enter the rule's
|
|
739
|
-
* boundness set, may ride the head, and satisfy negation safety — the
|
|
740
|
-
* interior-only identity projection of a finished table is spellable with no
|
|
741
|
-
* re-grounding join. A NEGATED one binds nothing, only rejects.
|
|
742
|
-
*/
|
|
743
667
|
function advanceInterior(
|
|
744
668
|
state: RuleBuildState,
|
|
745
669
|
target: DerivedTable,
|
|
@@ -771,7 +695,6 @@ function advanceInterior(
|
|
|
771
695
|
})
|
|
772
696
|
}
|
|
773
697
|
|
|
774
|
-
/** Narrows a find entry to an aggregate value. */
|
|
775
698
|
function isAggregateEntry(value: unknown): value is { readonly agg: string; readonly over?: unknown } {
|
|
776
699
|
return typeof value === "object" && value !== null && "agg" in value
|
|
777
700
|
}
|
|
@@ -784,7 +707,6 @@ function asVarTerm(context: string, value: unknown): AnyVar {
|
|
|
784
707
|
throw errors.new(`${context}: expected a variable`)
|
|
785
708
|
}
|
|
786
709
|
|
|
787
|
-
/** Classifies one aggregate find entry into its runtime data (variables ride by reference). */
|
|
788
710
|
function aggDataOf(name: string, entry: { readonly agg: string; readonly over?: unknown }): AggData {
|
|
789
711
|
if (entry.agg === "count") {
|
|
790
712
|
return Object.freeze({ op: "count" as const })
|
|
@@ -797,10 +719,7 @@ function aggDataOf(name: string, entry: { readonly agg: string; readonly over?:
|
|
|
797
719
|
if (isTerm(over) && over[term] === "var") {
|
|
798
720
|
return Object.freeze({ op: "fold" as const, fold: entry.agg, over })
|
|
799
721
|
}
|
|
800
|
-
|
|
801
|
-
return Object.freeze({ op: "fold" as const, fold: entry.agg, over: Object.freeze({ duration: over.over }) })
|
|
802
|
-
}
|
|
803
|
-
throw errors.new(`find ${name} (${entry.agg}): takes a variable or r.duration(v)`)
|
|
722
|
+
throw errors.new(`find ${name} (${entry.agg}): takes a variable`)
|
|
804
723
|
}
|
|
805
724
|
case "pack":
|
|
806
725
|
return Object.freeze({ op: "pack" as const, over: asVarTerm(`find ${name} (pack)`, over) })
|
|
@@ -809,11 +728,6 @@ function aggDataOf(name: string, entry: { readonly agg: string; readonly over?:
|
|
|
809
728
|
}
|
|
810
729
|
}
|
|
811
730
|
|
|
812
|
-
/**
|
|
813
|
-
* Classifies one find entry into its named answer column (the KEY names the
|
|
814
|
-
* column, `count` included). The `slot`/`closed` slices are resolved LATER,
|
|
815
|
-
* at rule completion, where boundness and the mint slots are in hand.
|
|
816
|
-
*/
|
|
817
731
|
function findColumnOf(name: string, entry: unknown): FindColumn {
|
|
818
732
|
if (isTerm(entry)) {
|
|
819
733
|
if (entry[term] === "var") {
|
|
@@ -824,17 +738,7 @@ function findColumnOf(name: string, entry: unknown): FindColumn {
|
|
|
824
738
|
slot: undefined
|
|
825
739
|
})
|
|
826
740
|
}
|
|
827
|
-
|
|
828
|
-
return Object.freeze({
|
|
829
|
-
name,
|
|
830
|
-
entry: Object.freeze({ kind: "measure" as const, over: entry.over }),
|
|
831
|
-
closed: undefined,
|
|
832
|
-
slot: undefined
|
|
833
|
-
})
|
|
834
|
-
}
|
|
835
|
-
throw errors.new(
|
|
836
|
-
`find ${name}: a ${entry[term]} is not projectable — find takes variables, r.duration(v), or aggregates`
|
|
837
|
-
)
|
|
741
|
+
throw errors.new(`find ${name}: a ${entry[term]} is not projectable — find takes variables or aggregates`)
|
|
838
742
|
}
|
|
839
743
|
if (isAggregateEntry(entry)) {
|
|
840
744
|
return Object.freeze({
|
|
@@ -844,32 +748,29 @@ function findColumnOf(name: string, entry: unknown): FindColumn {
|
|
|
844
748
|
slot: undefined
|
|
845
749
|
})
|
|
846
750
|
}
|
|
847
|
-
throw errors.new(`find ${name}: not a find entry — find takes variables
|
|
751
|
+
throw errors.new(`find ${name}: not a find entry — find takes variables or aggregates`)
|
|
848
752
|
}
|
|
849
753
|
|
|
850
754
|
/**
|
|
851
|
-
* The orderable ban's pointed refusal
|
|
755
|
+
* The orderable ban's pointed refusal
|
|
852
756
|
* § orderability): a closed reference is equality-and-membership only.
|
|
853
757
|
*/
|
|
854
758
|
function closedOrderError(context: string, position: string, vocabulary: string): Error {
|
|
855
759
|
return errors.new(
|
|
856
|
-
`${context}: ${position} is a ${vocabulary} reference — declaration order is an accident, not semantics: vocabularies do not order (
|
|
760
|
+
`${context}: ${position} is a ${vocabulary} reference — declaration order is an accident, not semantics: vocabularies do not order (equality, membership, and counting remain)`
|
|
857
761
|
)
|
|
858
762
|
}
|
|
859
763
|
|
|
860
|
-
/** The comparison ops under the orderable ban (order roster + point membership). */
|
|
861
764
|
function isOrderOp(op: CmpKind | "binding"): op is "lt" | "le" | "gt" | "ge" | "pointIn" {
|
|
862
765
|
return op === "lt" || op === "le" || op === "gt" || op === "ge" || op === "pointIn"
|
|
863
766
|
}
|
|
864
767
|
|
|
865
|
-
/** Requires a variable to be bound by a relation atom of the rule (the boundness wall — invisible to the type tier). */
|
|
866
768
|
function assertBound(where: string, bound: ReadonlySet<AnyVar>, ref: AnyVar): void {
|
|
867
769
|
if (!bound.has(ref)) {
|
|
868
770
|
throw errors.new(`${where}: the variable ${ref.label} is not bound by a relation atom of the rule`)
|
|
869
771
|
}
|
|
870
772
|
}
|
|
871
773
|
|
|
872
|
-
/** Requires a variable to be interval-typed (the measure's and pack's domain), off its own descriptor. */
|
|
873
774
|
function assertInterval(where: string, ref: AnyVar): void {
|
|
874
775
|
if (ref.field.kind !== "interval") {
|
|
875
776
|
throw errors.new(
|
|
@@ -878,7 +779,6 @@ function assertInterval(where: string, ref: AnyVar): void {
|
|
|
878
779
|
}
|
|
879
780
|
}
|
|
880
781
|
|
|
881
|
-
/** Requires a variable's own field to be non-closed (the orderable ban's runtime twin). */
|
|
882
782
|
function assertNotClosed(where: string, position: string, ref: AnyVar): void {
|
|
883
783
|
const roster = rosterOf(ref.field)
|
|
884
784
|
if (roster !== undefined) {
|
|
@@ -886,11 +786,12 @@ function assertNotClosed(where: string, position: string, ref: AnyVar): void {
|
|
|
886
786
|
}
|
|
887
787
|
}
|
|
888
788
|
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
789
|
+
function assertNumeric(where: string, position: string, ref: AnyVar): void {
|
|
790
|
+
if (ref.field.kind !== "u64" && ref.field.kind !== "i64") {
|
|
791
|
+
throw errors.new(`${where}: ${position} ${ref.label} is ${ref.field.kind}, not numeric — a fold reads u64/i64 only`)
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
894
795
|
function findColumnSlotOf(context: ChainContext, column: FindColumn): ClassedField | undefined {
|
|
895
796
|
const entry = column.entry
|
|
896
797
|
if (entry.kind === "var") {
|
|
@@ -899,7 +800,6 @@ function findColumnSlotOf(context: ChainContext, column: FindColumn): ClassedFie
|
|
|
899
800
|
return undefined
|
|
900
801
|
}
|
|
901
802
|
|
|
902
|
-
/** Validates one find column's variable references (boundness + the orderable/interval walls, off the var's own field). */
|
|
903
803
|
function validateColumn(context: ChainContext, bound: ReadonlySet<AnyVar>, column: FindColumn): void {
|
|
904
804
|
const where = `${contextLabel(context)} find ${column.name}`
|
|
905
805
|
const entry = column.entry
|
|
@@ -907,23 +807,14 @@ function validateColumn(context: ChainContext, bound: ReadonlySet<AnyVar>, colum
|
|
|
907
807
|
assertBound(where, bound, entry.over)
|
|
908
808
|
return
|
|
909
809
|
}
|
|
910
|
-
if (entry.kind === "measure") {
|
|
911
|
-
assertBound(where, bound, entry.over)
|
|
912
|
-
assertInterval(where, entry.over)
|
|
913
|
-
return
|
|
914
|
-
}
|
|
915
810
|
const agg = entry.agg
|
|
916
811
|
switch (agg.op) {
|
|
917
812
|
case "count":
|
|
918
813
|
return
|
|
919
814
|
case "fold": {
|
|
920
|
-
if ("duration" in agg.over) {
|
|
921
|
-
assertBound(where, bound, agg.over.duration)
|
|
922
|
-
assertInterval(where, agg.over.duration)
|
|
923
|
-
return
|
|
924
|
-
}
|
|
925
815
|
assertBound(where, bound, agg.over)
|
|
926
816
|
assertNotClosed(where, `the ${agg.fold} input`, agg.over)
|
|
817
|
+
assertNumeric(where, `the ${agg.fold} input`, agg.over)
|
|
927
818
|
return
|
|
928
819
|
}
|
|
929
820
|
case "pack":
|
|
@@ -933,11 +824,6 @@ function validateColumn(context: ChainContext, bound: ReadonlySet<AnyVar>, colum
|
|
|
933
824
|
}
|
|
934
825
|
}
|
|
935
826
|
|
|
936
|
-
/**
|
|
937
|
-
* Validates one condition's variable references against the rule's bound
|
|
938
|
-
* set — and, for `eq`/`ne` over two variables, holds the class wall through
|
|
939
|
-
* the mint slots (the unification IS a join; bare pairs only with bare).
|
|
940
|
-
*/
|
|
941
827
|
function validateCond(context: ChainContext, bound: ReadonlySet<AnyVar>, cond: CondData): void {
|
|
942
828
|
const label = contextLabel(context)
|
|
943
829
|
if (cond.kind === "cmp") {
|
|
@@ -949,10 +835,6 @@ function validateCond(context: ChainContext, bound: ReadonlySet<AnyVar>, cond: C
|
|
|
949
835
|
throw closedOrderError(label, `the ${cond.op.kind} side ${side.ref.label}`, roster.name)
|
|
950
836
|
}
|
|
951
837
|
}
|
|
952
|
-
if (side.kind === "measure") {
|
|
953
|
-
assertBound(label, bound, side.ref)
|
|
954
|
-
assertInterval(label, side.ref)
|
|
955
|
-
}
|
|
956
838
|
}
|
|
957
839
|
if ((cond.op.kind === "eq" || cond.op.kind === "ne") && cond.lhs.kind === "var" && cond.rhs.kind === "var") {
|
|
958
840
|
assertBound(label, bound, cond.lhs.ref)
|
|
@@ -972,16 +854,6 @@ function validateCond(context: ChainContext, bound: ReadonlySet<AnyVar>, cond: C
|
|
|
972
854
|
}
|
|
973
855
|
}
|
|
974
856
|
|
|
975
|
-
/**
|
|
976
|
-
* Validates one interior item: every head column of the table is bound exactly
|
|
977
|
-
* once (a missing or extra key is a pointed error) and each variable joins
|
|
978
|
-
* its head column's classed slot. A POSITIVE interior atom GROUNDS its
|
|
979
|
-
* variables (a positive occurrence, exactly the engine's representation),
|
|
980
|
-
* so no boundness precondition exists; a NEGATED one binds nothing — its
|
|
981
|
-
* variables must be positively bound elsewhere in the rule, the same
|
|
982
|
-
* safety rule as EDB negation. When the table's own first rule is in flight
|
|
983
|
-
* (`finds` empty), the completing rule's OWN find columns ARE the head.
|
|
984
|
-
*/
|
|
985
857
|
function validateInterior(
|
|
986
858
|
context: ChainContext,
|
|
987
859
|
bound: ReadonlySet<AnyVar>,
|
|
@@ -1035,12 +907,6 @@ function validateInterior(
|
|
|
1035
907
|
}
|
|
1036
908
|
}
|
|
1037
909
|
|
|
1038
|
-
/**
|
|
1039
|
-
* Completes one rule: enriches the find columns (declaration-order-safe
|
|
1040
|
-
* keys, boundness validated, each column's classed slot and closed slice
|
|
1041
|
-
* resolved), then walks the body walls — negated-atom boundness safety, interior
|
|
1042
|
-
* head pairing, and condition validation.
|
|
1043
|
-
*/
|
|
1044
910
|
function completeRule(context: ChainContext, state: RuleBuildState, rawColumns: readonly FindColumn[]): RuleData {
|
|
1045
911
|
const label = contextLabel(context)
|
|
1046
912
|
if (rawColumns.length === 0) {
|
|
@@ -1072,15 +938,10 @@ function completeRule(context: ChainContext, state: RuleBuildState, rawColumns:
|
|
|
1072
938
|
return Object.freeze({ items: state.items, finds: Object.freeze(columns), paramUses: state.paramUses })
|
|
1073
939
|
}
|
|
1074
940
|
|
|
1075
|
-
/** Builds one typed rule value over completed rule data. */
|
|
1076
941
|
function makeRuleValue<Row, P extends ParamsRecord>(rule: RuleData): RuleValue<Row, P> {
|
|
1077
942
|
return Object.freeze({ rule })
|
|
1078
943
|
}
|
|
1079
944
|
|
|
1080
|
-
/**
|
|
1081
|
-
* The one runtime chain every context shares — non-generic on purpose. The
|
|
1082
|
-
* typed chain interfaces apply at the scope factories' boundaries.
|
|
1083
|
-
*/
|
|
1084
945
|
interface RawChain {
|
|
1085
946
|
match(relation: MatchOwner, bindings: Readonly<Record<string, unknown>>): RawChain
|
|
1086
947
|
where(cond: AnyCond): RawChain
|
|
@@ -1088,18 +949,15 @@ interface RawChain {
|
|
|
1088
949
|
find(entries: Readonly<Record<string, unknown>>): RuleValue<never, never>
|
|
1089
950
|
}
|
|
1090
951
|
|
|
1091
|
-
/** The runtime rule-builder shape beneath every typed scope. */
|
|
1092
952
|
interface RawScope extends TermOps {
|
|
1093
953
|
match(relation: MatchOwner, bindings: Readonly<Record<string, unknown>>): RawChain
|
|
1094
954
|
interior(name: string, bindings: Readonly<Record<string, unknown>>): RawChain
|
|
1095
955
|
}
|
|
1096
956
|
|
|
1097
|
-
/** The declared derived tables a chain may name. */
|
|
1098
957
|
type DerivedEnv =
|
|
1099
958
|
| { readonly interiors: readonly InteriorData[] }
|
|
1100
959
|
| { readonly interiors: readonly InteriorData[]; readonly rec: RecHandle | RecHead | RecData }
|
|
1101
960
|
|
|
1102
|
-
/** Which rule family a chain builds — plus the schema's runtime class map and theory value (the join judge's authority). */
|
|
1103
961
|
type ChainContext = { readonly classes: SchemaClasses; readonly theory: AnySchema } & DerivedEnv &
|
|
1104
962
|
(
|
|
1105
963
|
| { readonly kind: "query" }
|
|
@@ -1108,7 +966,6 @@ type ChainContext = { readonly classes: SchemaClasses; readonly theory: AnySchem
|
|
|
1108
966
|
| { readonly kind: "rec-arm"; readonly self: RecHead }
|
|
1109
967
|
)
|
|
1110
968
|
|
|
1111
|
-
/** The diagnostic label of a chain context. */
|
|
1112
969
|
function contextLabel(context: ChainContext): string {
|
|
1113
970
|
switch (context.kind) {
|
|
1114
971
|
case "query":
|
|
@@ -1122,12 +979,10 @@ function contextLabel(context: ChainContext): string {
|
|
|
1122
979
|
}
|
|
1123
980
|
}
|
|
1124
981
|
|
|
1125
|
-
/** RecHandle is name-only staging; RecHead/RecData carry the sealed finds. */
|
|
1126
982
|
function isRecHead(rec: RecHandle | RecHead | RecData): rec is RecHead {
|
|
1127
983
|
return Array.isArray((rec as RecHead).finds)
|
|
1128
984
|
}
|
|
1129
985
|
|
|
1130
|
-
/** Resolves a derived-table name against the context's visible tables. */
|
|
1131
986
|
function lookupDerived(context: ChainContext, name: string): DerivedTable {
|
|
1132
987
|
const interior = context.interiors.find(function byName(candidate) {
|
|
1133
988
|
return candidate.name === name
|
|
@@ -1156,7 +1011,6 @@ function lookupDerived(context: ChainContext, name: string): DerivedTable {
|
|
|
1156
1011
|
throw errors.new(`${contextLabel(context)}: no derived table named ${name} is in scope`)
|
|
1157
1012
|
}
|
|
1158
1013
|
|
|
1159
|
-
/** Validates and records one interior atom per the context's cut. */
|
|
1160
1014
|
function interiorAdvance(
|
|
1161
1015
|
context: ChainContext,
|
|
1162
1016
|
state: RuleBuildState,
|
|
@@ -1166,11 +1020,6 @@ function interiorAdvance(
|
|
|
1166
1020
|
return advanceInterior(state, lookupDerived(context, name), bindings, "interior")
|
|
1167
1021
|
}
|
|
1168
1022
|
|
|
1169
|
-
/**
|
|
1170
|
-
* Validates and records one NEGATED finished-table atom — main and interior
|
|
1171
|
-
* rules: a finished set is a set. Rec bodies refuse every negation
|
|
1172
|
-
* (`NegationInRec` — self is the wall; EDB / earlier-interior is this-cut).
|
|
1173
|
-
*/
|
|
1174
1023
|
function notInteriorAdvance(
|
|
1175
1024
|
context: ChainContext,
|
|
1176
1025
|
state: RuleBuildState,
|
|
@@ -1185,7 +1034,6 @@ function notInteriorAdvance(
|
|
|
1185
1034
|
return advanceInterior(state, lookupDerived(context, name), bindings, "negatedInterior")
|
|
1186
1035
|
}
|
|
1187
1036
|
|
|
1188
|
-
/** Classifies one find record per the context (interior and rec heads project bound variables only). */
|
|
1189
1037
|
function findColumns(context: ChainContext, entries: Readonly<Record<string, unknown>>): FindColumn[] {
|
|
1190
1038
|
const columns: FindColumn[] = []
|
|
1191
1039
|
const derivedHead = context.kind !== "query"
|
|
@@ -1204,7 +1052,6 @@ function findColumns(context: ChainContext, entries: Readonly<Record<string, unk
|
|
|
1204
1052
|
return columns
|
|
1205
1053
|
}
|
|
1206
1054
|
|
|
1207
|
-
/** Builds one runtime chain (immutably — every step is a fresh chain over fresh state). */
|
|
1208
1055
|
function makeRawChain(context: ChainContext, state: RuleBuildState): RawChain {
|
|
1209
1056
|
const chain: RawChain = {
|
|
1210
1057
|
match(relation, bindings) {
|
|
@@ -1224,7 +1071,6 @@ function makeRawChain(context: ChainContext, state: RuleBuildState): RawChain {
|
|
|
1224
1071
|
return chain
|
|
1225
1072
|
}
|
|
1226
1073
|
|
|
1227
|
-
/** Builds one runtime rule-builder over a context. */
|
|
1228
1074
|
function makeRawScope(context: ChainContext): RawScope {
|
|
1229
1075
|
const scope: RawScope = {
|
|
1230
1076
|
...termOps,
|
|
@@ -1252,7 +1098,6 @@ function isTypedScope<S>(scope: RawScope): scope is RawScope & S {
|
|
|
1252
1098
|
return typeof scope.match === "function"
|
|
1253
1099
|
}
|
|
1254
1100
|
|
|
1255
|
-
/** Builds one query-rule builder (the typed face of the raw builder). */
|
|
1256
1101
|
function makeQueryRuleScope<Rels extends SchemaRelations, Classes extends SchemaClasses>(
|
|
1257
1102
|
theory: AnySchema,
|
|
1258
1103
|
env: DerivedEnv
|
|
@@ -1264,7 +1109,6 @@ function makeQueryRuleScope<Rels extends SchemaRelations, Classes extends Schema
|
|
|
1264
1109
|
return raw
|
|
1265
1110
|
}
|
|
1266
1111
|
|
|
1267
|
-
/** Builds one interior-rule builder. */
|
|
1268
1112
|
function makeInteriorRuleScope<Rels extends SchemaRelations, Classes extends SchemaClasses>(
|
|
1269
1113
|
theory: AnySchema,
|
|
1270
1114
|
env: DerivedEnv,
|
|
@@ -1277,7 +1121,6 @@ function makeInteriorRuleScope<Rels extends SchemaRelations, Classes extends Sch
|
|
|
1277
1121
|
return raw
|
|
1278
1122
|
}
|
|
1279
1123
|
|
|
1280
|
-
/** Builds one rec-arm builder. */
|
|
1281
1124
|
function makeRecRuleScope<Rels extends SchemaRelations, Classes extends SchemaClasses>(
|
|
1282
1125
|
theory: AnySchema,
|
|
1283
1126
|
env: DerivedEnv,
|
|
@@ -1318,15 +1161,13 @@ function makeRecRuleScope<Rels extends SchemaRelations, Classes extends SchemaCl
|
|
|
1318
1161
|
return raw
|
|
1319
1162
|
}
|
|
1320
1163
|
|
|
1321
|
-
/** Renders one head column's closed slice for the rule-alignment check's diagnostics. */
|
|
1322
1164
|
function renderClosedSlice(closed: ClosedRoster | undefined): string {
|
|
1323
1165
|
return closed === undefined ? "a bare value" : `a ${closed.name} reference`
|
|
1324
1166
|
}
|
|
1325
1167
|
|
|
1326
|
-
/** Renders one head column's signature for the rule-alignment check. */
|
|
1327
1168
|
function headSignature(column: FindColumn): string {
|
|
1328
1169
|
const entry = column.entry
|
|
1329
|
-
if (entry.kind === "var"
|
|
1170
|
+
if (entry.kind === "var") {
|
|
1330
1171
|
return `${column.name}:var`
|
|
1331
1172
|
}
|
|
1332
1173
|
const agg = entry.agg
|
|
@@ -1336,12 +1177,6 @@ function headSignature(column: FindColumn): string {
|
|
|
1336
1177
|
return `${column.name}:${agg.op}`
|
|
1337
1178
|
}
|
|
1338
1179
|
|
|
1339
|
-
/** The roster a param anchor carries: present exactly on a closed-reference field anchor. */
|
|
1340
|
-
function anchorRosterOf(anchor: AnyField | "measure" | undefined): ClosedRoster | undefined {
|
|
1341
|
-
return anchor === "measure" ? undefined : rosterOf(anchor)
|
|
1342
|
-
}
|
|
1343
|
-
|
|
1344
|
-
/** Renders one param anchor's closedness for the registry's coherence diagnostics. */
|
|
1345
1180
|
function renderParamAnchor(roster: ClosedRoster | undefined): string {
|
|
1346
1181
|
return roster === undefined ? "a non-closed position" : `a ${roster.name} reference`
|
|
1347
1182
|
}
|
|
@@ -1397,8 +1232,8 @@ function paramRegistryOf(
|
|
|
1397
1232
|
)
|
|
1398
1233
|
}
|
|
1399
1234
|
if (existing.anchor !== undefined && use.anchor !== undefined) {
|
|
1400
|
-
const registered =
|
|
1401
|
-
const anchored =
|
|
1235
|
+
const registered = rosterOf(existing.anchor)
|
|
1236
|
+
const anchored = rosterOf(use.anchor)
|
|
1402
1237
|
if (registered !== anchored) {
|
|
1403
1238
|
throw errors.new(
|
|
1404
1239
|
`query param ${use.name} is anchored at ${renderParamAnchor(registered)} and at ${renderParamAnchor(anchored)} — a closed-anchored param translates handle names through ONE roster (one name, one domain); name the params differently`
|
|
@@ -1433,14 +1268,7 @@ function paramRegistryOf(
|
|
|
1433
1268
|
if (entry === undefined) {
|
|
1434
1269
|
throw errors.new(`query param ${name} lost its registry entry`)
|
|
1435
1270
|
}
|
|
1436
|
-
|
|
1437
|
-
* A membership array's handle names are program constants, so the
|
|
1438
|
-
* entry stores the resolved IMAGE: each name rides the one
|
|
1439
|
-
* roster-verification point (`taggedHandleId`, through
|
|
1440
|
-
* `taggedCmpLiteral`) exactly once, HERE — an out-of-roster name
|
|
1441
|
-
* fails at build, and every execute returns this frozen value by
|
|
1442
|
-
* reference.
|
|
1443
|
-
*/
|
|
1271
|
+
|
|
1444
1272
|
let membership: QueryParam | undefined
|
|
1445
1273
|
if (entry.members !== undefined) {
|
|
1446
1274
|
const anchor = entry.anchor
|
|
@@ -1461,7 +1289,6 @@ function paramRegistryOf(
|
|
|
1461
1289
|
)
|
|
1462
1290
|
}
|
|
1463
1291
|
|
|
1464
|
-
/** The runtime query shape beneath the typed `Query` face. */
|
|
1465
1292
|
interface RawQuery {
|
|
1466
1293
|
readonly schema: AnySchema
|
|
1467
1294
|
readonly data: QueryData
|
|
@@ -1470,7 +1297,6 @@ interface RawQuery {
|
|
|
1470
1297
|
reach(name: string, arms: never): never
|
|
1471
1298
|
}
|
|
1472
1299
|
|
|
1473
|
-
/** Asserts every rule in a list derives the same head (name, aggregate shape, closed slice, class). */
|
|
1474
1300
|
function assertAlignedHeads(label: string, rules: readonly RuleData[]): void {
|
|
1475
1301
|
const first = rules[0]
|
|
1476
1302
|
if (first === undefined) {
|
|
@@ -1509,13 +1335,6 @@ function afterMainError(what: string): Error {
|
|
|
1509
1335
|
)
|
|
1510
1336
|
}
|
|
1511
1337
|
|
|
1512
|
-
/**
|
|
1513
|
-
* Assembles the runtime query value over completed rules: every rule must
|
|
1514
|
-
* derive the SAME head (name and aggregate shape, position for position —
|
|
1515
|
-
* the decode labels and the engine's alignment rule agree), and the param
|
|
1516
|
-
* registry folds in query-walk order. CQ lowering does not mention rec;
|
|
1517
|
-
* Reach carries RecData by value.
|
|
1518
|
-
*/
|
|
1519
1338
|
function makeRawQuery(
|
|
1520
1339
|
theory: AnySchema,
|
|
1521
1340
|
interiors: readonly InteriorData[],
|
|
@@ -1575,7 +1394,6 @@ function makeQuery<Rels extends SchemaRelations, Row, P extends ParamsRecord, Cl
|
|
|
1575
1394
|
return makeRawQuery(theory, interiors, rec, rules) as unknown as Query<Rels, Row, P, Classes>
|
|
1576
1395
|
}
|
|
1577
1396
|
|
|
1578
|
-
/** Collects one Interior from its builders. */
|
|
1579
1397
|
function collectInterior<Rels extends SchemaRelations, Classes extends SchemaClasses>(
|
|
1580
1398
|
theory: Schema<Rels, Classes>,
|
|
1581
1399
|
env: DerivedEnv,
|
|
@@ -1596,7 +1414,6 @@ function collectInterior<Rels extends SchemaRelations, Classes extends SchemaCla
|
|
|
1596
1414
|
return Object.freeze({ name, finds: first.finds, rules: Object.freeze(rules) })
|
|
1597
1415
|
}
|
|
1598
1416
|
|
|
1599
|
-
/** Collects the Rec from tagged base/rec builder arrays. */
|
|
1600
1417
|
function collectRec<Rels extends SchemaRelations, Classes extends SchemaClasses>(
|
|
1601
1418
|
theory: Schema<Rels, Classes>,
|
|
1602
1419
|
interiors: readonly InteriorData[],
|
|
@@ -1646,7 +1463,6 @@ function collectRec<Rels extends SchemaRelations, Classes extends SchemaClasses>
|
|
|
1646
1463
|
return recData
|
|
1647
1464
|
}
|
|
1648
1465
|
|
|
1649
|
-
/** Builds the CQ query start (interiors, then reach or the first main rule). */
|
|
1650
1466
|
function makeQueryStart<Rels extends SchemaRelations, Classes extends SchemaClasses, P extends ParamsRecord>(
|
|
1651
1467
|
theory: Schema<Rels, Classes>,
|
|
1652
1468
|
interiors: readonly InteriorData[]
|
|
@@ -1702,7 +1518,6 @@ function makeQueryStart<Rels extends SchemaRelations, Classes extends SchemaClas
|
|
|
1702
1518
|
return start as unknown as QueryStart<Rels, Classes, P>
|
|
1703
1519
|
}
|
|
1704
1520
|
|
|
1705
|
-
/** Builds the Reach query start (rec sealed; only the first main rule remains). */
|
|
1706
1521
|
function makeQueryReachStart<Rels extends SchemaRelations, Classes extends SchemaClasses, P extends ParamsRecord>(
|
|
1707
1522
|
theory: Schema<Rels, Classes>,
|
|
1708
1523
|
interiors: readonly InteriorData[],
|
|
@@ -1721,24 +1536,12 @@ function makeQueryReachStart<Rels extends SchemaRelations, Classes extends Schem
|
|
|
1721
1536
|
return start as unknown as QueryReachStart<Rels, Classes, P>
|
|
1722
1537
|
}
|
|
1723
1538
|
|
|
1724
|
-
/**
|
|
1725
|
-
* Opens a query over a schema: `query(S).rule(r => ...)`, optionally with
|
|
1726
|
-
* `interior` / `reach` first. Each `.rule` adds one conjunctive rule;
|
|
1727
|
-
* multiple rules are the set union. The schema's law-computed class map and
|
|
1728
|
-
* theory value ride into every rule builder — the join walls compare
|
|
1729
|
-
* against the mint slots off it.
|
|
1730
|
-
*/
|
|
1731
1539
|
function query<Rels extends SchemaRelations, Classes extends SchemaClasses>(
|
|
1732
1540
|
theory: Schema<Rels, Classes>
|
|
1733
1541
|
): QueryStart<Rels, Classes> {
|
|
1734
1542
|
return makeQueryStart<Rels, Classes, Record<never, never>>(theory, [])
|
|
1735
1543
|
}
|
|
1736
1544
|
|
|
1737
|
-
/**
|
|
1738
|
-
* Tags one closed-reference literal: the handle NAME, verified against the
|
|
1739
|
-
* roster and translated to its declaration-order row id, tagged u64. THE
|
|
1740
|
-
* single roster-verification point of the query surface.
|
|
1741
|
-
*/
|
|
1742
1545
|
function taggedHandleId(
|
|
1743
1546
|
context: string,
|
|
1744
1547
|
closed: { readonly name: string; readonly handles: readonly string[] },
|
|
@@ -1756,11 +1559,6 @@ function taggedHandleId(
|
|
|
1756
1559
|
return { kind: "u64", value: BigInt(id) }
|
|
1757
1560
|
}
|
|
1758
1561
|
|
|
1759
|
-
/**
|
|
1760
|
-
* Tags one literal in an interval element domain: a bigint tags as the
|
|
1761
|
-
* element (the membership typing rule's point side), an interval-shaped
|
|
1762
|
-
* value as the interval (value equality).
|
|
1763
|
-
*/
|
|
1764
1562
|
function taggedAtElementDomain(context: string, element: "u64" | "i64", value: unknown): TaggedValue {
|
|
1765
1563
|
if (typeof value === "bigint") {
|
|
1766
1564
|
if (element === "u64") {
|
|
@@ -1777,10 +1575,6 @@ function taggedAtElementDomain(context: string, element: "u64" | "i64", value: u
|
|
|
1777
1575
|
throw literalShapeError(context, "bigint (point) or { start, end } (interval)", value)
|
|
1778
1576
|
}
|
|
1779
1577
|
|
|
1780
|
-
/**
|
|
1781
|
-
* Tags one host literal at a FIELD position (atom bindings): the field's
|
|
1782
|
-
* structural kind directs the tag, never a guess.
|
|
1783
|
-
*/
|
|
1784
1578
|
function taggedLiteral(context: string, field: AnyField, value: unknown): TaggedValue {
|
|
1785
1579
|
const roster = rosterOf(field)
|
|
1786
1580
|
if (roster !== undefined) {
|
|
@@ -1834,18 +1628,7 @@ function taggedLiteral(context: string, field: AnyField, value: unknown): Tagged
|
|
|
1834
1628
|
* interval operand of `pointIn(t, span(...))`; under every other operator an
|
|
1835
1629
|
* interval shape against a scalar sibling stays refused.
|
|
1836
1630
|
*/
|
|
1837
|
-
function taggedCmpLiteral(
|
|
1838
|
-
context: string,
|
|
1839
|
-
sibling: AnyField | "measure",
|
|
1840
|
-
value: unknown,
|
|
1841
|
-
op: CmpKind | "binding"
|
|
1842
|
-
): TaggedValue {
|
|
1843
|
-
if (sibling === "measure") {
|
|
1844
|
-
if (typeof value !== "bigint") {
|
|
1845
|
-
throw literalShapeError(context, "bigint (the measure is u64)", value)
|
|
1846
|
-
}
|
|
1847
|
-
return { kind: "u64", value }
|
|
1848
|
-
}
|
|
1631
|
+
function taggedCmpLiteral(context: string, sibling: AnyField, value: unknown, op: CmpKind | "binding"): TaggedValue {
|
|
1849
1632
|
if (rosterOf(sibling) === undefined && sibling.kind === "interval") {
|
|
1850
1633
|
return taggedAtElementDomain(context, sibling.element, value)
|
|
1851
1634
|
}
|
|
@@ -1860,7 +1643,6 @@ function taggedCmpLiteral(
|
|
|
1860
1643
|
return taggedLiteral(context, sibling, value)
|
|
1861
1644
|
}
|
|
1862
1645
|
|
|
1863
|
-
/** The shared lowering context of one `lowerQuery` run. */
|
|
1864
1646
|
interface LowerContext {
|
|
1865
1647
|
readonly theory: AnySchema
|
|
1866
1648
|
readonly relationIds: ReadonlyMap<string, number>
|
|
@@ -1869,12 +1651,10 @@ interface LowerContext {
|
|
|
1869
1651
|
readonly params: ReadonlyMap<string, ParamEntry>
|
|
1870
1652
|
}
|
|
1871
1653
|
|
|
1872
|
-
/** One rule's dense variable numbering: first occurrence in written order, keyed on the object REFERENCE. */
|
|
1873
1654
|
interface VarIds {
|
|
1874
1655
|
of(ref: AnyVar): number
|
|
1875
1656
|
}
|
|
1876
1657
|
|
|
1877
|
-
/** Creates one rule-scoped variable numberer. */
|
|
1878
1658
|
function freshVarIds(): VarIds {
|
|
1879
1659
|
const assigned = new Map<AnyVar, number>()
|
|
1880
1660
|
return {
|
|
@@ -1890,7 +1670,6 @@ function freshVarIds(): VarIds {
|
|
|
1890
1670
|
}
|
|
1891
1671
|
}
|
|
1892
1672
|
|
|
1893
|
-
/** Resolves a param name to its dense positional id. */
|
|
1894
1673
|
function paramIdOf(ctx: LowerContext, name: string): number {
|
|
1895
1674
|
const id = ctx.paramIds.get(name)
|
|
1896
1675
|
if (id === undefined) {
|
|
@@ -1899,10 +1678,6 @@ function paramIdOf(ctx: LowerContext, name: string): number {
|
|
|
1899
1678
|
return id
|
|
1900
1679
|
}
|
|
1901
1680
|
|
|
1902
|
-
/**
|
|
1903
|
-
* Lowers one EDB atom (either polarity). A CLOSED owner lowers through the
|
|
1904
|
-
* same edb source, with field ordinals over the SEALED shape.
|
|
1905
|
-
*/
|
|
1906
1681
|
function lowerAtom(ctx: LowerContext, atom: AtomData, ids: VarIds): AtomIr {
|
|
1907
1682
|
const member = ctx.theory.relations[atom.relation.name]
|
|
1908
1683
|
if (member !== atom.relation) {
|
|
@@ -1927,7 +1702,6 @@ function lowerAtom(ctx: LowerContext, atom: AtomData, ids: VarIds): AtomIr {
|
|
|
1927
1702
|
return { source: { kind: "edb", relation: relationId }, bindings }
|
|
1928
1703
|
}
|
|
1929
1704
|
|
|
1930
|
-
/** Lowers one binding term. A membership ARRAY lowers to the existing param-set term over its content-addressed entry. */
|
|
1931
1705
|
function lowerBindingTerm(ctx: LowerContext, context: string, binding: BindingEntry, ids: VarIds): TermIr {
|
|
1932
1706
|
const bound = binding.term
|
|
1933
1707
|
switch (bound.kind) {
|
|
@@ -1975,7 +1749,6 @@ function lowerInteriorAtom(
|
|
|
1975
1749
|
return { source: { kind: "interior", interior }, bindings: irBindings }
|
|
1976
1750
|
}
|
|
1977
1751
|
|
|
1978
|
-
/** Lowers one comparison side; literals tag by the sibling's anchor (op-aware at `pointIn`). */
|
|
1979
1752
|
function lowerCmpTerm(ctx: LowerContext, side: CmpTermData, sibling: CmpTermData, ids: VarIds, op: CmpKind): TermIr {
|
|
1980
1753
|
switch (side.kind) {
|
|
1981
1754
|
case "var":
|
|
@@ -1984,13 +1757,11 @@ function lowerCmpTerm(ctx: LowerContext, side: CmpTermData, sibling: CmpTermData
|
|
|
1984
1757
|
return { kind: "param", param: paramIdOf(ctx, side.name) }
|
|
1985
1758
|
case "setParam":
|
|
1986
1759
|
return { kind: "paramSet", param: paramIdOf(ctx, side.name) }
|
|
1987
|
-
case "measure":
|
|
1988
|
-
return { kind: "measure", var: ids.of(side.ref) }
|
|
1989
1760
|
case "literal": {
|
|
1990
1761
|
const anchor = cmpAnchorOf(ctx, sibling)
|
|
1991
1762
|
if (anchor === undefined) {
|
|
1992
1763
|
throw errors.new(
|
|
1993
|
-
"query lowering: a comparison literal needs a bound-variable
|
|
1764
|
+
"query lowering: a comparison literal needs a bound-variable or anchored-param sibling to type it"
|
|
1994
1765
|
)
|
|
1995
1766
|
}
|
|
1996
1767
|
return { kind: "literal", value: taggedCmpLiteral("comparison literal", anchor, side.value, op) }
|
|
@@ -1998,21 +1769,16 @@ function lowerCmpTerm(ctx: LowerContext, side: CmpTermData, sibling: CmpTermData
|
|
|
1998
1769
|
}
|
|
1999
1770
|
}
|
|
2000
1771
|
|
|
2001
|
-
|
|
2002
|
-
function cmpAnchorOf(ctx: LowerContext, sibling: CmpTermData): AnyField | "measure" | undefined {
|
|
1772
|
+
function cmpAnchorOf(ctx: LowerContext, sibling: CmpTermData): AnyField | undefined {
|
|
2003
1773
|
if (sibling.kind === "var") {
|
|
2004
1774
|
return sibling.ref.field
|
|
2005
1775
|
}
|
|
2006
|
-
if (sibling.kind === "measure") {
|
|
2007
|
-
return "measure"
|
|
2008
|
-
}
|
|
2009
1776
|
if (sibling.kind === "param" || sibling.kind === "setParam") {
|
|
2010
1777
|
return ctx.params.get(sibling.name)?.anchor
|
|
2011
1778
|
}
|
|
2012
1779
|
return undefined
|
|
2013
1780
|
}
|
|
2014
1781
|
|
|
2015
|
-
/** Lowers one comparison. */
|
|
2016
1782
|
function lowerComparison(ctx: LowerContext, cmp: CmpData, ids: VarIds): ComparisonIr {
|
|
2017
1783
|
if (cmp.op.kind === "allen") {
|
|
2018
1784
|
return {
|
|
@@ -2028,7 +1794,6 @@ function lowerComparison(ctx: LowerContext, cmp: CmpData, ids: VarIds): Comparis
|
|
|
2028
1794
|
}
|
|
2029
1795
|
}
|
|
2030
1796
|
|
|
2031
|
-
/** Lowers one condition node (comparison leaf or and/or tree). */
|
|
2032
1797
|
function lowerCondition(ctx: LowerContext, cond: CondData, ids: VarIds): ConditionTreeIr {
|
|
2033
1798
|
if (cond.kind === "cmp") {
|
|
2034
1799
|
return { kind: "leaf", cmp: lowerComparison(ctx, cond, ids) }
|
|
@@ -2041,30 +1806,21 @@ function lowerCondition(ctx: LowerContext, cond: CondData, ids: VarIds): Conditi
|
|
|
2041
1806
|
}
|
|
2042
1807
|
}
|
|
2043
1808
|
|
|
2044
|
-
/** Lowers one find entry to its per-rule find term. */
|
|
2045
1809
|
function lowerFind(entry: FindEntryData, ids: VarIds): FindTermIr {
|
|
2046
1810
|
if (entry.kind === "var") {
|
|
2047
1811
|
return { kind: "var", var: ids.of(entry.over) }
|
|
2048
1812
|
}
|
|
2049
|
-
if (entry.kind === "measure") {
|
|
2050
|
-
return { kind: "measure", var: ids.of(entry.over) }
|
|
2051
|
-
}
|
|
2052
1813
|
const agg = entry.agg
|
|
2053
1814
|
switch (agg.op) {
|
|
2054
1815
|
case "count":
|
|
2055
1816
|
return { kind: "count" }
|
|
2056
|
-
case "fold":
|
|
2057
|
-
if ("duration" in agg.over) {
|
|
2058
|
-
return { kind: "aggregateMeasure", op: { kind: agg.fold }, over: ids.of(agg.over.duration) }
|
|
2059
|
-
}
|
|
1817
|
+
case "fold":
|
|
2060
1818
|
return { kind: "aggregate", op: { kind: agg.fold }, over: ids.of(agg.over) }
|
|
2061
|
-
}
|
|
2062
1819
|
case "pack":
|
|
2063
1820
|
return { kind: "pack", over: ids.of(agg.over) }
|
|
2064
1821
|
}
|
|
2065
1822
|
}
|
|
2066
1823
|
|
|
2067
|
-
/** One aggregate's var-free head-op kind (`AggOp::head_op`). */
|
|
2068
1824
|
function headOpOf(agg: AggData): HeadOpIr {
|
|
2069
1825
|
switch (agg.op) {
|
|
2070
1826
|
case "count":
|
|
@@ -2076,16 +1832,14 @@ function headOpOf(agg: AggData): HeadOpIr {
|
|
|
2076
1832
|
}
|
|
2077
1833
|
}
|
|
2078
1834
|
|
|
2079
|
-
/** One find entry's var-free head shape. */
|
|
2080
1835
|
function headTermOf(column: FindColumn): HeadTermIr {
|
|
2081
1836
|
const entry = column.entry
|
|
2082
|
-
if (entry.kind === "var"
|
|
1837
|
+
if (entry.kind === "var") {
|
|
2083
1838
|
return { kind: "var" }
|
|
2084
1839
|
}
|
|
2085
1840
|
return { kind: "aggregate", op: headOpOf(entry.agg) }
|
|
2086
1841
|
}
|
|
2087
1842
|
|
|
2088
|
-
/** Lowers one rule: body walked in written order (var ids by first occurrence), finds last. */
|
|
2089
1843
|
function lowerRule(ctx: LowerContext, rule: RuleData): RuleIr {
|
|
2090
1844
|
const ids = freshVarIds()
|
|
2091
1845
|
const atoms: AtomIr[] = []
|
|
@@ -2125,11 +1879,6 @@ function lowerRule(ctx: LowerContext, rule: RuleData): RuleIr {
|
|
|
2125
1879
|
}
|
|
2126
1880
|
}
|
|
2127
1881
|
|
|
2128
|
-
/**
|
|
2129
|
-
* Lowers a query value to the bridge's `QueryIr` — pure and stable:
|
|
2130
|
-
* interiors in declaration order, then CQ or Reach, then main. Every
|
|
2131
|
-
* registered param must carry a field anchor by now.
|
|
2132
|
-
*/
|
|
2133
1882
|
function lowerQuery(q: AnyQuery): ParsedQuery {
|
|
2134
1883
|
const theory = q.schema
|
|
2135
1884
|
const relationIds = new Map<string, number>()
|