@bjornpagen/bumbledb 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COOKBOOK.md +181 -47
- package/README.md +4 -2
- package/dist/closed.d.ts +30 -2
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +66 -20
- package/dist/closed.js.map +1 -1
- package/dist/db.d.ts +33 -6
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +87 -70
- package/dist/db.js.map +1 -1
- package/dist/exhume.d.ts.map +1 -1
- package/dist/exhume.js +1 -14
- package/dist/exhume.js.map +1 -1
- package/dist/face.d.ts +1 -1
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +2 -1
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +18 -1
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +44 -16
- package/dist/fields.js.map +1 -1
- package/dist/index.d.ts +11 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -4
- package/dist/index.js.map +1 -1
- package/dist/law.d.ts +2 -1
- package/dist/law.d.ts.map +1 -1
- package/dist/law.js +15 -14
- package/dist/law.js.map +1 -1
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +1 -7
- package/dist/lower.js.map +1 -1
- package/dist/marshal.d.ts.map +1 -1
- package/dist/marshal.js +15 -27
- package/dist/marshal.js.map +1 -1
- package/dist/native.d.ts +21 -2
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +20 -3
- package/dist/native.js.map +1 -1
- package/dist/order.d.ts +36 -0
- package/dist/order.d.ts.map +1 -0
- package/dist/order.js +135 -0
- package/dist/order.js.map +1 -0
- package/dist/query/atom.d.ts +18 -15
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +9 -16
- package/dist/query/atom.js.map +1 -1
- package/dist/query/lower.d.ts +5 -8
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +87 -76
- package/dist/query/lower.js.map +1 -1
- package/dist/query/predicate.d.ts.map +1 -1
- package/dist/query/predicate.js +34 -2
- package/dist/query/predicate.js.map +1 -1
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +1 -2
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +2 -16
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +7 -47
- package/dist/query/scope.js.map +1 -1
- package/dist/relation.d.ts +9 -22
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +9 -28
- package/dist/relation.js.map +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +7 -31
- package/dist/schema.js.map +1 -1
- package/dist/statements.d.ts +5 -2
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +30 -35
- package/dist/statements.js.map +1 -1
- package/package.json +2 -5
- package/src/closed.ts +73 -28
- package/src/db.ts +122 -84
- package/src/exhume.ts +1 -15
- package/src/face.ts +4 -3
- package/src/fields.ts +58 -16
- package/src/index.ts +10 -8
- package/src/law.ts +15 -14
- package/src/lower.ts +2 -9
- package/src/marshal.ts +15 -31
- package/src/native.ts +22 -4
- package/src/order.ts +156 -0
- package/src/query/atom.ts +13 -21
- package/src/query/lower.ts +90 -91
- package/src/query/predicate.ts +39 -4
- package/src/query/run.ts +1 -3
- package/src/query/scope.ts +7 -59
- package/src/relation.ts +9 -51
- package/src/schema.ts +7 -33
- package/src/statements.ts +33 -38
package/src/query/lower.ts
CHANGED
|
@@ -26,8 +26,9 @@
|
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
28
|
import * as errors from "@superbuilders/errors"
|
|
29
|
+
import { sealedFieldsOf } from "#closed.ts"
|
|
29
30
|
import type { AnyField, ClosedRoster } from "#fields.ts"
|
|
30
|
-
import { assertDeclarationOrderKey } from "#fields.ts"
|
|
31
|
+
import { assertDeclarationOrderKey, isIntervalValue, literalShapeError, rosterOf } from "#fields.ts"
|
|
31
32
|
import type { ClassRecordOf, SchemaClasses } from "#law.ts"
|
|
32
33
|
import type {
|
|
33
34
|
AtomIr,
|
|
@@ -68,7 +69,7 @@ import type {
|
|
|
68
69
|
SelectEntryData,
|
|
69
70
|
TreeData
|
|
70
71
|
} from "#query/atom.ts"
|
|
71
|
-
import { allen, and,
|
|
72
|
+
import { allen, and, eq, ge, gt, le, lt, ne, not, or, pointIn } from "#query/atom.ts"
|
|
72
73
|
import type {
|
|
73
74
|
ClassedField,
|
|
74
75
|
EnvShape,
|
|
@@ -88,13 +89,12 @@ import {
|
|
|
88
89
|
makeParam,
|
|
89
90
|
makeSetParam,
|
|
90
91
|
makeVar,
|
|
91
|
-
makeVars,
|
|
92
92
|
renderFieldKind,
|
|
93
93
|
term
|
|
94
94
|
} from "#query/scope.ts"
|
|
95
95
|
import type { CheckNameSelect, CheckSelect, RowOfNameSelect, RowOfSelect, SelectEntry } from "#query/select.ts"
|
|
96
96
|
import { argMax, argMin, count, countDistinct, max, min, pack, sum } from "#query/select.ts"
|
|
97
|
-
import type { FieldsShape
|
|
97
|
+
import type { FieldsShape } from "#relation.ts"
|
|
98
98
|
import type { AnySchema, Schema, SchemaRelations } from "#schema.ts"
|
|
99
99
|
|
|
100
100
|
/**
|
|
@@ -198,8 +198,6 @@ type CheckIdbVars<Env extends EnvShape, V, Head extends HeadShape = undefined> =
|
|
|
198
198
|
interface TermOps {
|
|
199
199
|
/** Declares/names one variable: typed by the field it first binds; reuse joins. */
|
|
200
200
|
readonly var: typeof makeVar
|
|
201
|
-
/** Mints several variables at once — `const { service, w } = r.vars("service", "w")`: each name typed exactly; duplicates refuse. */
|
|
202
|
-
readonly vars: typeof makeVars
|
|
203
201
|
/** Names one scalar parameter: typed by its use; the key of the execute params object. */
|
|
204
202
|
readonly param: typeof makeParam
|
|
205
203
|
/** Names one ∈-set parameter (the IR's `ParamSet`): bound to a readonly array at execution. */
|
|
@@ -215,7 +213,6 @@ interface TermOps {
|
|
|
215
213
|
readonly gt: typeof gt
|
|
216
214
|
readonly ge: typeof ge
|
|
217
215
|
readonly pointIn: typeof pointIn
|
|
218
|
-
readonly covers: typeof covers
|
|
219
216
|
readonly allen: typeof allen
|
|
220
217
|
readonly and: typeof and
|
|
221
218
|
readonly or: typeof or
|
|
@@ -431,7 +428,6 @@ interface QueryStart<Rels extends SchemaRelations, Classes extends SchemaClasses
|
|
|
431
428
|
/** The frozen constructor vocabulary every rule builder spreads. */
|
|
432
429
|
const termOps: TermOps = Object.freeze({
|
|
433
430
|
var: makeVar,
|
|
434
|
-
vars: makeVars,
|
|
435
431
|
param: makeParam,
|
|
436
432
|
inSet: makeSetParam,
|
|
437
433
|
maskParam: makeMaskParam,
|
|
@@ -443,7 +439,6 @@ const termOps: TermOps = Object.freeze({
|
|
|
443
439
|
gt,
|
|
444
440
|
ge,
|
|
445
441
|
pointIn,
|
|
446
|
-
covers,
|
|
447
442
|
allen,
|
|
448
443
|
and,
|
|
449
444
|
or,
|
|
@@ -479,22 +474,6 @@ interface ResolvedBindings {
|
|
|
479
474
|
readonly uses: readonly ParamUse[]
|
|
480
475
|
}
|
|
481
476
|
|
|
482
|
-
/**
|
|
483
|
-
* The ordered matchable fields of an atom owner — the runtime twin of the
|
|
484
|
-
* type tier's `MatchFields`: a relation's declared fields; a closed
|
|
485
|
-
* relation's SEALED shape, the synthetic `id` (the value's own
|
|
486
|
-
* roster-carrying descriptor, by identity) at ordinal 0 and the declared
|
|
487
|
-
* payload columns at declared index + 1 (the sealed shift, mirroring
|
|
488
|
-
* `spec.rs`'s resolver — a `ClosedColumn` is structurally a
|
|
489
|
-
* {@link RelationField}). The lowering golden pins this mapping.
|
|
490
|
-
*/
|
|
491
|
-
function matchFieldsOf(owner: MatchOwner): readonly RelationField[] {
|
|
492
|
-
if ("axioms" in owner) {
|
|
493
|
-
return [Object.freeze({ name: "id", field: owner.id }), ...owner.data.columns]
|
|
494
|
-
}
|
|
495
|
-
return owner.data.fields
|
|
496
|
-
}
|
|
497
|
-
|
|
498
477
|
/**
|
|
499
478
|
* Judges one membership ARRAY at a binding position — legal exactly at a
|
|
500
479
|
* CLOSED-reference field (the owner ruling: ordinary u64/str membership is
|
|
@@ -515,7 +494,8 @@ function membershipSet(
|
|
|
515
494
|
field: AnyField,
|
|
516
495
|
value: readonly unknown[]
|
|
517
496
|
): { readonly name: string; readonly members: readonly string[] } {
|
|
518
|
-
|
|
497
|
+
const roster = rosterOf(field)
|
|
498
|
+
if (roster === undefined) {
|
|
519
499
|
throw errors.new(
|
|
520
500
|
`${context}: a membership array is the closed-reference spelling — ordinary field membership is a bound ∈-set param (r.inSet)`
|
|
521
501
|
)
|
|
@@ -531,7 +511,7 @@ function membershipSet(
|
|
|
531
511
|
const seen = new Set<string>()
|
|
532
512
|
const members = value.map(function memberName(member) {
|
|
533
513
|
if (typeof member !== "string") {
|
|
534
|
-
throw literalShapeError(context, `a ${
|
|
514
|
+
throw literalShapeError(context, `a ${roster.name} handle name (string)`, member)
|
|
535
515
|
}
|
|
536
516
|
if (seen.has(member)) {
|
|
537
517
|
throw errors.new(
|
|
@@ -542,7 +522,7 @@ function membershipSet(
|
|
|
542
522
|
return member
|
|
543
523
|
})
|
|
544
524
|
const key = [...members].sort()
|
|
545
|
-
return { name: `∈ ${
|
|
525
|
+
return { name: `∈ ${roster.name} ${JSON.stringify(key)}`, members: Object.freeze(members) }
|
|
546
526
|
}
|
|
547
527
|
|
|
548
528
|
/**
|
|
@@ -564,7 +544,7 @@ function resolveBindings(
|
|
|
564
544
|
const vars: Array<{ readonly name: string; readonly slot: ClassedField }> = []
|
|
565
545
|
const uses: ParamUse[] = []
|
|
566
546
|
const relationClasses = classes[relation.name]
|
|
567
|
-
const ordered =
|
|
547
|
+
const ordered = sealedFieldsOf(relation)
|
|
568
548
|
for (const [fieldName, value] of Object.entries(bindings)) {
|
|
569
549
|
if (value === undefined) {
|
|
570
550
|
continue
|
|
@@ -922,7 +902,7 @@ function closedOrderError(context: string, position: string, vocabulary: string)
|
|
|
922
902
|
)
|
|
923
903
|
}
|
|
924
904
|
|
|
925
|
-
/** The comparison ops the orderable ban
|
|
905
|
+
/** The comparison ops under the orderable ban (order roster + point membership — every order-comparison position). */
|
|
926
906
|
function isOrderOp(op: CmpKind | "binding"): op is "lt" | "le" | "gt" | "ge" | "pointIn" {
|
|
927
907
|
return op === "lt" || op === "le" || op === "gt" || op === "ge" || op === "pointIn"
|
|
928
908
|
}
|
|
@@ -960,8 +940,9 @@ function validateCond(context: string, varFields: Readonly<Record<string, Classe
|
|
|
960
940
|
for (const side of [cond.lhs, cond.rhs]) {
|
|
961
941
|
if (side.kind === "var") {
|
|
962
942
|
const slot = assertBound(context, varFields, side.name)
|
|
963
|
-
|
|
964
|
-
|
|
943
|
+
const roster = rosterOf(slot.field)
|
|
944
|
+
if (isOrderOp(cond.op) && roster !== undefined) {
|
|
945
|
+
throw closedOrderError(context, `the ${cond.op} side ${side.name}`, roster.name)
|
|
965
946
|
}
|
|
966
947
|
}
|
|
967
948
|
if (side.kind === "measure") {
|
|
@@ -1009,12 +990,9 @@ function validateColumn(
|
|
|
1009
990
|
case "fold": {
|
|
1010
991
|
if (typeof agg.over === "string") {
|
|
1011
992
|
const slot = assertBound(`${context} select ${column.name}`, varFields, agg.over)
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
`the ${agg.fold} input ${agg.over}`,
|
|
1016
|
-
slot.field.closed.name
|
|
1017
|
-
)
|
|
993
|
+
const roster = rosterOf(slot.field)
|
|
994
|
+
if (roster !== undefined) {
|
|
995
|
+
throw closedOrderError(`${context} select ${column.name}`, `the ${agg.fold} input ${agg.over}`, roster.name)
|
|
1018
996
|
}
|
|
1019
997
|
return
|
|
1020
998
|
}
|
|
@@ -1024,11 +1002,12 @@ function validateColumn(
|
|
|
1024
1002
|
case "arg": {
|
|
1025
1003
|
assertBound(`${context} select ${column.name}`, varFields, agg.over)
|
|
1026
1004
|
const key = assertBound(`${context} select ${column.name}`, varFields, agg.key)
|
|
1027
|
-
|
|
1005
|
+
const keyRoster = rosterOf(key.field)
|
|
1006
|
+
if (keyRoster !== undefined) {
|
|
1028
1007
|
throw closedOrderError(
|
|
1029
1008
|
`${context} select ${column.name}`,
|
|
1030
1009
|
`the ${agg.direction} key ${agg.key}`,
|
|
1031
|
-
|
|
1010
|
+
keyRoster.name
|
|
1032
1011
|
)
|
|
1033
1012
|
}
|
|
1034
1013
|
return
|
|
@@ -1064,11 +1043,7 @@ function selectClosedOf(
|
|
|
1064
1043
|
if (over === undefined) {
|
|
1065
1044
|
return undefined
|
|
1066
1045
|
}
|
|
1067
|
-
|
|
1068
|
-
if (field !== undefined && "closed" in field) {
|
|
1069
|
-
return field.closed
|
|
1070
|
-
}
|
|
1071
|
-
return undefined
|
|
1046
|
+
return rosterOf(varFields[over]?.field)
|
|
1072
1047
|
}
|
|
1073
1048
|
|
|
1074
1049
|
/**
|
|
@@ -1267,10 +1242,12 @@ function makeRawScope(context: ChainContext): RawScope {
|
|
|
1267
1242
|
}
|
|
1268
1243
|
|
|
1269
1244
|
/**
|
|
1270
|
-
* The
|
|
1271
|
-
*
|
|
1272
|
-
*
|
|
1273
|
-
*
|
|
1245
|
+
* The rule builders' trusted admission seam — THE home of the
|
|
1246
|
+
* trusted-admission-seam pattern the other mint guards cite (the face,
|
|
1247
|
+
* class-map, axiom-readback, rec-handle, and query-value seams): the raw
|
|
1248
|
+
* builder is one runtime shape for every context, and this guard verifies
|
|
1249
|
+
* the checkable fact — the builder verbs exist — before the value is
|
|
1250
|
+
* admitted at its TYPED face. The type-level
|
|
1274
1251
|
* judgments (domain-equal joins, boundness, the recursion cut) live in the
|
|
1275
1252
|
* interfaces themselves; the runtime twin of every one of them is a
|
|
1276
1253
|
* construction-time validation in this module.
|
|
@@ -1329,17 +1306,30 @@ function headSignature(column: SelectColumn): string {
|
|
|
1329
1306
|
return `${column.name}:${agg.op}`
|
|
1330
1307
|
}
|
|
1331
1308
|
|
|
1332
|
-
/**
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1309
|
+
/**
|
|
1310
|
+
* The classed slot one answer column's VALUES flow from, resolved through
|
|
1311
|
+
* the rule's own binding environment: a projected var's first-binding slot,
|
|
1312
|
+
* or an Arg-carried payload's (`argMax`/`argMin` carry `over` verbatim —
|
|
1313
|
+
* the same two shapes the closed slice lifts). Counts, folds, `pack` and
|
|
1314
|
+
* the measure derive numbers/intervals rather than carrying a slot's ids,
|
|
1315
|
+
* so they resolve no slot (`undefined`).
|
|
1316
|
+
*/
|
|
1317
|
+
function headSlotOf(rule: RuleData, column: SelectColumn): ClassedField | undefined {
|
|
1318
|
+
const entry = column.entry
|
|
1319
|
+
if (entry.kind === "var") {
|
|
1320
|
+
return rule.varFields[entry.over]
|
|
1336
1321
|
}
|
|
1337
|
-
if ("
|
|
1338
|
-
return
|
|
1322
|
+
if (entry.kind === "aggregate" && entry.agg.op === "arg") {
|
|
1323
|
+
return rule.varFields[entry.agg.over]
|
|
1339
1324
|
}
|
|
1340
1325
|
return undefined
|
|
1341
1326
|
}
|
|
1342
1327
|
|
|
1328
|
+
/** The roster a param anchor carries: present exactly on a closed-reference field anchor (rides THE one `rosterOf` reader). */
|
|
1329
|
+
function anchorRosterOf(anchor: AnyField | "measure" | undefined): ClosedRoster | undefined {
|
|
1330
|
+
return anchor === "measure" ? undefined : rosterOf(anchor)
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1343
1333
|
/** Renders one param anchor's closedness for the registry's coherence diagnostics. */
|
|
1344
1334
|
function renderParamAnchor(roster: ClosedRoster | undefined): string {
|
|
1345
1335
|
return roster === undefined ? "a non-closed position" : `a ${roster.name} reference`
|
|
@@ -1430,13 +1420,9 @@ function paramRegistryOf(recs: readonly RecData[], rules: readonly RuleData[]):
|
|
|
1430
1420
|
if (entry === undefined) {
|
|
1431
1421
|
throw errors.new(`query param ${name} lost its registry entry`)
|
|
1432
1422
|
}
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
entry.
|
|
1436
|
-
entry.anchor !== "measure" &&
|
|
1437
|
-
"closed" in entry.anchor
|
|
1438
|
-
) {
|
|
1439
|
-
throw closedOrderError(`query param ${name}`, `its ${entry.orderOp} use's anchor`, entry.anchor.closed.name)
|
|
1423
|
+
const anchorRoster = anchorRosterOf(entry.anchor)
|
|
1424
|
+
if (entry.orderOp !== undefined && anchorRoster !== undefined) {
|
|
1425
|
+
throw closedOrderError(`query param ${name}`, `its ${entry.orderOp} use's anchor`, anchorRoster.name)
|
|
1440
1426
|
}
|
|
1441
1427
|
return Object.freeze({ name, shape: entry.shape, anchor: entry.anchor, op: entry.op, members: entry.members })
|
|
1442
1428
|
})
|
|
@@ -1481,6 +1467,23 @@ function makeRawQuery(theory: AnySchema, recs: readonly RecData[], rules: readon
|
|
|
1481
1467
|
`every rule of a query derives the same head — the answer column ${lead.name} is ${renderClosedSlice(lead.closed)} in rule 0 but ${renderClosedSlice(column.closed)} in rule ${index} (one column decodes through one roster)`
|
|
1482
1468
|
)
|
|
1483
1469
|
}
|
|
1470
|
+
// The law-class wall on the union head: one answer column is one
|
|
1471
|
+
// value space, so the classed slot each rule binds the column at
|
|
1472
|
+
// must join across rules — the SAME fieldJoins judgment every
|
|
1473
|
+
// join/eq/negated-atom position enforces. The SDK holds this wall
|
|
1474
|
+
// because the wire IR carries no domains: the engine cannot
|
|
1475
|
+
// backstop it, and without it a union mixes (say) Holder ids and
|
|
1476
|
+
// Account ids in one column the consumer reads as one id space.
|
|
1477
|
+
if (lead === undefined) {
|
|
1478
|
+
return
|
|
1479
|
+
}
|
|
1480
|
+
const leadSlot = headSlotOf(first, lead)
|
|
1481
|
+
const slot = headSlotOf(rule, column)
|
|
1482
|
+
if (leadSlot !== undefined && slot !== undefined && !fieldJoins(leadSlot, slot)) {
|
|
1483
|
+
throw errors.new(
|
|
1484
|
+
`every rule of a query derives the same head — the answer column ${lead.name} unions domain-unequal fields: bound at ${renderFieldKind(leadSlot)} in rule 0 but at ${renderFieldKind(slot)} in rule ${index} (a union column joins only class-equal slots; bare pairs only with bare)`
|
|
1485
|
+
)
|
|
1486
|
+
}
|
|
1484
1487
|
})
|
|
1485
1488
|
})
|
|
1486
1489
|
const data: QueryData = Object.freeze({
|
|
@@ -1502,7 +1505,7 @@ function makeRawQuery(theory: AnySchema, recs: readonly RecData[], rules: readon
|
|
|
1502
1505
|
}
|
|
1503
1506
|
|
|
1504
1507
|
/**
|
|
1505
|
-
* The query values' trusted admission seam (the
|
|
1508
|
+
* The query values' trusted admission seam (the {@link isTypedScope} pattern):
|
|
1506
1509
|
* the checkable fact — the value was assembled over the identical theory —
|
|
1507
1510
|
* is verified before the raw value is admitted at its typed face.
|
|
1508
1511
|
*/
|
|
@@ -1548,23 +1551,6 @@ function query<Rels extends SchemaRelations, Classes extends SchemaClasses>(
|
|
|
1548
1551
|
return start
|
|
1549
1552
|
}
|
|
1550
1553
|
|
|
1551
|
-
/** The typed shape refusal of the literal tagger — a genuine failure, never data. */
|
|
1552
|
-
function literalShapeError(context: string, expected: string, value: unknown): Error {
|
|
1553
|
-
return errors.new(`${context}: expected ${expected}, got ${typeof value}`)
|
|
1554
|
-
}
|
|
1555
|
-
|
|
1556
|
-
/** Narrows an interval-shaped literal (a plain `{ start, end }` bigint pair). */
|
|
1557
|
-
function isIntervalShaped(value: unknown): value is { readonly start: bigint; readonly end: bigint } {
|
|
1558
|
-
return (
|
|
1559
|
-
typeof value === "object" &&
|
|
1560
|
-
value !== null &&
|
|
1561
|
-
"start" in value &&
|
|
1562
|
-
"end" in value &&
|
|
1563
|
-
typeof value.start === "bigint" &&
|
|
1564
|
-
typeof value.end === "bigint"
|
|
1565
|
-
)
|
|
1566
|
-
}
|
|
1567
|
-
|
|
1568
1554
|
/**
|
|
1569
1555
|
* Tags one closed-reference literal: the handle NAME, verified against the
|
|
1570
1556
|
* roster (the belt the wide fallback type cannot provide — structural
|
|
@@ -1604,7 +1590,7 @@ function taggedAtElementDomain(context: string, element: "u64" | "i64", value: u
|
|
|
1604
1590
|
}
|
|
1605
1591
|
return { kind: "i64", value }
|
|
1606
1592
|
}
|
|
1607
|
-
if (
|
|
1593
|
+
if (isIntervalValue(value)) {
|
|
1608
1594
|
if (element === "u64") {
|
|
1609
1595
|
return { kind: "intervalU64", start: value.start, end: value.end }
|
|
1610
1596
|
}
|
|
@@ -1622,8 +1608,9 @@ function taggedAtElementDomain(context: string, element: "u64" | "i64", value: u
|
|
|
1622
1608
|
* tagged u64 after a roster verification.
|
|
1623
1609
|
*/
|
|
1624
1610
|
function taggedLiteral(context: string, field: AnyField, value: unknown): TaggedValue {
|
|
1625
|
-
|
|
1626
|
-
|
|
1611
|
+
const roster = rosterOf(field)
|
|
1612
|
+
if (roster !== undefined) {
|
|
1613
|
+
return taggedHandleId(context, roster, value)
|
|
1627
1614
|
}
|
|
1628
1615
|
switch (field.kind) {
|
|
1629
1616
|
case "bool": {
|
|
@@ -1648,6 +1635,18 @@ function taggedLiteral(context: string, field: AnyField, value: unknown): Tagged
|
|
|
1648
1635
|
if (typeof value !== "string") {
|
|
1649
1636
|
throw literalShapeError(context, "string", value)
|
|
1650
1637
|
}
|
|
1638
|
+
/**
|
|
1639
|
+
* The marshal's bijection law at the query seam (`marshal.ts`
|
|
1640
|
+
* cellOf): a lone surrogate would be lossily replaced with
|
|
1641
|
+
* U+FFFD at the bridge's UTF-8 crossing and silently match a
|
|
1642
|
+
* fact the typed write surface can never store — distinct JS
|
|
1643
|
+
* strings collapsing to one wire query. This is the single
|
|
1644
|
+
* seam every query string literal, string param
|
|
1645
|
+
* (`taggedCmpLiteral`), and membership member lowers through.
|
|
1646
|
+
*/
|
|
1647
|
+
if (!value.isWellFormed()) {
|
|
1648
|
+
throw literalShapeError(context, "well-formed string", value)
|
|
1649
|
+
}
|
|
1651
1650
|
return { kind: "string", value }
|
|
1652
1651
|
}
|
|
1653
1652
|
case "bytes": {
|
|
@@ -1665,11 +1664,11 @@ function taggedLiteral(context: string, field: AnyField, value: unknown): Tagged
|
|
|
1665
1664
|
* Tags one host literal at a COMPARISON or PARAM position, where the
|
|
1666
1665
|
* SIBLING anchors the type: a measure sibling is u64, an interval-field
|
|
1667
1666
|
* sibling contributes its element domain (so both a point literal in
|
|
1668
|
-
* `
|
|
1667
|
+
* `pointIn` and a `span` literal in `allen` tag correctly), a scalar
|
|
1669
1668
|
* sibling its own type. At `pointIn` the operand order is interval-left,
|
|
1670
1669
|
* point-right (`ir::CmpOp::PointIn`), so an interval-shaped literal
|
|
1671
|
-
* beside a scalar element-typed sibling is the LEGAL
|
|
1672
|
-
* `
|
|
1670
|
+
* beside a scalar element-typed sibling is the LEGAL interval operand of
|
|
1671
|
+
* `pointIn(t, span(...))` and tags as the interval of the sibling's
|
|
1673
1672
|
* element domain; under every other operator an interval shape against a
|
|
1674
1673
|
* scalar sibling stays refused (the engine's IllegalComparison — the
|
|
1675
1674
|
* bug-hunt fix, preserved op-aware).
|
|
@@ -1686,14 +1685,14 @@ function taggedCmpLiteral(
|
|
|
1686
1685
|
}
|
|
1687
1686
|
return { kind: "u64", value }
|
|
1688
1687
|
}
|
|
1689
|
-
if (
|
|
1688
|
+
if (rosterOf(sibling) === undefined && sibling.kind === "interval") {
|
|
1690
1689
|
return taggedAtElementDomain(context, sibling.element, value)
|
|
1691
1690
|
}
|
|
1692
1691
|
if (
|
|
1693
1692
|
op === "pointIn" &&
|
|
1694
|
-
|
|
1693
|
+
rosterOf(sibling) === undefined &&
|
|
1695
1694
|
(sibling.kind === "u64" || sibling.kind === "i64") &&
|
|
1696
|
-
|
|
1695
|
+
isIntervalValue(value)
|
|
1697
1696
|
) {
|
|
1698
1697
|
return taggedAtElementDomain(context, sibling.kind, value)
|
|
1699
1698
|
}
|
|
@@ -1743,7 +1742,7 @@ function paramIdOf(ctx: LowerContext, name: string): number {
|
|
|
1743
1742
|
* Lowers one EDB atom (either polarity). A CLOSED owner lowers through the
|
|
1744
1743
|
* same edb source — its ordinal is its record-declaration slot exactly like
|
|
1745
1744
|
* an ordinary relation's — with field ordinals over the SEALED shape: `id`
|
|
1746
|
-
* at 0, each payload column at its declared index + 1 (`
|
|
1745
|
+
* at 0, each payload column at its declared index + 1 (`sealedFieldsOf`
|
|
1747
1746
|
* carries the shift; the lowering golden pins it).
|
|
1748
1747
|
*/
|
|
1749
1748
|
function lowerAtom(ctx: LowerContext, atom: AtomData, ids: VarIds): AtomIr {
|
|
@@ -1757,7 +1756,7 @@ function lowerAtom(ctx: LowerContext, atom: AtomData, ids: VarIds): AtomIr {
|
|
|
1757
1756
|
if (relationId === undefined) {
|
|
1758
1757
|
throw errors.new(`query lowering: relation ${atom.relation.name} has no ordinal`)
|
|
1759
1758
|
}
|
|
1760
|
-
const ordered =
|
|
1759
|
+
const ordered = sealedFieldsOf(atom.relation)
|
|
1761
1760
|
const bindings: Array<readonly [number, TermIr]> = atom.bindings.map(function lowerBinding(binding) {
|
|
1762
1761
|
const ordinal = ordered.findIndex(function byName(candidate) {
|
|
1763
1762
|
return candidate.name === binding.field
|
package/src/query/predicate.ts
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
|
|
30
30
|
import * as errors from "@superbuilders/errors"
|
|
31
31
|
import type { SchemaClasses } from "#law.ts"
|
|
32
|
-
import type { RecData } from "#query/atom.ts"
|
|
32
|
+
import type { RecData, RuleData, SelectColumn } from "#query/atom.ts"
|
|
33
33
|
import type {
|
|
34
34
|
AnyRuleValue,
|
|
35
35
|
HeadOf,
|
|
@@ -45,8 +45,8 @@ import type {
|
|
|
45
45
|
RuleValue
|
|
46
46
|
} from "#query/lower.ts"
|
|
47
47
|
import { makeOutputRuleScope, makeQuery, makeRawScope } from "#query/lower.ts"
|
|
48
|
-
import type { Flatten, ParamsRecord, ShapeOf } from "#query/scope.ts"
|
|
49
|
-
import { inferred } from "#query/scope.ts"
|
|
48
|
+
import type { ClassedField, Flatten, ParamsRecord, ShapeOf } from "#query/scope.ts"
|
|
49
|
+
import { fieldJoins, inferred, renderFieldKind } from "#query/scope.ts"
|
|
50
50
|
import type { Schema, SchemaRelations } from "#schema.ts"
|
|
51
51
|
|
|
52
52
|
/**
|
|
@@ -112,6 +112,20 @@ interface RawRec<Name extends string> {
|
|
|
112
112
|
rule(build: (r: RawScope) => RuleValue<never, never>): RawRec<Name>
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
/**
|
|
116
|
+
* The classed slot one rec head column binds, through the rule's own
|
|
117
|
+
* environment: a rec head projects bound variable NAMES only (the strata
|
|
118
|
+
* roster's unwritability), so every column is a projected var and its
|
|
119
|
+
* slot is the var's first positive binding.
|
|
120
|
+
*/
|
|
121
|
+
function recHeadSlotOf(rule: RuleData, column: SelectColumn): ClassedField | undefined {
|
|
122
|
+
const entry = column.entry
|
|
123
|
+
if (entry.kind === "var") {
|
|
124
|
+
return rule.varFields[entry.over]
|
|
125
|
+
}
|
|
126
|
+
return undefined
|
|
127
|
+
}
|
|
128
|
+
|
|
115
129
|
/** Builds the runtime rec handle over shared rec data. */
|
|
116
130
|
function makeRawRec<Name extends string>(state: ProgramState, name: Name, data: RecData): RawRec<Name> {
|
|
117
131
|
const rec: RawRec<Name> = {
|
|
@@ -137,6 +151,26 @@ function makeRawRec<Name extends string>(state: ProgramState, name: Name, data:
|
|
|
137
151
|
`rec ${name}: every rule derives the same head — rule 0 projects (${declared.join(", ")}), this rule projects (${candidate.join(", ")})`
|
|
138
152
|
)
|
|
139
153
|
}
|
|
154
|
+
// The law-class wall on the sealed head: names alone do not
|
|
155
|
+
// align value spaces. Every rule must bind each head column
|
|
156
|
+
// at a slot that JOINS rule 0's (the sealing rule — the one
|
|
157
|
+
// slot every downstream idb pairing class-checks against),
|
|
158
|
+
// under the same fieldJoins judgment every reuse site
|
|
159
|
+
// enforces; otherwise a later rule feeds (say) bare weights
|
|
160
|
+
// into a column the idb joins as Node ids.
|
|
161
|
+
built.rule.select.forEach(function verifyHeadSlot(column, position) {
|
|
162
|
+
const lead = head.select[position]
|
|
163
|
+
if (lead === undefined) {
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
const leadSlot = recHeadSlotOf(head, lead)
|
|
167
|
+
const slot = recHeadSlotOf(built.rule, column)
|
|
168
|
+
if (leadSlot !== undefined && slot !== undefined && !fieldJoins(leadSlot, slot)) {
|
|
169
|
+
throw errors.new(
|
|
170
|
+
`rec ${name}: every rule derives the same head — the head column ${lead.name} is bound at ${renderFieldKind(leadSlot)} in rule 0 but at ${renderFieldKind(slot)} in this rule (a head column joins only class-equal slots; bare pairs only with bare)`
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
})
|
|
140
174
|
}
|
|
141
175
|
data.rules.push(built.rule)
|
|
142
176
|
return makeRawRec<Name>(state, name, data)
|
|
@@ -147,7 +181,8 @@ function makeRawRec<Name extends string>(state: ProgramState, name: Name, data:
|
|
|
147
181
|
}
|
|
148
182
|
|
|
149
183
|
/**
|
|
150
|
-
* The rec handles' trusted admission seam (the
|
|
184
|
+
* The rec handles' trusted admission seam (the pattern's home is
|
|
185
|
+
* `isTypedScope` in query/lower.ts):
|
|
151
186
|
* the checkable fact — the handle owns exactly the rec data it names — is
|
|
152
187
|
* verified before the raw handle is admitted at its typed face.
|
|
153
188
|
*/
|
package/src/query/run.ts
CHANGED
|
@@ -20,12 +20,10 @@ import * as errors from "@superbuilders/errors"
|
|
|
20
20
|
import { handleOf } from "#marshal.ts"
|
|
21
21
|
import type { FactValue, QueryParam, TaggedValue } from "#native.ts"
|
|
22
22
|
import type { SelectColumn } from "#query/atom.ts"
|
|
23
|
+
import { ALLEN_ALL_BITS } from "#query/atom.ts"
|
|
23
24
|
import { taggedCmpLiteral } from "#query/lower.ts"
|
|
24
25
|
import type { ParamEntry } from "#query/scope.ts"
|
|
25
26
|
|
|
26
|
-
/** The 13-bit Allen mask ceiling (`bumbledb/crates/bumbledb/src/allen.rs`: bits above the low 13 are unrepresentable). */
|
|
27
|
-
const ALLEN_ALL_BITS = (1 << 13) - 1
|
|
28
|
-
|
|
29
27
|
/** Tags one supplied mask-param value. */
|
|
30
28
|
function wireMask(name: string, value: unknown): TaggedValue {
|
|
31
29
|
if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value > ALLEN_ALL_BITS) {
|
package/src/query/scope.ts
CHANGED
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
* inference ride.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import * as errors from "@superbuilders/errors"
|
|
23
22
|
import type { AnyField, Infer } from "#fields.ts"
|
|
23
|
+
import { rosterOf } from "#fields.ts"
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* The runtime discriminant of query term values. Host literals (bigints,
|
|
@@ -110,57 +110,6 @@ function makeVar<const Name extends string>(name: Name): Var<Name> {
|
|
|
110
110
|
return Object.freeze(value)
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
/** The record `makeVars` mints: one own frozen `Var<Name>` per requested name, each typed exactly. */
|
|
114
|
-
type VarsRecord<Names extends string> = { readonly [N in Names]: Var<N> }
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* The trusted seam of the vars mint: every requested name reads back as an
|
|
118
|
-
* own var term of exactly that name — verified before the record is
|
|
119
|
-
* admitted at the {@link VarsRecord} type (a name riding the
|
|
120
|
-
* object-protocol accessor instead of an own definition would fail exactly
|
|
121
|
-
* this check).
|
|
122
|
-
*/
|
|
123
|
-
function varsMinted<Names extends string>(
|
|
124
|
-
record: Readonly<Record<string, unknown>>,
|
|
125
|
-
names: readonly Names[]
|
|
126
|
-
): record is Readonly<Record<string, unknown>> & VarsRecord<Names> {
|
|
127
|
-
return names.every(function varMinted(name) {
|
|
128
|
-
if (!Object.hasOwn(record, name)) {
|
|
129
|
-
return false
|
|
130
|
-
}
|
|
131
|
-
const value = record[name]
|
|
132
|
-
return isTerm(value) && value[term] === "var" && value.name === name
|
|
133
|
-
})
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Mints several variables at once — `const { service, w } = r.vars("service",
|
|
138
|
-
* "w")`: tuple-to-object, each name typed exactly (`Var<"service">`),
|
|
139
|
-
* inference identical to the one-at-a-time `r.var` spelling (one lowering,
|
|
140
|
-
* two entry flavors). Each key is defined as an OWN property (a name like
|
|
141
|
-
* `"__proto__"` is a record key like any other, never a prototype write),
|
|
142
|
-
* and a duplicate name in one call is a construction error: each name mints
|
|
143
|
-
* one variable — write it once and reuse the binding.
|
|
144
|
-
*/
|
|
145
|
-
function makeVars<const Names extends readonly string[]>(...names: Names): VarsRecord<Names[number]> {
|
|
146
|
-
const out: Record<string, unknown> = {}
|
|
147
|
-
const seen = new Set<string>()
|
|
148
|
-
for (const name of names) {
|
|
149
|
-
if (seen.has(name)) {
|
|
150
|
-
throw errors.new(
|
|
151
|
-
`vars: duplicate name ${name} — each name mints one variable; write it once and reuse the binding`
|
|
152
|
-
)
|
|
153
|
-
}
|
|
154
|
-
seen.add(name)
|
|
155
|
-
Object.defineProperty(out, name, { value: makeVar(name), enumerable: true })
|
|
156
|
-
}
|
|
157
|
-
Object.freeze(out)
|
|
158
|
-
if (!varsMinted<Names[number]>(out, names)) {
|
|
159
|
-
throw errors.new("vars: variable minting incomplete")
|
|
160
|
-
}
|
|
161
|
-
return out
|
|
162
|
-
}
|
|
163
|
-
|
|
164
113
|
/** Builds one scalar-parameter term. */
|
|
165
114
|
function makeParam<const Name extends string>(name: Name): Param<Name> {
|
|
166
115
|
const value: Param<Name> = { [term]: "param", name }
|
|
@@ -279,8 +228,8 @@ function fieldJoins(a: ClassedField, b: ClassedField): boolean {
|
|
|
279
228
|
const widthB = "width" in b.field ? b.field.width : undefined
|
|
280
229
|
const elementA = "element" in a.field ? a.field.element : undefined
|
|
281
230
|
const elementB = "element" in b.field ? b.field.element : undefined
|
|
282
|
-
const rosterA =
|
|
283
|
-
const rosterB =
|
|
231
|
+
const rosterA = rosterOf(a.field)
|
|
232
|
+
const rosterB = rosterOf(b.field)
|
|
284
233
|
return (
|
|
285
234
|
a.field.kind === b.field.kind &&
|
|
286
235
|
a.class === b.class &&
|
|
@@ -300,8 +249,9 @@ function fieldJoins(a: ClassedField, b: ClassedField): boolean {
|
|
|
300
249
|
function renderFieldKind(slot: ClassedField): string {
|
|
301
250
|
const field = slot.field
|
|
302
251
|
let base: string = field.kind
|
|
303
|
-
|
|
304
|
-
|
|
252
|
+
const roster = rosterOf(field)
|
|
253
|
+
if (roster !== undefined) {
|
|
254
|
+
base = `u64 referencing ${roster.name}`
|
|
305
255
|
}
|
|
306
256
|
if (field.kind === "bytes") {
|
|
307
257
|
base = `bytes<${field.width}>`
|
|
@@ -363,8 +313,7 @@ export type {
|
|
|
363
313
|
SetParam,
|
|
364
314
|
ShapeOf,
|
|
365
315
|
UnionToIntersection,
|
|
366
|
-
Var
|
|
367
|
-
VarsRecord
|
|
316
|
+
Var
|
|
368
317
|
}
|
|
369
318
|
export {
|
|
370
319
|
fieldJoins,
|
|
@@ -375,7 +324,6 @@ export {
|
|
|
375
324
|
makeParam,
|
|
376
325
|
makeSetParam,
|
|
377
326
|
makeVar,
|
|
378
|
-
makeVars,
|
|
379
327
|
renderFieldKind,
|
|
380
328
|
term
|
|
381
329
|
}
|