@bjornpagen/bumbledb 1.1.0 → 1.2.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 +114 -11
- package/README.md +52 -9
- package/dist/capacity.d.ts +22 -59
- package/dist/capacity.d.ts.map +1 -1
- package/dist/capacity.js +66 -13
- package/dist/capacity.js.map +1 -1
- package/dist/changes.d.ts +26 -3
- package/dist/changes.d.ts.map +1 -1
- package/dist/changes.js +89 -44
- package/dist/changes.js.map +1 -1
- package/dist/closed.d.ts +23 -51
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +110 -168
- package/dist/closed.js.map +1 -1
- package/dist/codec.d.ts +4 -9
- package/dist/codec.d.ts.map +1 -1
- package/dist/codec.js +108 -183
- package/dist/codec.js.map +1 -1
- package/dist/compile.d.ts +16 -16
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +36 -15
- package/dist/compile.js.map +1 -1
- package/dist/db-native.d.ts +39 -0
- package/dist/db-native.d.ts.map +1 -1
- package/dist/db-native.js +8 -0
- package/dist/db-native.js.map +1 -1
- package/dist/db.d.ts +28 -8
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +91 -38
- package/dist/db.js.map +1 -1
- package/dist/errors.d.ts +11 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +13 -1
- package/dist/errors.js.map +1 -1
- package/dist/face.d.ts +19 -25
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +30 -24
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +13 -26
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +89 -116
- package/dist/fields.js.map +1 -1
- package/dist/immutable.d.ts +10 -0
- package/dist/immutable.d.ts.map +1 -0
- package/dist/immutable.js +75 -0
- package/dist/immutable.js.map +1 -0
- package/dist/index.d.ts +16 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/internal/log.d.ts +2 -0
- package/dist/internal/log.d.ts.map +1 -1
- package/dist/internal/log.js +2 -0
- package/dist/internal/log.js.map +1 -1
- package/dist/law.d.ts +13 -11
- package/dist/law.d.ts.map +1 -1
- package/dist/law.js +1 -1
- package/dist/law.js.map +1 -1
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +11 -6
- package/dist/lower.js.map +1 -1
- package/dist/native.d.ts +1 -1
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js.map +1 -1
- package/dist/query/atom.d.ts +1 -1
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js.map +1 -1
- package/dist/query/description.d.ts +33 -0
- package/dist/query/description.d.ts.map +1 -0
- package/dist/query/description.js +302 -0
- package/dist/query/description.js.map +1 -0
- package/dist/query/find.d.ts +21 -6
- package/dist/query/find.d.ts.map +1 -1
- package/dist/query/find.js.map +1 -1
- package/dist/query/lower.d.ts +75 -10
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +132 -163
- package/dist/query/lower.js.map +1 -1
- package/dist/query/parse-ir.d.ts +7 -2
- package/dist/query/parse-ir.d.ts.map +1 -1
- package/dist/query/parse-ir.js +240 -53
- package/dist/query/parse-ir.js.map +1 -1
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +8 -10
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +24 -16
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +5 -10
- package/dist/query/scope.js.map +1 -1
- package/dist/relation.d.ts +22 -48
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +30 -82
- package/dist/relation.js.map +1 -1
- package/dist/rows.d.ts +7 -4
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +44 -198
- package/dist/rows.js.map +1 -1
- package/dist/runtime-errors.d.ts +26 -2
- package/dist/runtime-errors.d.ts.map +1 -1
- package/dist/runtime-errors.js +43 -3
- package/dist/runtime-errors.js.map +1 -1
- package/dist/schema.d.ts +9 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +85 -24
- package/dist/schema.js.map +1 -1
- package/dist/selection.d.ts +27 -0
- package/dist/selection.d.ts.map +1 -0
- package/dist/selection.js +104 -0
- package/dist/selection.js.map +1 -0
- package/dist/shape.d.ts +12 -12
- package/dist/shape.d.ts.map +1 -1
- package/dist/statements.d.ts +17 -52
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +92 -111
- package/dist/statements.js.map +1 -1
- package/dist/values.d.ts +25 -0
- package/dist/values.d.ts.map +1 -0
- package/dist/values.js +203 -0
- package/dist/values.js.map +1 -0
- package/pack-provenance.json +3 -3
- package/package.json +4 -4
- package/src/capacity.ts +93 -80
- package/src/changes.ts +183 -54
- package/src/closed.ts +161 -314
- package/src/codec.ts +116 -190
- package/src/compile.ts +56 -32
- package/src/db-native.ts +42 -0
- package/src/db.ts +134 -52
- package/src/errors.ts +16 -1
- package/src/face.ts +44 -74
- package/src/fields.ts +100 -146
- package/src/immutable.ts +76 -0
- package/src/index.ts +36 -29
- package/src/internal/log.ts +2 -0
- package/src/law.ts +21 -18
- package/src/lower.ts +13 -10
- package/src/native.ts +2 -0
- package/src/query/atom.ts +1 -0
- package/src/query/description.ts +394 -0
- package/src/query/find.ts +20 -7
- package/src/query/lower.ts +177 -187
- package/src/query/parse-ir.ts +247 -54
- package/src/query/run.ts +15 -9
- package/src/query/scope.ts +42 -33
- package/src/relation.ts +58 -142
- package/src/rows.ts +62 -205
- package/src/runtime-errors.ts +47 -3
- package/src/schema.ts +99 -29
- package/src/selection.ts +124 -0
- package/src/shape.ts +18 -12
- package/src/statements.ts +138 -189
- package/src/values.ts +223 -0
package/src/query/parse-ir.ts
CHANGED
|
@@ -1,75 +1,268 @@
|
|
|
1
1
|
import { AuthoringError } from "#errors.ts"
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
AtomIr,
|
|
4
|
+
CmpOpIr,
|
|
5
|
+
ConditionTreeIr,
|
|
6
|
+
FindTermIr,
|
|
7
|
+
HeadTermIr,
|
|
8
|
+
ParsedQuery,
|
|
9
|
+
QueryIr,
|
|
10
|
+
RuleIr,
|
|
11
|
+
ScalarExprIr,
|
|
12
|
+
TermIr
|
|
13
|
+
} from "#native.ts"
|
|
14
|
+
import { arrayValue as array, recordValue, valueDescriptor } from "#values.ts"
|
|
3
15
|
|
|
4
|
-
function
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
align("query", ir.head, ir.rules)
|
|
9
|
-
ir.interiors.forEach(function checkInterior(interior, index) {
|
|
10
|
-
align(`interior ${index}`, interior.head, interior.rules)
|
|
16
|
+
function fail(context: string, expected: string): never {
|
|
17
|
+
throw new AuthoringError({
|
|
18
|
+
message: `${context}: ${expected}`,
|
|
19
|
+
diagnostic: { code: "InvalidQuery", context, expected }
|
|
11
20
|
})
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function tagged(context: string, input: unknown): Readonly<Record<string, unknown>> {
|
|
24
|
+
if (typeof input !== "object" || input === null) return fail(context, "expected a descriptor record")
|
|
25
|
+
return recordValue(context, input, Object.keys(input))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function ordinal(context: string, input: unknown, maximum = 0xffff): number {
|
|
29
|
+
if (typeof input !== "number" || !Number.isInteger(input) || input < 0 || input > maximum)
|
|
30
|
+
return fail(context, `expected an ordinal from 0 through ${maximum}`)
|
|
31
|
+
return input
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function term(context: string, input: unknown): TermIr {
|
|
35
|
+
const raw = tagged(context, input)
|
|
36
|
+
switch (raw.kind) {
|
|
37
|
+
case "var":
|
|
38
|
+
recordValue(context, raw, ["kind", "var"])
|
|
39
|
+
return Object.freeze({ kind: raw.kind, var: ordinal(context, raw.var) })
|
|
40
|
+
case "param":
|
|
41
|
+
case "paramSet":
|
|
42
|
+
recordValue(context, raw, ["kind", "param"])
|
|
43
|
+
return Object.freeze({ kind: raw.kind, param: ordinal(context, raw.param) })
|
|
44
|
+
case "literal":
|
|
45
|
+
recordValue(context, raw, ["kind", "value"])
|
|
46
|
+
return Object.freeze({ kind: raw.kind, value: valueDescriptor(`${context}.value`, raw.value) })
|
|
47
|
+
default:
|
|
48
|
+
return fail(context, "unknown term kind")
|
|
21
49
|
}
|
|
22
|
-
return ir as ParsedQuery
|
|
23
50
|
}
|
|
24
51
|
|
|
25
|
-
function
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
52
|
+
function scalar(context: string, input: unknown, depth = 1): ScalarExprIr {
|
|
53
|
+
if (depth > 128) return fail(context, "scalar expression deeper than 128")
|
|
54
|
+
const raw = tagged(context, input)
|
|
55
|
+
switch (raw.kind) {
|
|
56
|
+
case "var":
|
|
57
|
+
recordValue(context, raw, ["kind", "var"])
|
|
58
|
+
return Object.freeze({ kind: raw.kind, var: ordinal(context, raw.var) })
|
|
59
|
+
case "literal":
|
|
60
|
+
recordValue(context, raw, ["kind", "value"])
|
|
61
|
+
return Object.freeze({ kind: raw.kind, value: valueDescriptor(`${context}.value`, raw.value) })
|
|
62
|
+
case "negate":
|
|
63
|
+
case "isNaN":
|
|
64
|
+
case "isFinite":
|
|
65
|
+
recordValue(context, raw, ["kind", "expr"])
|
|
66
|
+
return Object.freeze({ kind: raw.kind, expr: scalar(`${context}.expr`, raw.expr, depth + 1) })
|
|
67
|
+
case "add":
|
|
68
|
+
case "subtract":
|
|
69
|
+
case "multiply":
|
|
70
|
+
case "divide":
|
|
71
|
+
recordValue(context, raw, ["kind", "left", "right"])
|
|
72
|
+
return Object.freeze({
|
|
73
|
+
kind: raw.kind,
|
|
74
|
+
left: scalar(`${context}.left`, raw.left, depth + 1),
|
|
75
|
+
right: scalar(`${context}.right`, raw.right, depth + 1)
|
|
30
76
|
})
|
|
77
|
+
case "cast": {
|
|
78
|
+
recordValue(context, raw, ["kind", "cast", "expr"])
|
|
79
|
+
const cast = raw.cast
|
|
80
|
+
if (cast !== "toF64" && cast !== "toF64Exact" && cast !== "toI64Exact" && cast !== "toU64Exact")
|
|
81
|
+
return fail(context, "unknown numeric cast")
|
|
82
|
+
return Object.freeze({ kind: raw.kind, cast, expr: scalar(`${context}.expr`, raw.expr, depth + 1) })
|
|
31
83
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const family = findFamily(find)
|
|
35
|
-
if (family !== head[position]?.kind) {
|
|
36
|
-
throw new AuthoringError({
|
|
37
|
-
message: `${context}: rule ${ruleIndex} find ${position} is ${family}, not head ${head[position]?.kind}`
|
|
38
|
-
})
|
|
39
|
-
}
|
|
40
|
-
})
|
|
84
|
+
default:
|
|
85
|
+
return fail(context, "unknown scalar expression kind")
|
|
41
86
|
}
|
|
42
87
|
}
|
|
43
88
|
|
|
44
|
-
function
|
|
45
|
-
const raw =
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
return
|
|
51
|
-
}
|
|
52
|
-
if (find.kind === "pack" || find.kind === "aggregate") {
|
|
53
|
-
if (!("over" in raw)) {
|
|
54
|
-
throw new AuthoringError({ message: `${context}: ${find.kind} requires over` })
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
if (find.kind === "compute" && !("expr" in raw)) {
|
|
58
|
-
throw new AuthoringError({ message: `${context}: compute requires expr` })
|
|
89
|
+
function headTerm(context: string, input: unknown): HeadTermIr {
|
|
90
|
+
const raw = tagged(context, input)
|
|
91
|
+
if (raw.kind === "var" || raw.kind === "compute") {
|
|
92
|
+
recordValue(context, raw, ["kind"])
|
|
93
|
+
return Object.freeze({ kind: raw.kind })
|
|
59
94
|
}
|
|
95
|
+
recordValue(context, raw, ["kind", "op"])
|
|
96
|
+
if (raw.kind !== "aggregate") return fail(context, "unknown head kind")
|
|
97
|
+
const op = raw.op
|
|
98
|
+
if (op !== "sum" && op !== "mean" && op !== "min" && op !== "max" && op !== "count" && op !== "pack")
|
|
99
|
+
return fail(context, "unknown head aggregate")
|
|
100
|
+
return Object.freeze({ kind: raw.kind, op })
|
|
60
101
|
}
|
|
61
102
|
|
|
62
|
-
function
|
|
63
|
-
|
|
103
|
+
function find(context: string, input: unknown): FindTermIr {
|
|
104
|
+
const raw = tagged(context, input)
|
|
105
|
+
switch (raw.kind) {
|
|
64
106
|
case "var":
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return "compute"
|
|
107
|
+
recordValue(context, raw, ["kind", "var"])
|
|
108
|
+
return Object.freeze({ kind: raw.kind, var: ordinal(context, raw.var) })
|
|
68
109
|
case "count":
|
|
110
|
+
if (Object.hasOwn(raw, "over")) return fail(context, "Count carries no over")
|
|
111
|
+
recordValue(context, raw, ["kind"])
|
|
112
|
+
return Object.freeze({ kind: raw.kind })
|
|
113
|
+
case "compute":
|
|
114
|
+
if (!Object.hasOwn(raw, "expr")) return fail(context, "compute requires expr")
|
|
115
|
+
recordValue(context, raw, ["kind", "expr"])
|
|
116
|
+
return Object.freeze({ kind: raw.kind, expr: scalar(`${context}.expr`, raw.expr) })
|
|
69
117
|
case "pack":
|
|
70
|
-
|
|
71
|
-
return
|
|
118
|
+
recordValue(context, raw, ["kind", "over"])
|
|
119
|
+
return Object.freeze({ kind: raw.kind, over: ordinal(context, raw.over) })
|
|
120
|
+
case "aggregate": {
|
|
121
|
+
recordValue(context, raw, ["kind", "op", "over"])
|
|
122
|
+
const op = recordValue(`${context}.op`, raw.op, ["kind"]).kind
|
|
123
|
+
if (op !== "sum" && op !== "mean" && op !== "min" && op !== "max") return fail(context, "unknown fold operator")
|
|
124
|
+
return Object.freeze({ kind: raw.kind, op: Object.freeze({ kind: op }), over: ordinal(context, raw.over) })
|
|
125
|
+
}
|
|
126
|
+
default:
|
|
127
|
+
return fail(context, "unknown find kind")
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function atom(context: string, input: unknown): AtomIr {
|
|
132
|
+
const raw = recordValue(context, input, ["source", "bindings"])
|
|
133
|
+
const source = tagged(`${context}.source`, raw.source)
|
|
134
|
+
let owned: AtomIr["source"]
|
|
135
|
+
if (source.kind === "edb") {
|
|
136
|
+
recordValue(context, source, ["kind", "relation"])
|
|
137
|
+
owned = { kind: source.kind, relation: ordinal(`${context}.source.relation`, source.relation, 0xffffffff) }
|
|
138
|
+
} else if (source.kind === "interior") {
|
|
139
|
+
recordValue(context, source, ["kind", "interior"])
|
|
140
|
+
owned = { kind: source.kind, interior: ordinal(`${context}.source.interior`, source.interior, 0xffffffff) }
|
|
141
|
+
} else return fail(context, "unknown atom source")
|
|
142
|
+
const seen = new Set<number>()
|
|
143
|
+
const bindings = array(`${context}.bindings`, raw.bindings, (path, pair): readonly [number, TermIr] => {
|
|
144
|
+
const cells = array(path, pair, (_, value) => value)
|
|
145
|
+
if (cells.length !== 2) return fail(path, "a binding has exactly two elements")
|
|
146
|
+
const field = ordinal(`${path}[0]`, cells[0])
|
|
147
|
+
if (seen.has(field)) return fail(path, "duplicate field binding")
|
|
148
|
+
seen.add(field)
|
|
149
|
+
return Object.freeze([field, term(`${path}[1]`, cells[1])])
|
|
150
|
+
})
|
|
151
|
+
return Object.freeze({ source: Object.freeze(owned), bindings })
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function comparisonOp(context: string, input: unknown): CmpOpIr {
|
|
155
|
+
const raw = tagged(context, input)
|
|
156
|
+
if (raw.kind === "allen") {
|
|
157
|
+
recordValue(context, raw, ["kind", "mask"])
|
|
158
|
+
return Object.freeze({ kind: raw.kind, mask: ordinal(`${context}.mask`, raw.mask, 0x1fff) })
|
|
159
|
+
}
|
|
160
|
+
recordValue(context, raw, ["kind"])
|
|
161
|
+
const kind = raw.kind
|
|
162
|
+
if (
|
|
163
|
+
kind !== "eq" &&
|
|
164
|
+
kind !== "ne" &&
|
|
165
|
+
kind !== "lt" &&
|
|
166
|
+
kind !== "le" &&
|
|
167
|
+
kind !== "gt" &&
|
|
168
|
+
kind !== "ge" &&
|
|
169
|
+
kind !== "pointIn"
|
|
170
|
+
)
|
|
171
|
+
return fail(context, "unknown comparison operator")
|
|
172
|
+
return Object.freeze({ kind })
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function condition(context: string, input: unknown, depth = 1): ConditionTreeIr {
|
|
176
|
+
if (depth > 64) return fail(context, "condition tree deeper than 64")
|
|
177
|
+
const raw = tagged(context, input)
|
|
178
|
+
if (raw.kind === "leaf") {
|
|
179
|
+
recordValue(context, raw, ["kind", "cmp"])
|
|
180
|
+
const cmp = recordValue(`${context}.cmp`, raw.cmp, ["op", "lhs", "rhs"])
|
|
181
|
+
return Object.freeze({
|
|
182
|
+
kind: raw.kind,
|
|
183
|
+
cmp: Object.freeze({
|
|
184
|
+
op: comparisonOp(`${context}.cmp.op`, cmp.op),
|
|
185
|
+
lhs: term(`${context}.cmp.lhs`, cmp.lhs),
|
|
186
|
+
rhs: term(`${context}.cmp.rhs`, cmp.rhs)
|
|
187
|
+
})
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
if (raw.kind !== "and" && raw.kind !== "or") return fail(context, "unknown condition kind")
|
|
191
|
+
recordValue(context, raw, ["kind", "children"])
|
|
192
|
+
return Object.freeze({
|
|
193
|
+
kind: raw.kind,
|
|
194
|
+
children: array(`${context}.children`, raw.children, (path, value) => condition(path, value, depth + 1))
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function rule(context: string, input: unknown): RuleIr {
|
|
199
|
+
const raw = recordValue(context, input, ["finds", "atoms", "negated", "conditions"])
|
|
200
|
+
return Object.freeze({
|
|
201
|
+
finds: array(`${context}.finds`, raw.finds, find),
|
|
202
|
+
atoms: array(`${context}.atoms`, raw.atoms, atom),
|
|
203
|
+
negated: array(`${context}.negated`, raw.negated, atom),
|
|
204
|
+
conditions: array(`${context}.conditions`, raw.conditions, condition)
|
|
205
|
+
})
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function align(context: string, head: readonly HeadTermIr[], rules: readonly RuleIr[]): void {
|
|
209
|
+
for (const [index, rule] of rules.entries()) {
|
|
210
|
+
if (rule.finds.length !== head.length) fail(`${context}.rules[${index}]`, "finds width does not match head width")
|
|
211
|
+
for (const [position, find] of rule.finds.entries()) {
|
|
212
|
+
const term = head[position]
|
|
213
|
+
const family = find.kind === "var" || find.kind === "compute" ? find.kind : "aggregate"
|
|
214
|
+
if (term?.kind !== family)
|
|
215
|
+
fail(`${context}.rules[${index}].finds[${position}]`, "find family does not match head")
|
|
216
|
+
if (term.kind === "aggregate") {
|
|
217
|
+
const op = find.kind === "aggregate" ? find.op.kind : find.kind
|
|
218
|
+
if (op !== term.op)
|
|
219
|
+
fail(`${context}.rules[${index}].finds[${position}]`, "aggregate operator does not match head")
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Own and validate the complete wire grammar. The native compiler then
|
|
227
|
+
* checks schema membership, binding types, safety and recursion semantics;
|
|
228
|
+
* parsing alone does not assert that a query is admitted by a schema.
|
|
229
|
+
*/
|
|
230
|
+
function parseQueryIr(input: unknown): ParsedQuery {
|
|
231
|
+
const raw = tagged("query", input)
|
|
232
|
+
if (raw.kind !== "cq" && raw.kind !== "reach") return fail("query", "unknown query kind")
|
|
233
|
+
recordValue(
|
|
234
|
+
"query",
|
|
235
|
+
raw,
|
|
236
|
+
raw.kind === "cq" ? ["kind", "interiors", "head", "rules"] : ["kind", "interiors", "rec", "head", "rules"]
|
|
237
|
+
)
|
|
238
|
+
const rules = array("query.rules", raw.rules, rule)
|
|
239
|
+
if (rules.length === 0) return fail("parseQueryIr", "main rules are empty")
|
|
240
|
+
const head = array("query.head", raw.head, headTerm)
|
|
241
|
+
align("query", head, rules)
|
|
242
|
+
const interiors = array("query.interiors", raw.interiors, (path, input) => {
|
|
243
|
+
const raw = recordValue(path, input, ["head", "rules"])
|
|
244
|
+
const head = array(`${path}.head`, raw.head, headTerm)
|
|
245
|
+
const rules = array(`${path}.rules`, raw.rules, rule)
|
|
246
|
+
if (rules.length === 0) return fail(path, "interior rules are empty")
|
|
247
|
+
align(path, head, rules)
|
|
248
|
+
return Object.freeze({ head, rules })
|
|
249
|
+
})
|
|
250
|
+
let owned: QueryIr
|
|
251
|
+
if (raw.kind === "cq") owned = { kind: raw.kind, interiors, head, rules }
|
|
252
|
+
else {
|
|
253
|
+
const rawRec = recordValue("query.rec", raw.rec, ["head", "base", "rec"])
|
|
254
|
+
const headRec = array("query.rec.head", rawRec.head, headTerm)
|
|
255
|
+
const base = array("query.rec.base", rawRec.base, rule)
|
|
256
|
+
if (base.length === 0) return fail("parseQueryIr", "rec base is empty")
|
|
257
|
+
const rec = array("query.rec.rec", rawRec.rec, rule)
|
|
258
|
+
if (rec.length === 0) return fail("parseQueryIr", "rec step is empty")
|
|
259
|
+
align("rec base", headRec, base)
|
|
260
|
+
align("rec step", headRec, rec)
|
|
261
|
+
if (headRec.some((term) => term.kind !== "var") || [...base, ...rec].some((rule) => rule.negated.length !== 0))
|
|
262
|
+
return fail("query.rec", "recursion requires projection-only heads and no negation")
|
|
263
|
+
owned = { kind: raw.kind, interiors, head, rules, rec: Object.freeze({ head: headRec, base, rec }) }
|
|
72
264
|
}
|
|
265
|
+
return Object.freeze(owned) as ParsedQuery
|
|
73
266
|
}
|
|
74
267
|
|
|
75
268
|
export type { ParsedQuery }
|
package/src/query/run.ts
CHANGED
|
@@ -4,7 +4,8 @@ import type { FindColumn } from "#query/atom.ts"
|
|
|
4
4
|
import { taggedCmpLiteral } from "#query/lower.ts"
|
|
5
5
|
import type { ParamEntry } from "#query/scope.ts"
|
|
6
6
|
import type { CellValue } from "#rows.ts"
|
|
7
|
-
import { decodeCell, handleOf } from "#rows.ts"
|
|
7
|
+
import { decodeCell, handleOf, setOwnField } from "#rows.ts"
|
|
8
|
+
import { arrayValue, recordValue } from "#values.ts"
|
|
8
9
|
|
|
9
10
|
function wireValue(entry: ParamEntry, context: string, value: unknown): TaggedValue {
|
|
10
11
|
if (entry.anchor === undefined) {
|
|
@@ -16,22 +17,24 @@ function wireValue(entry: ParamEntry, context: string, value: unknown): TaggedVa
|
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
function wireParams(entries: readonly ParamEntry[], supplied: Readonly<Record<string, unknown>>): QueryParam[] {
|
|
20
|
+
const input = recordValue(
|
|
21
|
+
"query parameters",
|
|
22
|
+
supplied,
|
|
23
|
+
entries.filter((entry) => entry.membership === undefined).map((entry) => entry.name)
|
|
24
|
+
)
|
|
19
25
|
return entries.map(function wireOne(entry): QueryParam {
|
|
20
26
|
if (entry.membership !== undefined) {
|
|
21
27
|
return entry.membership
|
|
22
28
|
}
|
|
23
|
-
const value =
|
|
29
|
+
const value = input[entry.name]
|
|
24
30
|
if (value === undefined) {
|
|
25
31
|
throw new AuthoringError({ message: `execute params object is missing param ${entry.name}` })
|
|
26
32
|
}
|
|
27
33
|
if (entry.shape === "set") {
|
|
28
|
-
if (!Array.isArray(value)) {
|
|
29
|
-
throw new AuthoringError({ message: `param ${entry.name}: a set param binds a readonly array of values` })
|
|
30
|
-
}
|
|
31
34
|
return {
|
|
32
35
|
kind: "set",
|
|
33
|
-
values: value
|
|
34
|
-
return wireValue(entry,
|
|
36
|
+
values: arrayValue(`param ${entry.name}`, value, function wireElement(context, element) {
|
|
37
|
+
return wireValue(entry, context, element)
|
|
35
38
|
})
|
|
36
39
|
}
|
|
37
40
|
}
|
|
@@ -71,11 +74,14 @@ function decodeAnswers<Row>(finds: readonly FindColumn[], rows: readonly (readon
|
|
|
71
74
|
throw new SdkInvariantError({ message: `query answer cell ${ordinal} (${column.name}) is absent` })
|
|
72
75
|
}
|
|
73
76
|
if (column.slot !== undefined) {
|
|
74
|
-
decoded
|
|
77
|
+
setOwnField(decoded, column.name, decodeCell(`query answer column ${column.name}`, column.slot.field, cell))
|
|
75
78
|
return
|
|
76
79
|
}
|
|
77
|
-
|
|
80
|
+
setOwnField(
|
|
81
|
+
decoded,
|
|
82
|
+
column.name,
|
|
78
83
|
column.closed === undefined ? cell : handleOf(`query answer column ${column.name}`, column.closed, cell)
|
|
84
|
+
)
|
|
79
85
|
})
|
|
80
86
|
Object.freeze(decoded)
|
|
81
87
|
if (!isAnswerRow<Row>(finds, decoded)) {
|
package/src/query/scope.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AnyClosed } from "#closed.ts"
|
|
2
|
-
import { sealedFieldsOf } from "#closed.ts"
|
|
2
|
+
import { memberDescriptor, sealedFieldsOf } from "#closed.ts"
|
|
3
3
|
import { AuthoringError, SdkInvariantError } from "#errors.ts"
|
|
4
4
|
import type { AnyField, Infer, SignatureOf } from "#fields.ts"
|
|
5
5
|
import { rosterOf, signaturesAgree } from "#fields.ts"
|
|
@@ -7,7 +7,8 @@ import type { Same, SameLen } from "#judgment.ts"
|
|
|
7
7
|
import type { ClassLookup, ClassRecordOf, SchemaClasses } from "#law.ts"
|
|
8
8
|
import type { QueryParam } from "#native.ts"
|
|
9
9
|
import type { FindColumn } from "#query/atom.ts"
|
|
10
|
-
import type { AnyRelation
|
|
10
|
+
import type { AnyRelation } from "#relation.ts"
|
|
11
|
+
import type { FieldsOf } from "#selection.ts"
|
|
11
12
|
|
|
12
13
|
const term: unique symbol = Symbol("bumbledb.query.term")
|
|
13
14
|
|
|
@@ -15,11 +16,7 @@ const inferred: unique symbol = Symbol("bumbledb.query.inferred")
|
|
|
15
16
|
|
|
16
17
|
type MatchOwner = AnyRelation | AnyClosed
|
|
17
18
|
|
|
18
|
-
type MatchFields<R extends MatchOwner> = R
|
|
19
|
-
? { readonly id: R["id"] } & R["columns"]
|
|
20
|
-
: R extends AnyRelation
|
|
21
|
-
? RelationFields<R>
|
|
22
|
-
: never
|
|
19
|
+
type MatchFields<R extends MatchOwner> = FieldsOf<R>
|
|
23
20
|
|
|
24
21
|
interface Var<F extends AnyField, RN extends string, K extends string> {
|
|
25
22
|
readonly [term]: "var"
|
|
@@ -125,9 +122,23 @@ function isImportedSource(value: unknown): value is ImportedSource {
|
|
|
125
122
|
|
|
126
123
|
type RowOfImport<Q> = InferredOf<Q> extends { readonly row: infer R } ? R : never
|
|
127
124
|
|
|
125
|
+
type HeadOfImport<Q> = InferredOf<Q> extends { readonly head: infer H } ? H : never
|
|
126
|
+
|
|
127
|
+
/** Named imported slot so downstream declarations need not spell its private marker. */
|
|
128
|
+
interface ImportedFieldVar<S extends ClassedField, K extends string> extends Var<S["field"], string, K> {
|
|
129
|
+
readonly [inferred]?: { readonly slot: S }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Retain the same descriptor and carrier class as the runtime import facade.
|
|
133
|
+
// A host bigint alone cannot distinguish i64 from u64 or a closed identifier.
|
|
134
|
+
type ImportedVar<Q, K extends string> =
|
|
135
|
+
HeadOfImport<Q> extends Readonly<Record<K, infer S extends ClassedField>>
|
|
136
|
+
? ImportedFieldVar<S, K>
|
|
137
|
+
: Var<AnyField, string, K>
|
|
138
|
+
|
|
128
139
|
type ImportVars<Q> = [RowOfImport<Q>] extends [never]
|
|
129
140
|
? Readonly<Record<string, Var<AnyField, string, string>>>
|
|
130
|
-
: { readonly [K in keyof RowOfImport<Q> & string]:
|
|
141
|
+
: { readonly [K in keyof RowOfImport<Q> & string]: ImportedVar<Q, K> }
|
|
131
142
|
|
|
132
143
|
interface ImportFacade {
|
|
133
144
|
/** The pseudo-owner every import var carries (structurally an owner). */
|
|
@@ -157,7 +168,7 @@ function importFacadeOf(source: ImportedSource): ImportFacade {
|
|
|
157
168
|
importOrdinal += 1
|
|
158
169
|
const name = importLabels.get(source) ?? `\u0000import:${importOrdinal}`
|
|
159
170
|
const columns = new Map<string, FindColumn>()
|
|
160
|
-
const fields: Array<
|
|
171
|
+
const fields: Array<readonly [string, AnyField]> = []
|
|
161
172
|
for (const column of source.data.finds) {
|
|
162
173
|
if (column.slot === undefined) {
|
|
163
174
|
throw new AuthoringError({
|
|
@@ -165,18 +176,13 @@ function importFacadeOf(source: ImportedSource): ImportFacade {
|
|
|
165
176
|
})
|
|
166
177
|
}
|
|
167
178
|
columns.set(column.name, column)
|
|
168
|
-
fields.push(
|
|
179
|
+
fields.push([column.name, column.slot.field])
|
|
169
180
|
}
|
|
170
|
-
const data = Object.freeze({ name, fields: Object.freeze(fields) })
|
|
171
181
|
const owner = Object.freeze({
|
|
182
|
+
kind: "relation" as const,
|
|
172
183
|
name,
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
throw new AuthoringError({
|
|
176
|
-
message: `imported query ${name}: an imported relation expression takes no selection — filter through where() in the consuming rule`
|
|
177
|
-
})
|
|
178
|
-
}
|
|
179
|
-
}) as unknown as MatchOwner & { readonly name: string }
|
|
184
|
+
fields: Object.freeze(Object.fromEntries(fields))
|
|
185
|
+
})
|
|
180
186
|
const facade: ImportFacade = Object.freeze({ owner, source, columns })
|
|
181
187
|
facadeBySource.set(source, facade)
|
|
182
188
|
facadeByOwner.set(owner, facade)
|
|
@@ -210,7 +216,7 @@ function v(owner: MatchOwner | ImportedSource): Readonly<Record<string, AnyVar>>
|
|
|
210
216
|
}
|
|
211
217
|
return Object.freeze(record)
|
|
212
218
|
}
|
|
213
|
-
const member = owner as MatchOwner
|
|
219
|
+
const member = memberDescriptor(owner as MatchOwner)
|
|
214
220
|
const record: Record<string, AnyVar> = {}
|
|
215
221
|
for (const declared of sealedFieldsOf(member)) {
|
|
216
222
|
const variable: AnyVar = Object.freeze({
|
|
@@ -249,10 +255,11 @@ type MintClassOf<Classes extends SchemaClasses, V> =
|
|
|
249
255
|
? ClassLookup<ClassRecordOf<Classes, RN>, K>
|
|
250
256
|
: never
|
|
251
257
|
|
|
252
|
-
type MintSlotOf<Classes extends SchemaClasses, V extends AnyVar> =
|
|
253
|
-
readonly field: V["field"]
|
|
254
|
-
readonly
|
|
255
|
-
|
|
258
|
+
type MintSlotOf<Classes extends SchemaClasses, V extends AnyVar> = [InferredOf<V>] extends [never]
|
|
259
|
+
? { readonly field: V["field"]; readonly class: MintClassOf<Classes, V> }
|
|
260
|
+
: InferredOf<V> extends { readonly slot: infer S extends ClassedField }
|
|
261
|
+
? S
|
|
262
|
+
: { readonly field: V["field"]; readonly class: MintClassOf<Classes, V> }
|
|
256
263
|
|
|
257
264
|
type ParamsRecord = Readonly<Record<string, unknown>>
|
|
258
265
|
|
|
@@ -265,15 +272,8 @@ type UnionToIntersection<U> = (U extends unknown ? (member: U) => void : never)
|
|
|
265
272
|
type ShapeOf<U> = [U] extends [never] ? Record<never, never> : Flatten<UnionToIntersection<U>>
|
|
266
273
|
|
|
267
274
|
/**
|
|
268
|
-
* A slot
|
|
269
|
-
*
|
|
270
|
-
* face pairing wall) plus the slot's law class.
|
|
271
|
-
*/
|
|
272
|
-
type SlotSignature<S extends ClassedField> = readonly [SignatureOf<S["field"]>, S["class"]]
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* A widened slot — a variable minted from an imported query template (its
|
|
276
|
-
* facade columns carry `AnyField`) or fully generic code — cannot be judged
|
|
275
|
+
* A widened slot — an explicitly erased query template or fully generic
|
|
276
|
+
* code with no retained descriptor — cannot be judged
|
|
277
277
|
* at the type tier. The degradation law applies: best effort degrades to
|
|
278
278
|
* SILENT, never to a wrong refusal; the runtime join judgment (real
|
|
279
279
|
* descriptors off the imported head's slots) stays authoritative.
|
|
@@ -281,7 +281,15 @@ type SlotSignature<S extends ClassedField> = readonly [SignatureOf<S["field"]>,
|
|
|
281
281
|
type WidenedSlot<S extends ClassedField> = [AnyField] extends [S["field"]] ? true : false
|
|
282
282
|
|
|
283
283
|
type JoinOk<A extends ClassedField, B extends ClassedField> =
|
|
284
|
-
WidenedSlot<A> extends true
|
|
284
|
+
WidenedSlot<A> extends true
|
|
285
|
+
? true
|
|
286
|
+
: WidenedSlot<B> extends true
|
|
287
|
+
? true
|
|
288
|
+
: Same<SignatureOf<A["field"]>, SignatureOf<B["field"]>> extends true
|
|
289
|
+
? string extends A["class"] | B["class"]
|
|
290
|
+
? true
|
|
291
|
+
: Same<A["class"], B["class"]>
|
|
292
|
+
: false
|
|
285
293
|
|
|
286
294
|
type U64Wire<F extends AnyField> = F extends { readonly kind: "u64" }
|
|
287
295
|
? F extends { readonly closed: unknown }
|
|
@@ -398,6 +406,7 @@ export type {
|
|
|
398
406
|
ClassedField,
|
|
399
407
|
ExactVars,
|
|
400
408
|
Flatten,
|
|
409
|
+
ImportedFieldVar,
|
|
401
410
|
ImportedSource,
|
|
402
411
|
ImportVars,
|
|
403
412
|
InferredOf,
|