@bjornpagen/bumbledb 0.11.0 → 0.12.2
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 +4 -4
- package/README.md +4 -4
- package/dist/capacity.d.ts +2 -2
- package/dist/closed.d.ts +4 -4
- package/dist/db.d.ts +69 -44
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +20 -6
- package/dist/db.js.map +1 -1
- package/dist/exhume.d.ts +2 -2
- package/dist/face.d.ts +3 -3
- package/dist/fields.d.ts +1 -1
- package/dist/index.d.ts +34 -34
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/law.d.ts +5 -5
- package/dist/lower.d.ts +5 -5
- package/dist/marshal.d.ts +3 -3
- package/dist/native.d.ts +13 -6
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js.map +1 -1
- package/dist/query/atom.d.ts +5 -5
- package/dist/query/find.d.ts +4 -4
- package/dist/query/lower.d.ts +53 -40
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +102 -73
- package/dist/query/lower.js.map +1 -1
- package/dist/query/parse-ir.d.ts +5 -5
- package/dist/query/parse-ir.js +5 -5
- package/dist/query/parse-ir.js.map +1 -1
- package/dist/query/run.d.ts +3 -3
- package/dist/query/scope.d.ts +5 -5
- package/dist/relation.d.ts +14 -4
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js.map +1 -1
- package/dist/schema.d.ts +4 -4
- package/dist/statements.d.ts +4 -4
- package/package.json +4 -3
- package/src/db.ts +125 -64
- package/src/index.ts +8 -2
- package/src/native.ts +17 -8
- package/src/query/atom.ts +1 -1
- package/src/query/lower.ts +180 -138
- package/src/query/parse-ir.ts +5 -5
- package/src/relation.ts +15 -2
package/src/query/lower.ts
CHANGED
|
@@ -114,7 +114,7 @@ type HeadShape = Readonly<Record<string, ClassedField>> | undefined
|
|
|
114
114
|
|
|
115
115
|
/**
|
|
116
116
|
* One finished rule as a plain value: the runtime data plus the inferred
|
|
117
|
-
* row/params carrier (and, for an interior or
|
|
117
|
+
* row/params carrier (and, for an interior or rec rule, the head
|
|
118
118
|
* record of classed slots an `.interior(name)` join pairs against).
|
|
119
119
|
* `.rule(...)` consumes it.
|
|
120
120
|
*/
|
|
@@ -265,7 +265,7 @@ interface InteriorRuleChain<
|
|
|
265
265
|
find<const F extends FindShape>(entries: F & CheckRecFind<F>): RuleValue<RowOfFind<F>, P, HeadRecordOf<Classes, F>>
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
-
/** The rule builder a
|
|
268
|
+
/** The rule builder a `.reach("reach", { base, rec })` arm receives. */
|
|
269
269
|
interface RecRuleScope<Rels extends SchemaRelations, Classes extends SchemaClasses = SchemaClasses> extends TermOps {
|
|
270
270
|
match<R extends QueryRelation<Rels>, const B extends MatchShape<MatchFields<R>>>(
|
|
271
271
|
relation: R,
|
|
@@ -278,10 +278,10 @@ interface RecRuleScope<Rels extends SchemaRelations, Classes extends SchemaClass
|
|
|
278
278
|
}
|
|
279
279
|
|
|
280
280
|
/**
|
|
281
|
-
* The chain of a
|
|
281
|
+
* The chain of a rec arm. `.interior("reach", …)` is the self-atom
|
|
282
282
|
* on rec arms (and a prior interior on either list). `find` takes bound
|
|
283
283
|
* variables only — aggregates and the measure are unrepresentable in a
|
|
284
|
-
*
|
|
284
|
+
* rec head.
|
|
285
285
|
*/
|
|
286
286
|
interface RecRuleChain<
|
|
287
287
|
Rels extends SchemaRelations,
|
|
@@ -303,18 +303,31 @@ interface RecRuleChain<
|
|
|
303
303
|
}
|
|
304
304
|
|
|
305
305
|
/** A query's runtime description — everything lowering, the wire marshal, and answer decode read. */
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
306
|
+
type QueryData =
|
|
307
|
+
| {
|
|
308
|
+
readonly kind: "cq"
|
|
309
|
+
/** Named interiors in declaration order (DAG). */
|
|
310
|
+
readonly interiors: readonly InteriorData[]
|
|
311
|
+
/** The main rules in written order (multiple rules = set union). */
|
|
312
|
+
readonly rules: readonly RuleData[]
|
|
313
|
+
/** The head columns (every rule derives the same head; written order = answer column order). */
|
|
314
|
+
readonly finds: readonly FindColumn[]
|
|
315
|
+
/** The registered params in first-use order across the query walk (= dense `ParamId`s). */
|
|
316
|
+
readonly params: readonly ParamEntry[]
|
|
317
|
+
}
|
|
318
|
+
| {
|
|
319
|
+
readonly kind: "reach"
|
|
320
|
+
/** Named interiors in declaration order (DAG). */
|
|
321
|
+
readonly interiors: readonly InteriorData[]
|
|
322
|
+
/** The linear rec (base and rec arms nonempty by type). */
|
|
323
|
+
readonly rec: RecData
|
|
324
|
+
/** The main rules in written order (multiple rules = set union). */
|
|
325
|
+
readonly rules: readonly RuleData[]
|
|
326
|
+
/** The head columns (every rule derives the same head; written order = answer column order). */
|
|
327
|
+
readonly finds: readonly FindColumn[]
|
|
328
|
+
/** The registered params in first-use order across the query walk (= dense `ParamId`s). */
|
|
329
|
+
readonly params: readonly ParamEntry[]
|
|
330
|
+
}
|
|
318
331
|
|
|
319
332
|
/**
|
|
320
333
|
* An inert query value. `Row` is the inferred answer-row object type;
|
|
@@ -335,8 +348,8 @@ interface Query<
|
|
|
335
348
|
): Query<Rels, Row | RowOf<RV>, Flatten<Params & ParamsOf<RV>>, Classes>
|
|
336
349
|
/** Construction error: interiors precede main rules. Uncallable after `.rule()`. */
|
|
337
350
|
interior(name: string, ...builds: never[]): never
|
|
338
|
-
/** Construction error:
|
|
339
|
-
|
|
351
|
+
/** Construction error: reach precedes main rules. Uncallable after `.rule()`. */
|
|
352
|
+
reach(name: string, arms: never): never
|
|
340
353
|
readonly [inferred]?: { readonly row: Row; readonly params: Params }
|
|
341
354
|
}
|
|
342
355
|
|
|
@@ -353,37 +366,38 @@ type QueryRow<Q extends AnyQuery> = RowOf<Q>
|
|
|
353
366
|
type QueryParams<Q extends AnyQuery> = ParamsOf<Q>
|
|
354
367
|
|
|
355
368
|
/**
|
|
356
|
-
* The entry value of `query(S)`: interiors, then
|
|
357
|
-
* the first `.rule` mints the query.
|
|
358
|
-
*
|
|
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.
|
|
359
372
|
*/
|
|
360
373
|
type QueryStart<
|
|
361
374
|
Rels extends SchemaRelations,
|
|
362
375
|
Classes extends SchemaClasses = SchemaClasses,
|
|
363
|
-
P extends ParamsRecord = Record<never, never
|
|
364
|
-
Rec extends RecData | null = null
|
|
376
|
+
P extends ParamsRecord = Record<never, never>
|
|
365
377
|
> = {
|
|
366
378
|
rule<RV extends AnyRuleValue>(
|
|
367
379
|
build: (r: QueryRuleScope<Rels, Classes>) => RV
|
|
368
380
|
): Query<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes>
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
381
|
+
interior<const Builds extends readonly InteriorBuild<Rels, Classes>[]>(
|
|
382
|
+
name: string,
|
|
383
|
+
...builds: Builds
|
|
384
|
+
): QueryStart<Rels, Classes, Flatten<P & BuildsParams<Builds>>>
|
|
385
|
+
reach<const Base extends readonly RecBuild<Rels, Classes>[], const Step extends readonly RecBuild<Rels, Classes>[]>(
|
|
386
|
+
name: string,
|
|
387
|
+
arms: { readonly base: Base; readonly rec: Step }
|
|
388
|
+
): QueryReachStart<Rels, Classes, Flatten<P & BuildsParams<Base> & BuildsParams<Step>>>
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** After `.reach()`: interior/reach are unrepresentable; only `.rule` remains. */
|
|
392
|
+
type QueryReachStart<
|
|
393
|
+
Rels extends SchemaRelations,
|
|
394
|
+
Classes extends SchemaClasses = SchemaClasses,
|
|
395
|
+
P extends ParamsRecord = Record<never, never>
|
|
396
|
+
> = {
|
|
397
|
+
rule<RV extends AnyRuleValue>(
|
|
398
|
+
build: (r: QueryRuleScope<Rels, Classes>) => RV
|
|
399
|
+
): Query<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes>
|
|
400
|
+
}
|
|
387
401
|
|
|
388
402
|
/** The frozen constructor vocabulary every rule builder spreads. */
|
|
389
403
|
const termOps: TermOps = Object.freeze({
|
|
@@ -1080,10 +1094,9 @@ interface RawScope extends TermOps {
|
|
|
1080
1094
|
}
|
|
1081
1095
|
|
|
1082
1096
|
/** The declared derived tables a chain may name. */
|
|
1083
|
-
|
|
1084
|
-
readonly interiors: readonly InteriorData[]
|
|
1085
|
-
readonly rec: RecHandle | RecHead | RecData
|
|
1086
|
-
}
|
|
1097
|
+
type DerivedEnv =
|
|
1098
|
+
| { readonly interiors: readonly InteriorData[] }
|
|
1099
|
+
| { readonly interiors: readonly InteriorData[]; readonly rec: RecHandle | RecHead | RecData }
|
|
1087
1100
|
|
|
1088
1101
|
/** Which rule family a chain builds — plus the schema's runtime class map and theory value (the join judge's authority). */
|
|
1089
1102
|
type ChainContext = { readonly classes: SchemaClasses; readonly theory: AnySchema } & DerivedEnv &
|
|
@@ -1102,12 +1115,17 @@ function contextLabel(context: ChainContext): string {
|
|
|
1102
1115
|
case "interior":
|
|
1103
1116
|
return `interior ${context.self} rule`
|
|
1104
1117
|
case "rec-base":
|
|
1105
|
-
return `
|
|
1118
|
+
return `rec ${context.self.name} base`
|
|
1106
1119
|
case "rec-arm":
|
|
1107
|
-
return `
|
|
1120
|
+
return `rec ${context.self.name} rec`
|
|
1108
1121
|
}
|
|
1109
1122
|
}
|
|
1110
1123
|
|
|
1124
|
+
/** RecHandle is name-only staging; RecHead/RecData carry the sealed finds. */
|
|
1125
|
+
function isRecHead(rec: RecHandle | RecHead | RecData): rec is RecHead {
|
|
1126
|
+
return Array.isArray((rec as RecHead).finds)
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1111
1129
|
/** Resolves a derived-table name against the context's visible tables. */
|
|
1112
1130
|
function lookupDerived(context: ChainContext, name: string): DerivedTable {
|
|
1113
1131
|
const interior = context.interiors.find(function byName(candidate) {
|
|
@@ -1121,19 +1139,18 @@ function lookupDerived(context: ChainContext, name: string): DerivedTable {
|
|
|
1121
1139
|
}
|
|
1122
1140
|
return interior
|
|
1123
1141
|
}
|
|
1124
|
-
|
|
1142
|
+
const rec = "rec" in context ? context.rec : undefined
|
|
1143
|
+
if (rec !== undefined && rec.name === name) {
|
|
1125
1144
|
if (context.kind === "interior") {
|
|
1126
1145
|
throw errors.new(`interior ${context.self}: interiors cannot read the rec — this cut's interiors are a prefix`)
|
|
1127
1146
|
}
|
|
1128
1147
|
if (context.kind === "rec-base") {
|
|
1129
|
-
throw errors.new(
|
|
1130
|
-
`recursive ${context.rec.name}: a base arm does not read the rec — self-atoms belong on rec arms`
|
|
1131
|
-
)
|
|
1148
|
+
throw errors.new(`rec ${rec.name}: a base arm does not read the rec — self-atoms belong on rec arms`)
|
|
1132
1149
|
}
|
|
1133
|
-
if (!(
|
|
1134
|
-
throw errors.new(`
|
|
1150
|
+
if (!isRecHead(rec)) {
|
|
1151
|
+
throw errors.new(`rec ${rec.name}: rec arms resolve the rec head after base arms seal it`)
|
|
1135
1152
|
}
|
|
1136
|
-
return
|
|
1153
|
+
return rec
|
|
1137
1154
|
}
|
|
1138
1155
|
throw errors.new(`${contextLabel(context)}: no derived table named ${name} is in scope`)
|
|
1139
1156
|
}
|
|
@@ -1161,7 +1178,7 @@ function notInteriorAdvance(
|
|
|
1161
1178
|
): RuleBuildState {
|
|
1162
1179
|
if (context.kind === "rec-base" || context.kind === "rec-arm") {
|
|
1163
1180
|
throw errors.new(
|
|
1164
|
-
`
|
|
1181
|
+
`rec ${context.self.name}: a rec rule negates no table — self-negation is negation through the cycle (a finished set is what keeps the operator monotone), and a finished table's fold belongs in the main rules`
|
|
1165
1182
|
)
|
|
1166
1183
|
}
|
|
1167
1184
|
return advanceInterior(state, lookupDerived(context, name), bindings, "negatedInterior")
|
|
@@ -1176,9 +1193,9 @@ function findColumns(context: ChainContext, entries: Readonly<Record<string, unk
|
|
|
1176
1193
|
continue
|
|
1177
1194
|
}
|
|
1178
1195
|
if (derivedHead && !(isTerm(entry) && entry[term] === "var")) {
|
|
1179
|
-
const who = context.kind === "interior" ? `interior ${context.self}` : `
|
|
1196
|
+
const who = context.kind === "interior" ? `interior ${context.self}` : `rec ${context.self.name}`
|
|
1180
1197
|
throw errors.new(
|
|
1181
|
-
`${who}: a
|
|
1198
|
+
`${who}: a rec head projects bound variables only — aggregates and the measure read finished sets (unwritable here)`
|
|
1182
1199
|
)
|
|
1183
1200
|
}
|
|
1184
1201
|
columns.push(findColumnOf(name, entry))
|
|
@@ -1295,7 +1312,7 @@ function makeRecRuleScope<Rels extends SchemaRelations, Classes extends SchemaCl
|
|
|
1295
1312
|
...env
|
|
1296
1313
|
})
|
|
1297
1314
|
if (!isTypedScope<RecRuleScope<Rels, Classes>>(raw)) {
|
|
1298
|
-
throw errors.new("
|
|
1315
|
+
throw errors.new("rec rule builder construction incomplete")
|
|
1299
1316
|
}
|
|
1300
1317
|
return raw
|
|
1301
1318
|
}
|
|
@@ -1342,7 +1359,7 @@ function renderParamAnchor(roster: ClosedRoster | undefined): string {
|
|
|
1342
1359
|
*/
|
|
1343
1360
|
function paramRegistryOf(
|
|
1344
1361
|
interiors: readonly InteriorData[],
|
|
1345
|
-
rec: RecData |
|
|
1362
|
+
rec: RecData | undefined,
|
|
1346
1363
|
rules: readonly RuleData[]
|
|
1347
1364
|
): readonly ParamEntry[] {
|
|
1348
1365
|
const order: string[] = []
|
|
@@ -1398,7 +1415,7 @@ function paramRegistryOf(
|
|
|
1398
1415
|
fold(rule.paramUses)
|
|
1399
1416
|
}
|
|
1400
1417
|
}
|
|
1401
|
-
if (rec !==
|
|
1418
|
+
if (rec !== undefined) {
|
|
1402
1419
|
for (const rule of rec.base) {
|
|
1403
1420
|
fold(rule.paramUses)
|
|
1404
1421
|
}
|
|
@@ -1449,7 +1466,7 @@ interface RawQuery {
|
|
|
1449
1466
|
readonly data: QueryData
|
|
1450
1467
|
rule(build: (r: RawScope) => RuleValue<never, never>): RawQuery
|
|
1451
1468
|
interior(name: string, ...builds: never[]): never
|
|
1452
|
-
|
|
1469
|
+
reach(name: string, arms: never): never
|
|
1453
1470
|
}
|
|
1454
1471
|
|
|
1455
1472
|
/** Asserts every rule in a list derives the same head (name, aggregate shape, closed slice, class). */
|
|
@@ -1495,12 +1512,13 @@ function afterMainError(what: string): Error {
|
|
|
1495
1512
|
* Assembles the runtime query value over completed rules: every rule must
|
|
1496
1513
|
* derive the SAME head (name and aggregate shape, position for position —
|
|
1497
1514
|
* the decode labels and the engine's alignment rule agree), and the param
|
|
1498
|
-
* registry folds in query-walk order.
|
|
1515
|
+
* registry folds in query-walk order. CQ lowering does not mention rec;
|
|
1516
|
+
* Reach carries RecData by value.
|
|
1499
1517
|
*/
|
|
1500
1518
|
function makeRawQuery(
|
|
1501
1519
|
theory: AnySchema,
|
|
1502
1520
|
interiors: readonly InteriorData[],
|
|
1503
|
-
rec: RecData |
|
|
1521
|
+
rec: RecData | undefined,
|
|
1504
1522
|
rules: readonly RuleData[]
|
|
1505
1523
|
): RawQuery {
|
|
1506
1524
|
assertAlignedHeads("a query", rules)
|
|
@@ -1508,14 +1526,27 @@ function makeRawQuery(
|
|
|
1508
1526
|
if (first === undefined) {
|
|
1509
1527
|
throw errors.new("a query needs at least one rule")
|
|
1510
1528
|
}
|
|
1511
|
-
const env: DerivedEnv = { interiors, rec }
|
|
1512
|
-
const
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1529
|
+
const env: DerivedEnv = rec === undefined ? { interiors } : { interiors, rec }
|
|
1530
|
+
const frozenInteriors = Object.freeze([...interiors])
|
|
1531
|
+
const frozenRules = Object.freeze([...rules])
|
|
1532
|
+
const params = paramRegistryOf(interiors, rec, rules)
|
|
1533
|
+
const data: QueryData =
|
|
1534
|
+
rec === undefined
|
|
1535
|
+
? Object.freeze({
|
|
1536
|
+
kind: "cq" as const,
|
|
1537
|
+
interiors: frozenInteriors,
|
|
1538
|
+
rules: frozenRules,
|
|
1539
|
+
finds: first.finds,
|
|
1540
|
+
params
|
|
1541
|
+
})
|
|
1542
|
+
: Object.freeze({
|
|
1543
|
+
kind: "reach" as const,
|
|
1544
|
+
interiors: frozenInteriors,
|
|
1545
|
+
rec,
|
|
1546
|
+
rules: frozenRules,
|
|
1547
|
+
finds: first.finds,
|
|
1548
|
+
params
|
|
1549
|
+
})
|
|
1519
1550
|
const value: RawQuery = {
|
|
1520
1551
|
schema: theory,
|
|
1521
1552
|
data,
|
|
@@ -1526,8 +1557,8 @@ function makeRawQuery(
|
|
|
1526
1557
|
interior() {
|
|
1527
1558
|
throw afterMainError("interior")
|
|
1528
1559
|
},
|
|
1529
|
-
|
|
1530
|
-
throw afterMainError("
|
|
1560
|
+
reach() {
|
|
1561
|
+
throw afterMainError("reach")
|
|
1531
1562
|
}
|
|
1532
1563
|
}
|
|
1533
1564
|
Object.freeze(value)
|
|
@@ -1537,7 +1568,7 @@ function makeRawQuery(
|
|
|
1537
1568
|
function makeQuery<Rels extends SchemaRelations, Row, P extends ParamsRecord, Classes extends SchemaClasses>(
|
|
1538
1569
|
theory: Schema<Rels, Classes>,
|
|
1539
1570
|
interiors: readonly InteriorData[],
|
|
1540
|
-
rec: RecData |
|
|
1571
|
+
rec: RecData | undefined,
|
|
1541
1572
|
rules: readonly RuleData[]
|
|
1542
1573
|
): Query<Rels, Row, P, Classes> {
|
|
1543
1574
|
return makeRawQuery(theory, interiors, rec, rules) as unknown as Query<Rels, Row, P, Classes>
|
|
@@ -1573,24 +1604,24 @@ function collectRec<Rels extends SchemaRelations, Classes extends SchemaClasses>
|
|
|
1573
1604
|
recBuilds: readonly RecBuild<Rels, Classes>[]
|
|
1574
1605
|
): RecData {
|
|
1575
1606
|
if (baseBuilds.length === 0) {
|
|
1576
|
-
throw errors.new(`query:
|
|
1607
|
+
throw errors.new(`query: rec ${name} has no base arms`)
|
|
1577
1608
|
}
|
|
1578
1609
|
if (recBuilds.length === 0) {
|
|
1579
|
-
throw errors.new(`query:
|
|
1610
|
+
throw errors.new(`query: rec ${name} has no rec arms`)
|
|
1580
1611
|
}
|
|
1581
1612
|
const handle: RecHandle = Object.freeze({ name })
|
|
1582
1613
|
const baseEnv: DerivedEnv = { interiors, rec: handle }
|
|
1583
1614
|
const base = baseBuilds.map(function buildBase(buildOne) {
|
|
1584
1615
|
return buildOne(makeRecRuleScope<Rels, Classes>(theory, baseEnv, handle, "rec-base")).rule
|
|
1585
1616
|
})
|
|
1586
|
-
assertAlignedHeads(`
|
|
1617
|
+
assertAlignedHeads(`rec ${name}`, base)
|
|
1587
1618
|
const first = base[0]
|
|
1588
1619
|
if (first === undefined) {
|
|
1589
|
-
throw errors.new(`query:
|
|
1620
|
+
throw errors.new(`query: rec ${name} has no base arms`)
|
|
1590
1621
|
}
|
|
1591
1622
|
const firstFind = first.finds[0]
|
|
1592
1623
|
if (firstFind === undefined) {
|
|
1593
|
-
throw errors.new(`query:
|
|
1624
|
+
throw errors.new(`query: rec ${name} has no head`)
|
|
1594
1625
|
}
|
|
1595
1626
|
const finds: RecHead["finds"] = [firstFind, ...first.finds.slice(1)]
|
|
1596
1627
|
const head: RecHead = Object.freeze({ name, finds })
|
|
@@ -1598,10 +1629,10 @@ function collectRec<Rels extends SchemaRelations, Classes extends SchemaClasses>
|
|
|
1598
1629
|
const rec = recBuilds.map(function buildRec(buildOne) {
|
|
1599
1630
|
return buildOne(makeRecRuleScope<Rels, Classes>(theory, recEnv, head, "rec-arm")).rule
|
|
1600
1631
|
})
|
|
1601
|
-
assertAlignedHeads(`
|
|
1632
|
+
assertAlignedHeads(`rec ${name}`, [...base, ...rec])
|
|
1602
1633
|
const firstRec = rec[0]
|
|
1603
1634
|
if (firstRec === undefined) {
|
|
1604
|
-
throw errors.new(`query:
|
|
1635
|
+
throw errors.new(`query: rec ${name} has no rec arms`)
|
|
1605
1636
|
}
|
|
1606
1637
|
const sealedBase: RecData["base"] = [first, ...base.slice(1)]
|
|
1607
1638
|
const sealedRec: RecData["rec"] = [firstRec, ...rec.slice(1)]
|
|
@@ -1614,24 +1645,17 @@ function collectRec<Rels extends SchemaRelations, Classes extends SchemaClasses>
|
|
|
1614
1645
|
return recData
|
|
1615
1646
|
}
|
|
1616
1647
|
|
|
1617
|
-
/** Builds the query start (interiors, then
|
|
1618
|
-
function makeQueryStart<
|
|
1619
|
-
Rels
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
>(theory: Schema<Rels, Classes>, interiors: readonly InteriorData[], rec: Rec): QueryStart<Rels, Classes, P, Rec> {
|
|
1624
|
-
const env: DerivedEnv = { interiors, rec }
|
|
1648
|
+
/** Builds the CQ query start (interiors, then reach or the first main rule). */
|
|
1649
|
+
function makeQueryStart<Rels extends SchemaRelations, Classes extends SchemaClasses, P extends ParamsRecord>(
|
|
1650
|
+
theory: Schema<Rels, Classes>,
|
|
1651
|
+
interiors: readonly InteriorData[]
|
|
1652
|
+
): QueryStart<Rels, Classes, P> {
|
|
1653
|
+
const env: DerivedEnv = { interiors }
|
|
1625
1654
|
const start = {
|
|
1626
1655
|
interior<const Builds extends readonly InteriorBuild<Rels, Classes>[]>(
|
|
1627
1656
|
name: string,
|
|
1628
1657
|
...builds: Builds
|
|
1629
|
-
): QueryStart<Rels, Classes, Flatten<P & BuildsParams<Builds
|
|
1630
|
-
if (rec !== null) {
|
|
1631
|
-
throw errors.new(
|
|
1632
|
-
"query: interior after recursive is unwritable — declaration order is interiors, then rec, then main"
|
|
1633
|
-
)
|
|
1634
|
-
}
|
|
1658
|
+
): QueryStart<Rels, Classes, Flatten<P & BuildsParams<Builds>>> {
|
|
1635
1659
|
if (
|
|
1636
1660
|
interiors.some(function sameName(interior) {
|
|
1637
1661
|
return interior.name === name
|
|
@@ -1643,35 +1667,48 @@ function makeQueryStart<
|
|
|
1643
1667
|
throw errors.new("query: an interior needs a name")
|
|
1644
1668
|
}
|
|
1645
1669
|
const data = collectInterior(theory, env, name, builds)
|
|
1646
|
-
return makeQueryStart<Rels, Classes, Flatten<P & BuildsParams<Builds
|
|
1670
|
+
return makeQueryStart<Rels, Classes, Flatten<P & BuildsParams<Builds>>>(theory, [...interiors, data])
|
|
1647
1671
|
},
|
|
1648
|
-
|
|
1649
|
-
const Base extends readonly RecBuild<Rels, Classes>[],
|
|
1650
|
-
const Step extends readonly RecBuild<Rels, Classes>[]
|
|
1651
|
-
>(
|
|
1672
|
+
reach<const Base extends readonly RecBuild<Rels, Classes>[], const Step extends readonly RecBuild<Rels, Classes>[]>(
|
|
1652
1673
|
name: string,
|
|
1653
1674
|
arms: { readonly base: Base; readonly rec: Step }
|
|
1654
|
-
):
|
|
1655
|
-
if (rec !== null) {
|
|
1656
|
-
throw errors.new("query: a second recursive is unwritable — this cut admits one linear rec")
|
|
1657
|
-
}
|
|
1675
|
+
): QueryReachStart<Rels, Classes, Flatten<P & BuildsParams<Base> & BuildsParams<Step>>> {
|
|
1658
1676
|
if (
|
|
1659
1677
|
interiors.some(function sameName(interior) {
|
|
1660
1678
|
return interior.name === name
|
|
1661
1679
|
})
|
|
1662
1680
|
) {
|
|
1663
|
-
throw errors.new(`query: interior and
|
|
1681
|
+
throw errors.new(`query: interior and rec share the name ${name}`)
|
|
1664
1682
|
}
|
|
1665
1683
|
if (name.length === 0) {
|
|
1666
|
-
throw errors.new("query:
|
|
1684
|
+
throw errors.new("query: reach needs a name")
|
|
1667
1685
|
}
|
|
1668
1686
|
const data = collectRec(theory, interiors, name, arms.base, arms.rec)
|
|
1669
|
-
return
|
|
1687
|
+
return makeQueryReachStart<Rels, Classes, Flatten<P & BuildsParams<Base> & BuildsParams<Step>>>(
|
|
1670
1688
|
theory,
|
|
1671
1689
|
interiors,
|
|
1672
1690
|
data
|
|
1673
1691
|
)
|
|
1674
1692
|
},
|
|
1693
|
+
rule<RV extends AnyRuleValue>(
|
|
1694
|
+
build: (r: QueryRuleScope<Rels, Classes>) => RV
|
|
1695
|
+
): Query<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes> {
|
|
1696
|
+
const built = build(makeQueryRuleScope<Rels, Classes>(theory, env))
|
|
1697
|
+
return makeQuery<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes>(theory, interiors, undefined, [built.rule])
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
Object.freeze(start)
|
|
1701
|
+
return start as unknown as QueryStart<Rels, Classes, P>
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
/** Builds the Reach query start (rec sealed; only the first main rule remains). */
|
|
1705
|
+
function makeQueryReachStart<Rels extends SchemaRelations, Classes extends SchemaClasses, P extends ParamsRecord>(
|
|
1706
|
+
theory: Schema<Rels, Classes>,
|
|
1707
|
+
interiors: readonly InteriorData[],
|
|
1708
|
+
rec: RecData
|
|
1709
|
+
): QueryReachStart<Rels, Classes, P> {
|
|
1710
|
+
const env: DerivedEnv = { interiors, rec }
|
|
1711
|
+
const start = {
|
|
1675
1712
|
rule<RV extends AnyRuleValue>(
|
|
1676
1713
|
build: (r: QueryRuleScope<Rels, Classes>) => RV
|
|
1677
1714
|
): Query<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes> {
|
|
@@ -1680,12 +1717,12 @@ function makeQueryStart<
|
|
|
1680
1717
|
}
|
|
1681
1718
|
}
|
|
1682
1719
|
Object.freeze(start)
|
|
1683
|
-
return start as unknown as
|
|
1720
|
+
return start as unknown as QueryReachStart<Rels, Classes, P>
|
|
1684
1721
|
}
|
|
1685
1722
|
|
|
1686
1723
|
/**
|
|
1687
1724
|
* Opens a query over a schema: `query(S).rule(r => ...)`, optionally with
|
|
1688
|
-
* `interior` / `
|
|
1725
|
+
* `interior` / `reach` first. Each `.rule` adds one conjunctive rule;
|
|
1689
1726
|
* multiple rules are the set union. The schema's law-computed class map and
|
|
1690
1727
|
* theory value ride into every rule builder — the join walls compare
|
|
1691
1728
|
* against the mint slots off it.
|
|
@@ -1693,7 +1730,7 @@ function makeQueryStart<
|
|
|
1693
1730
|
function query<Rels extends SchemaRelations, Classes extends SchemaClasses>(
|
|
1694
1731
|
theory: Schema<Rels, Classes>
|
|
1695
1732
|
): QueryStart<Rels, Classes> {
|
|
1696
|
-
return makeQueryStart<Rels, Classes, Record<never, never
|
|
1733
|
+
return makeQueryStart<Rels, Classes, Record<never, never>>(theory, [])
|
|
1697
1734
|
}
|
|
1698
1735
|
|
|
1699
1736
|
/**
|
|
@@ -2088,9 +2125,9 @@ function lowerRule(ctx: LowerContext, rule: RuleData): RuleIr {
|
|
|
2088
2125
|
}
|
|
2089
2126
|
|
|
2090
2127
|
/**
|
|
2091
|
-
* Lowers a query value to the bridge's `QueryIr` — pure and stable:
|
|
2092
|
-
* in declaration order,
|
|
2093
|
-
* carry a field anchor by now.
|
|
2128
|
+
* Lowers a query value to the bridge's `QueryIr` — pure and stable:
|
|
2129
|
+
* interiors in declaration order, then CQ or Reach, then main. Every
|
|
2130
|
+
* registered param must carry a field anchor by now.
|
|
2094
2131
|
*/
|
|
2095
2132
|
function lowerQuery(q: AnyQuery): ParsedQuery {
|
|
2096
2133
|
const theory = q.schema
|
|
@@ -2102,7 +2139,7 @@ function lowerQuery(q: AnyQuery): ParsedQuery {
|
|
|
2102
2139
|
q.data.interiors.forEach(function assignInteriorId(interior, index) {
|
|
2103
2140
|
interiorIds.set(interior.name, index)
|
|
2104
2141
|
})
|
|
2105
|
-
if (q.data.
|
|
2142
|
+
if (q.data.kind === "reach") {
|
|
2106
2143
|
interiorIds.set(q.data.rec.name, q.data.interiors.length)
|
|
2107
2144
|
}
|
|
2108
2145
|
const paramIds = new Map<string, number>()
|
|
@@ -2117,31 +2154,35 @@ function lowerQuery(q: AnyQuery): ParsedQuery {
|
|
|
2117
2154
|
params.set(entry.name, entry)
|
|
2118
2155
|
})
|
|
2119
2156
|
const ctx: LowerContext = { theory, relationIds, interiorIds, paramIds, params }
|
|
2157
|
+
const interiors = q.data.interiors.map(function lowerInterior(interior) {
|
|
2158
|
+
return {
|
|
2159
|
+
head: interior.finds.map(headTermOf),
|
|
2160
|
+
rules: interior.rules.map(function lowerInteriorRule(rule) {
|
|
2161
|
+
return lowerRule(ctx, rule)
|
|
2162
|
+
})
|
|
2163
|
+
}
|
|
2164
|
+
})
|
|
2165
|
+
const head = q.data.finds.map(headTermOf)
|
|
2166
|
+
const rules = q.data.rules.map(function lowerMainRule(rule) {
|
|
2167
|
+
return lowerRule(ctx, rule)
|
|
2168
|
+
})
|
|
2169
|
+
if (q.data.kind === "cq") {
|
|
2170
|
+
return parseQueryIr({ kind: "cq", interiors, head, rules })
|
|
2171
|
+
}
|
|
2120
2172
|
return parseQueryIr({
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
}
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
base: q.data.rec.base.map(function lowerBase(rule) {
|
|
2135
|
-
return lowerRule(ctx, rule)
|
|
2136
|
-
}),
|
|
2137
|
-
rec: q.data.rec.rec.map(function lowerRecArm(rule) {
|
|
2138
|
-
return lowerRule(ctx, rule)
|
|
2139
|
-
})
|
|
2140
|
-
},
|
|
2141
|
-
head: q.data.finds.map(headTermOf),
|
|
2142
|
-
rules: q.data.rules.map(function lowerMainRule(rule) {
|
|
2143
|
-
return lowerRule(ctx, rule)
|
|
2144
|
-
})
|
|
2173
|
+
kind: "reach",
|
|
2174
|
+
interiors,
|
|
2175
|
+
rec: {
|
|
2176
|
+
head: q.data.rec.finds.map(headTermOf),
|
|
2177
|
+
base: q.data.rec.base.map(function lowerBase(rule) {
|
|
2178
|
+
return lowerRule(ctx, rule)
|
|
2179
|
+
}),
|
|
2180
|
+
rec: q.data.rec.rec.map(function lowerRecArm(rule) {
|
|
2181
|
+
return lowerRule(ctx, rule)
|
|
2182
|
+
})
|
|
2183
|
+
},
|
|
2184
|
+
head,
|
|
2185
|
+
rules
|
|
2145
2186
|
})
|
|
2146
2187
|
}
|
|
2147
2188
|
|
|
@@ -2156,6 +2197,7 @@ export type {
|
|
|
2156
2197
|
Query,
|
|
2157
2198
|
QueryData,
|
|
2158
2199
|
QueryParams,
|
|
2200
|
+
QueryReachStart,
|
|
2159
2201
|
QueryRelation,
|
|
2160
2202
|
QueryRow,
|
|
2161
2203
|
QueryRuleChain,
|
package/src/query/parse-ir.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Host shape parse for the wire `QueryIr`:
|
|
3
|
-
* finds split (Count has no `over`; folds require
|
|
4
|
-
* alignment. The engine validator remains the one roster
|
|
5
|
-
* this parse refuses only shape the host type can see.
|
|
2
|
+
* Host shape parse for the wire `QueryIr`: CQ/Reach eliminator, rec/main
|
|
3
|
+
* nonempty, aggregate finds split (Count has no `over`; folds require
|
|
4
|
+
* it), head/find alignment. The engine validator remains the one roster
|
|
5
|
+
* authority — this parse refuses only shape the host type can see.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import * as errors from "@superbuilders/errors"
|
|
@@ -17,7 +17,7 @@ function parseQueryIr(ir: QueryIr): ParsedQuery {
|
|
|
17
17
|
ir.interiors.forEach(function checkInterior(interior, index) {
|
|
18
18
|
align(`interior ${index}`, interior.head, interior.rules)
|
|
19
19
|
})
|
|
20
|
-
if (ir.
|
|
20
|
+
if (ir.kind === "reach") {
|
|
21
21
|
if (ir.rec.base.length === 0) {
|
|
22
22
|
throw errors.new("parseQueryIr: rec base is empty")
|
|
23
23
|
}
|
package/src/relation.ts
CHANGED
|
@@ -20,6 +20,16 @@ import { type LiteralSetSpec, type LiteralSpec, renderLiteral } from "#spec.ts"
|
|
|
20
20
|
/** Flattens an intersection into one displayed object type (hover legibility). */
|
|
21
21
|
type Flatten<T> = { [K in keyof T]: T[K] }
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* An optional property that may be omitted OR explicitly `undefined`.
|
|
25
|
+
* `Partial<T>` under `exactOptionalPropertyTypes` is omit-only (`key?: T`),
|
|
26
|
+
* which rejects `key: T | undefined` — insert must accept both (omit-to-mint,
|
|
27
|
+
* and a host binding that is already `bigint | undefined`).
|
|
28
|
+
*/
|
|
29
|
+
type ExactOptional<T> = {
|
|
30
|
+
[K in keyof T]?: T[K] | undefined
|
|
31
|
+
}
|
|
32
|
+
|
|
23
33
|
/**
|
|
24
34
|
* Resolves one selection entry to its lowered literal set: a plain ARRAY
|
|
25
35
|
* (detected by `Array.isArray` — no field's value type is an array;
|
|
@@ -180,9 +190,12 @@ type FreshKeys<R extends AnyRelation> = {
|
|
|
180
190
|
/**
|
|
181
191
|
* The inferred row object type of a relation as INSERTED: fresh fields
|
|
182
192
|
* optional — omitted, the engine mints; supplied, identity is preserved
|
|
183
|
-
* (the ETL resupply idiom).
|
|
193
|
+
* (the ETL resupply idiom). Explicit `undefined` is the same as omit
|
|
194
|
+
* (`exactOptionalPropertyTypes`: `id?: bigint | undefined`, not omit-only).
|
|
184
195
|
*/
|
|
185
|
-
type InsertFact<R extends AnyRelation> = Flatten<
|
|
196
|
+
type InsertFact<R extends AnyRelation> = Flatten<
|
|
197
|
+
Omit<Fact<R>, FreshKeys<R>> & ExactOptional<Pick<Fact<R>, FreshKeys<R>>>
|
|
198
|
+
>
|
|
186
199
|
|
|
187
200
|
/**
|
|
188
201
|
* Declares one relation: `relation("Account", { id: u64.fresh,
|