@bjornpagen/bumbledb 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COOKBOOK.md +178 -0
- package/README.md +2 -2
- package/dist/alternatives.d.ts +25 -0
- package/dist/alternatives.d.ts.map +1 -0
- package/dist/alternatives.js +59 -0
- package/dist/alternatives.js.map +1 -0
- package/dist/capacity.d.ts +7 -4
- package/dist/capacity.d.ts.map +1 -1
- package/dist/capacity.js +3 -1
- package/dist/capacity.js.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/law.d.ts +3 -1
- package/dist/law.d.ts.map +1 -1
- package/dist/law.js.map +1 -1
- package/dist/native.d.ts +14 -0
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js.map +1 -1
- package/dist/query/atom.d.ts +2 -1
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js.map +1 -1
- package/dist/query/compute.d.ts +16 -1
- package/dist/query/compute.d.ts.map +1 -1
- package/dist/query/compute.js +15 -2
- package/dist/query/compute.js.map +1 -1
- package/dist/query/description.d.ts +10 -2
- package/dist/query/description.d.ts.map +1 -1
- package/dist/query/description.js +10 -1
- package/dist/query/description.js.map +1 -1
- package/dist/query/find.d.ts +32 -10
- package/dist/query/find.d.ts.map +1 -1
- package/dist/query/find.js +1 -1
- package/dist/query/find.js.map +1 -1
- package/dist/query/lower.d.ts +18 -6
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +43 -30
- package/dist/query/lower.js.map +1 -1
- package/dist/query/parse-ir.d.ts.map +1 -1
- package/dist/query/parse-ir.js +24 -1
- package/dist/query/parse-ir.js.map +1 -1
- package/dist/query/scope.d.ts +21 -12
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js.map +1 -1
- package/dist/query/segments.d.ts +20 -0
- package/dist/query/segments.d.ts.map +1 -0
- package/dist/query/segments.js +35 -0
- package/dist/query/segments.js.map +1 -0
- package/dist/scalar.d.ts +30 -8
- package/dist/scalar.d.ts.map +1 -1
- package/dist/scalar.js +80 -2
- package/dist/scalar.js.map +1 -1
- package/dist/shape.d.ts +6 -3
- package/dist/shape.d.ts.map +1 -1
- package/pack-provenance.json +3 -3
- package/package.json +4 -4
- package/src/alternatives.ts +95 -0
- package/src/capacity.ts +7 -4
- package/src/index.ts +14 -2
- package/src/law.ts +8 -5
- package/src/native.ts +14 -0
- package/src/query/atom.ts +2 -0
- package/src/query/compute.ts +41 -2
- package/src/query/description.ts +34 -4
- package/src/query/find.ts +47 -23
- package/src/query/lower.ts +73 -43
- package/src/query/parse-ir.ts +23 -1
- package/src/query/scope.ts +32 -15
- package/src/query/segments.ts +61 -0
- package/src/scalar.ts +140 -7
- package/src/shape.ts +9 -3
package/src/native.ts
CHANGED
|
@@ -89,6 +89,14 @@ type HeadOpIr = "sum" | "mean" | "min" | "max" | "count" | "pack"
|
|
|
89
89
|
type NumericCastIr = "toF64" | "toF64Exact" | "toI64Exact" | "toU64Exact"
|
|
90
90
|
|
|
91
91
|
type ScalarExprIr =
|
|
92
|
+
| { readonly kind: "measure"; readonly expr: ScalarExprIr }
|
|
93
|
+
| {
|
|
94
|
+
readonly kind: "mulDiv"
|
|
95
|
+
readonly a: ScalarExprIr
|
|
96
|
+
readonly b: ScalarExprIr
|
|
97
|
+
readonly divisor: ScalarExprIr
|
|
98
|
+
readonly rounding: import("#scalar.ts").Rounding
|
|
99
|
+
}
|
|
92
100
|
| { readonly kind: "var"; readonly var: number }
|
|
93
101
|
| { readonly kind: "literal"; readonly value: TaggedValue }
|
|
94
102
|
| { readonly kind: "negate"; readonly expr: ScalarExprIr }
|
|
@@ -114,6 +122,12 @@ type FoldOpIr =
|
|
|
114
122
|
| { readonly kind: "max" }
|
|
115
123
|
|
|
116
124
|
type FindTermIr =
|
|
125
|
+
| {
|
|
126
|
+
readonly kind: "segments"
|
|
127
|
+
readonly op: import("#query/segments.ts").SegmentOp
|
|
128
|
+
readonly left: number
|
|
129
|
+
readonly right: number
|
|
130
|
+
}
|
|
117
131
|
| { readonly kind: "var"; readonly var: number }
|
|
118
132
|
| { readonly kind: "compute"; readonly expr: ScalarExprIr }
|
|
119
133
|
| { readonly kind: "count" }
|
package/src/query/atom.ts
CHANGED
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
ShapeOf
|
|
17
17
|
} from "#query/scope.ts"
|
|
18
18
|
import { isTerm, term } from "#query/scope.ts"
|
|
19
|
+
import type { Segments } from "#query/segments.ts"
|
|
19
20
|
import type { FieldsShape } from "#relation.ts"
|
|
20
21
|
import type { ScalarKind } from "#scalar.ts"
|
|
21
22
|
import type { Uuid } from "#uuid.ts"
|
|
@@ -75,6 +76,7 @@ type AggData =
|
|
|
75
76
|
|
|
76
77
|
/** Compute `expr` is the shared `#scalar.ts` query-var node (one grammar). */
|
|
77
78
|
type FindEntryData =
|
|
79
|
+
| Segments
|
|
78
80
|
| { readonly kind: "var"; readonly over: AnyVar }
|
|
79
81
|
| { readonly kind: "aggregate"; readonly agg: AggData }
|
|
80
82
|
| { readonly kind: "compute"; readonly expr: QueryNode; readonly result: ScalarKind }
|
package/src/query/compute.ts
CHANGED
|
@@ -13,7 +13,7 @@ import type { AnyField } from "#fields.ts"
|
|
|
13
13
|
import { bool as boolField, f64 as f64Field, i64 as i64Field, rosterOf, u64 as u64Field } from "#fields.ts"
|
|
14
14
|
import type { AnyVar } from "#query/scope.ts"
|
|
15
15
|
import { isTerm, term } from "#query/scope.ts"
|
|
16
|
-
import type { ScalarKind, ScalarLiteral, ScalarNode } from "#scalar.ts"
|
|
16
|
+
import type { Rounding, ScalarKind, ScalarLiteral, ScalarNode } from "#scalar.ts"
|
|
17
17
|
import {
|
|
18
18
|
checkBool,
|
|
19
19
|
checkF64,
|
|
@@ -27,6 +27,8 @@ import {
|
|
|
27
27
|
scalarCast,
|
|
28
28
|
scalarFloatPredicate,
|
|
29
29
|
scalarLiteral,
|
|
30
|
+
scalarMeasure,
|
|
31
|
+
scalarMulDiv,
|
|
30
32
|
scalarNegate
|
|
31
33
|
} from "#scalar.ts"
|
|
32
34
|
|
|
@@ -61,7 +63,11 @@ type OperandKind<O> =
|
|
|
61
63
|
: never
|
|
62
64
|
|
|
63
65
|
function isComputeExpr(value: unknown): value is AnyComputeExpr {
|
|
64
|
-
return
|
|
66
|
+
return (
|
|
67
|
+
isScalarNode(value) &&
|
|
68
|
+
value.scope === "query-var" &&
|
|
69
|
+
(value.result === "u64" || value.result === "i64" || value.result === "f64" || value.result === "bool")
|
|
70
|
+
)
|
|
65
71
|
}
|
|
66
72
|
|
|
67
73
|
function varKindOf(where: string, ref: AnyVar): ScalarKind {
|
|
@@ -167,6 +173,37 @@ function negate<O extends SignedOperand>(operand: O): ComputeExpr<OperandKind<O>
|
|
|
167
173
|
return scalarNegate(where, asQueryNode(where, operand)) as ComputeExpr<OperandKind<O> & ("i64" | "f64")>
|
|
168
174
|
}
|
|
169
175
|
|
|
176
|
+
type IntegerOperand =
|
|
177
|
+
| ComputeExpr<"u64">
|
|
178
|
+
| ComputeExpr<"i64">
|
|
179
|
+
| (AnyVar & { readonly field: { readonly kind: "i64" | "u64" } })
|
|
180
|
+
|
|
181
|
+
function mulDiv<A extends IntegerOperand, B extends IntegerOperand, D extends IntegerOperand>(
|
|
182
|
+
a: A,
|
|
183
|
+
b: B & (OperandKind<A> extends OperandKind<B> ? unknown : never),
|
|
184
|
+
divisor: D & (OperandKind<A> extends OperandKind<D> ? unknown : never),
|
|
185
|
+
rounding: Rounding
|
|
186
|
+
): ComputeExpr<OperandKind<A>> {
|
|
187
|
+
return scalarMulDiv(
|
|
188
|
+
"Compute.mulDiv",
|
|
189
|
+
asQueryNode("Compute.mulDiv", a),
|
|
190
|
+
asQueryNode("Compute.mulDiv", b),
|
|
191
|
+
asQueryNode("Compute.mulDiv", divisor),
|
|
192
|
+
rounding
|
|
193
|
+
) as ComputeExpr<OperandKind<A>>
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function measure<V extends AnyVar & { readonly field: { readonly kind: "interval" } }>(
|
|
197
|
+
interval: V
|
|
198
|
+
): ComputeExpr<V["field"] extends { readonly element: "f64" } ? "f64" : "u64"> {
|
|
199
|
+
if (!isTerm(interval) || interval[term] !== "var" || interval.field.kind !== "interval")
|
|
200
|
+
throw new AuthoringError({ message: "Compute.measure: expected an interval variable" })
|
|
201
|
+
const kind = { u64: "intervalU64", i64: "intervalI64", f64: "intervalF64" } as const
|
|
202
|
+
return scalarMeasure("Compute.measure", queryVarLeaf(interval, kind[interval.field.element])) as ComputeExpr<
|
|
203
|
+
V["field"] extends { readonly element: "f64" } ? "f64" : "u64"
|
|
204
|
+
>
|
|
205
|
+
}
|
|
206
|
+
|
|
170
207
|
function toF64(operand: ArithmeticOperand): ComputeExpr<"f64"> {
|
|
171
208
|
const where = "Compute.toF64"
|
|
172
209
|
return scalarCast(where, "toF64", "f64", asQueryNode(where, operand)) as ComputeExpr<"f64">
|
|
@@ -225,6 +262,8 @@ const Compute = Object.freeze({
|
|
|
225
262
|
subtract,
|
|
226
263
|
multiply,
|
|
227
264
|
divide,
|
|
265
|
+
mulDiv,
|
|
266
|
+
measure,
|
|
228
267
|
toF64,
|
|
229
268
|
toF64Exact,
|
|
230
269
|
toI64Exact,
|
package/src/query/description.ts
CHANGED
|
@@ -10,9 +10,19 @@ import type { AnyQuery, ChainContext, Query } from "#query/lower.ts"
|
|
|
10
10
|
import { alignedHeadOf, EMPTY_RULE, lowerQuery, makeRawChain, makeRawQuery, taggedCmpLiteral } from "#query/lower.ts"
|
|
11
11
|
import { parseQueryIr } from "#query/parse-ir.ts"
|
|
12
12
|
import { type AnyVar, type MatchOwner, makeParam, makeSetParam, type ParamsRecord, v } from "#query/scope.ts"
|
|
13
|
+
import { difference, type IntervalVar, intersection } from "#query/segments.ts"
|
|
13
14
|
import type { FieldsShape } from "#relation.ts"
|
|
14
15
|
import { handleOf } from "#rows.ts"
|
|
15
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
queryVarLeaf,
|
|
18
|
+
scalarBinary,
|
|
19
|
+
scalarCast,
|
|
20
|
+
scalarFloatPredicate,
|
|
21
|
+
scalarLiteral,
|
|
22
|
+
scalarMeasure,
|
|
23
|
+
scalarMulDiv,
|
|
24
|
+
scalarNegate
|
|
25
|
+
} from "#scalar.ts"
|
|
16
26
|
import type { Schema, SchemaRelations } from "#schema.ts"
|
|
17
27
|
import { schemaDescriptor } from "#schema.ts"
|
|
18
28
|
import { arrayValue, recordValue, valueDescriptor } from "#values.ts"
|
|
@@ -39,6 +49,12 @@ interface DescriptionParameter {
|
|
|
39
49
|
|
|
40
50
|
type DescriptionRow<F extends FieldsShape> = { readonly [K in keyof F]: Infer<F[K]> }
|
|
41
51
|
|
|
52
|
+
/** Checked result fields remain exact through v(imported), just as at runtime.
|
|
53
|
+
* Carrier classes come from the replayed head and remain runtime-checked. */
|
|
54
|
+
type DescriptionHead<F extends FieldsShape> = {
|
|
55
|
+
readonly [K in keyof F]: { readonly field: F[K]; readonly class: string | undefined }
|
|
56
|
+
}
|
|
57
|
+
|
|
42
58
|
function refused(context: string, expected: string): never {
|
|
43
59
|
throw new AuthoringError({
|
|
44
60
|
message: `${context}: ${expected}`,
|
|
@@ -237,6 +253,11 @@ function replayRule(
|
|
|
237
253
|
case "var": {
|
|
238
254
|
const variable = variableAt(input.var)
|
|
239
255
|
const kind = variable.field.kind
|
|
256
|
+
if (variable.field.kind === "interval")
|
|
257
|
+
return queryVarLeaf(
|
|
258
|
+
variable,
|
|
259
|
+
({ u64: "intervalU64", i64: "intervalI64", f64: "intervalF64" } as const)[variable.field.element]
|
|
260
|
+
)
|
|
240
261
|
if (
|
|
241
262
|
rosterOf(variable.field) !== undefined ||
|
|
242
263
|
(kind !== "bool" && kind !== "u64" && kind !== "i64" && kind !== "f64")
|
|
@@ -259,6 +280,10 @@ function replayRule(
|
|
|
259
280
|
return refused("query compute", "requires a scalar literal")
|
|
260
281
|
}
|
|
261
282
|
}
|
|
283
|
+
case "measure":
|
|
284
|
+
return scalarMeasure("query compute", scalar(input.expr))
|
|
285
|
+
case "mulDiv":
|
|
286
|
+
return scalarMulDiv("query compute", scalar(input.a), scalar(input.b), scalar(input.divisor), input.rounding)
|
|
262
287
|
case "negate":
|
|
263
288
|
return scalarNegate("query compute", scalar(input.expr))
|
|
264
289
|
case "isNaN":
|
|
@@ -281,6 +306,11 @@ function replayRule(
|
|
|
281
306
|
let value: unknown
|
|
282
307
|
if (find.kind === "var") value = variableAt(find.var)
|
|
283
308
|
else if (find.kind === "compute") value = scalar(find.expr)
|
|
309
|
+
else if (find.kind === "segments")
|
|
310
|
+
value = (find.op === "intersection" ? intersection : difference)(
|
|
311
|
+
variableAt(find.left) as IntervalVar,
|
|
312
|
+
variableAt(find.right) as IntervalVar
|
|
313
|
+
)
|
|
284
314
|
else if (find.kind === "count") value = { agg: "count" }
|
|
285
315
|
else value = { agg: find.kind === "pack" ? "pack" : find.op.kind, over: variableAt(find.over) }
|
|
286
316
|
return [column, value]
|
|
@@ -293,7 +323,7 @@ function queryFromDescription<Rels extends SchemaRelations, Classes extends Sche
|
|
|
293
323
|
schema: Schema<Rels, Classes>,
|
|
294
324
|
input: unknown,
|
|
295
325
|
result: F
|
|
296
|
-
): Query<Rels, DescriptionRow<F>, ParamsRecord, Classes
|
|
326
|
+
): Query<Rels, DescriptionRow<F>, ParamsRecord, Classes, DescriptionHead<F>> {
|
|
297
327
|
const theory = schemaDescriptor(schema)
|
|
298
328
|
const description = checkedDescription(input)
|
|
299
329
|
const resultRecord = recordValue(
|
|
@@ -387,8 +417,8 @@ function queryFromDescription<Rels extends SchemaRelations, Classes extends Sche
|
|
|
387
417
|
if (usedParameters.size !== description.parameters.length) refused("query parameters", "declared parameter is unused")
|
|
388
418
|
// Complete shared lowering also checks tagged literals and ordinal bounds.
|
|
389
419
|
lowerQuery(value)
|
|
390
|
-
return value as unknown as Query<Rels, DescriptionRow<F>, ParamsRecord, Classes
|
|
420
|
+
return value as unknown as Query<Rels, DescriptionRow<F>, ParamsRecord, Classes, DescriptionHead<F>>
|
|
391
421
|
}
|
|
392
422
|
|
|
393
|
-
export type { DescriptionParameter, DescriptionRow, DescriptionTable, QueryDescription }
|
|
423
|
+
export type { DescriptionHead, DescriptionParameter, DescriptionRow, DescriptionTable, QueryDescription }
|
|
394
424
|
export { describeQuery, queryFromDescription }
|
package/src/query/find.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { BoolField, F64Field, I64Field, Infer, IntervalField, U64Field } from "#fields.ts"
|
|
2
2
|
import type { SchemaClasses } from "#law.ts"
|
|
3
3
|
import type { IntervalVarOk, NumericVarOk } from "#query/atom.ts"
|
|
4
4
|
import type { AnyComputeExpr, ComputeExpr, ComputeValue } from "#query/compute.ts"
|
|
5
5
|
import type { AnyVar, MintSlotOf } from "#query/scope.ts"
|
|
6
|
+
import type { Segments } from "#query/segments.ts"
|
|
6
7
|
import type { ScalarKind } from "#scalar.ts"
|
|
7
8
|
|
|
8
9
|
type FoldOpName = "sum" | "mean" | "min" | "max" | "pack"
|
|
@@ -18,7 +19,7 @@ interface Agg<Op extends FoldOpName, Over extends AnyVar> {
|
|
|
18
19
|
|
|
19
20
|
type AnyAgg = CountAgg | Agg<FoldOpName, AnyVar>
|
|
20
21
|
|
|
21
|
-
type FindEntry = AnyVar | AnyAgg | AnyComputeExpr
|
|
22
|
+
type FindEntry = AnyVar | AnyAgg | AnyComputeExpr | Segments
|
|
22
23
|
|
|
23
24
|
type FindShape = Readonly<Record<string, FindEntry>>
|
|
24
25
|
|
|
@@ -33,7 +34,7 @@ function count(): CountAgg {
|
|
|
33
34
|
/**
|
|
34
35
|
* Exact checked sum over a NUMERIC (u64/i64/f64) variable — wide accumulator,
|
|
35
36
|
* one finalize range check; overflow is the engine's typed runtime error —
|
|
36
|
-
*
|
|
37
|
+
* of a measured interval in a following stage. Bool stays refused: a
|
|
37
38
|
* quantifier is not an addition (R3).
|
|
38
39
|
*/
|
|
39
40
|
function sum<const O extends AnyVar>(over: O): Agg<"sum", O> {
|
|
@@ -69,7 +70,7 @@ type FindEntryOk<E> = E extends AnyVar
|
|
|
69
70
|
? NumericVarOk<O>
|
|
70
71
|
: E extends Agg<"pack", infer V extends AnyVar>
|
|
71
72
|
? IntervalVarOk<V>
|
|
72
|
-
: E extends AnyComputeExpr
|
|
73
|
+
: E extends AnyComputeExpr | Segments
|
|
73
74
|
? true
|
|
74
75
|
: false
|
|
75
76
|
|
|
@@ -81,31 +82,54 @@ type CheckRecFind<F extends FindShape> = {
|
|
|
81
82
|
readonly [K in keyof F]: F[K] extends AnyVar ? F[K] : never
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
type FindValue<E> =
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
85
|
+
type FindValue<E> =
|
|
86
|
+
E extends Segments<infer Element>
|
|
87
|
+
? Infer<IntervalField<Element, undefined>>
|
|
88
|
+
: E extends AnyVar
|
|
89
|
+
? Infer<E["field"]>
|
|
90
|
+
: E extends CountAgg
|
|
91
|
+
? bigint
|
|
92
|
+
: E extends Agg<"sum" | "mean" | "min" | "max", infer O extends AnyVar>
|
|
93
|
+
? Infer<O["field"]>
|
|
94
|
+
: E extends Agg<"pack", infer V extends AnyVar>
|
|
95
|
+
? Infer<V["field"]>
|
|
96
|
+
: E extends ComputeExpr<infer K extends ScalarKind>
|
|
97
|
+
? ComputeValue<K>
|
|
98
|
+
: never
|
|
95
99
|
|
|
96
100
|
type RowOfFind<F extends FindShape> = { readonly [K in keyof F]: FindValue<F[K]> }
|
|
97
101
|
|
|
102
|
+
type ComputeFields = {
|
|
103
|
+
readonly u64: U64Field
|
|
104
|
+
readonly i64: I64Field
|
|
105
|
+
readonly f64: F64Field
|
|
106
|
+
readonly bool: BoolField
|
|
107
|
+
}
|
|
108
|
+
|
|
98
109
|
/**
|
|
99
|
-
* The head signature
|
|
100
|
-
*
|
|
101
|
-
*
|
|
110
|
+
* The head signature as classed slots, shared by derived-stage and imported
|
|
111
|
+
* query variables. Projection preserves its carrier; aggregates/computations
|
|
112
|
+
* produce bare values, matching findColumnSlotOf at runtime.
|
|
102
113
|
*/
|
|
103
114
|
type HeadRecordOf<Classes extends SchemaClasses, F extends FindShape> = {
|
|
104
|
-
readonly [K in keyof F]: F[K] extends
|
|
105
|
-
?
|
|
106
|
-
: F[K] extends
|
|
107
|
-
?
|
|
108
|
-
:
|
|
115
|
+
readonly [K in keyof F]: F[K] extends Segments<infer Element>
|
|
116
|
+
? { readonly field: IntervalField<Element, undefined>; readonly class: undefined }
|
|
117
|
+
: F[K] extends AnyVar
|
|
118
|
+
? MintSlotOf<Classes, F[K]>
|
|
119
|
+
: F[K] extends ComputeExpr<infer Kind extends ScalarKind>
|
|
120
|
+
? { readonly field: ComputeFields[Kind]; readonly class: undefined }
|
|
121
|
+
: F[K] extends CountAgg
|
|
122
|
+
? { readonly field: U64Field; readonly class: undefined }
|
|
123
|
+
: F[K] extends Agg<"mean", AnyVar>
|
|
124
|
+
? { readonly field: F64Field; readonly class: undefined }
|
|
125
|
+
: F[K] extends Agg<"pack", infer Over extends AnyVar>
|
|
126
|
+
? {
|
|
127
|
+
readonly field: IntervalField<Over["field"] extends IntervalField<infer E> ? E : never, undefined>
|
|
128
|
+
readonly class: undefined
|
|
129
|
+
}
|
|
130
|
+
: F[K] extends Agg<FoldOpName, infer Over extends AnyVar>
|
|
131
|
+
? { readonly field: Over["field"]; readonly class: undefined }
|
|
132
|
+
: never
|
|
109
133
|
}
|
|
110
134
|
|
|
111
135
|
export type { Agg, CheckFind, CheckRecFind, FindEntry, FindShape, HeadRecordOf, RowOfFind }
|
package/src/query/lower.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { membersAgree, sealedFieldsOf } from "#closed.ts"
|
|
2
2
|
import { AuthoringError, SdkInvariantError } from "#errors.ts"
|
|
3
|
-
import type { AnyClosedRoster, AnyField } from "#fields.ts"
|
|
3
|
+
import type { AnyClosedRoster, AnyField, IntervalField } from "#fields.ts"
|
|
4
4
|
import {
|
|
5
5
|
assertDeclarationOrderKey,
|
|
6
6
|
f64 as f64Field,
|
|
@@ -87,7 +87,8 @@ import {
|
|
|
87
87
|
renderFieldKind,
|
|
88
88
|
term
|
|
89
89
|
} from "#query/scope.ts"
|
|
90
|
-
import {
|
|
90
|
+
import { difference, intersection, isSegments, segmentField } from "#query/segments.ts"
|
|
91
|
+
import { scalarWire } from "#scalar.ts"
|
|
91
92
|
import type { AnySchema, Schema, SchemaRelations } from "#schema.ts"
|
|
92
93
|
import { schemaDescriptor, schemasAgree } from "#schema.ts"
|
|
93
94
|
import { arrayValue, recordValue, taggedValueOf } from "#values.ts"
|
|
@@ -100,6 +101,8 @@ type RowOf<T> = InferredOf<T> extends { readonly row: infer R } ? R : never
|
|
|
100
101
|
|
|
101
102
|
type HeadShape = Readonly<Record<string, ClassedField>> | undefined
|
|
102
103
|
|
|
104
|
+
type HeadOf<T> = InferredOf<T> extends { readonly head: infer H extends HeadShape } ? H : HeadShape
|
|
105
|
+
|
|
103
106
|
interface RuleValue<Row, P extends ParamsRecord, Head extends HeadShape = undefined> {
|
|
104
107
|
readonly rule: RuleData
|
|
105
108
|
readonly [inferred]?: { readonly row: Row; readonly params: P; readonly head: Head }
|
|
@@ -145,6 +148,8 @@ interface TermOps {
|
|
|
145
148
|
readonly mean: typeof mean
|
|
146
149
|
readonly min: typeof min
|
|
147
150
|
readonly max: typeof max
|
|
151
|
+
readonly intersection: typeof intersection
|
|
152
|
+
readonly difference: typeof difference
|
|
148
153
|
readonly pack: typeof pack
|
|
149
154
|
}
|
|
150
155
|
|
|
@@ -225,7 +230,12 @@ interface QueryRuleChain<
|
|
|
225
230
|
bindings: B & CheckInteriorBindings<B>
|
|
226
231
|
): QueryRuleChain<Rels, P, Classes>
|
|
227
232
|
|
|
228
|
-
|
|
233
|
+
/** A record of variables is already a valid projection, including v(rel)
|
|
234
|
+
* for a generic relation. Aggregate judgments remain on the general form. */
|
|
235
|
+
find<const F extends Readonly<Record<string, AnyVar>>>(
|
|
236
|
+
entries: F
|
|
237
|
+
): RuleValue<RowOfFind<F>, P, HeadRecordOf<Classes, F>>
|
|
238
|
+
find<const F extends FindShape>(entries: F & CheckFind<F>): RuleValue<RowOfFind<F>, P, HeadRecordOf<Classes, F>>
|
|
229
239
|
}
|
|
230
240
|
|
|
231
241
|
interface InteriorRuleScope<Rels extends SchemaRelations, Classes extends SchemaClasses = SchemaClasses>
|
|
@@ -292,6 +302,9 @@ interface InteriorRuleChain<
|
|
|
292
302
|
* Nonrecursive derived stages emit aggregate/computed outputs too
|
|
293
303
|
* (C05): only the RECURSIVE head stays projection-only.
|
|
294
304
|
*/
|
|
305
|
+
find<const F extends Readonly<Record<string, AnyVar>>>(
|
|
306
|
+
entries: F
|
|
307
|
+
): RuleValue<RowOfFind<F>, P, HeadRecordOf<Classes, F>>
|
|
295
308
|
find<const F extends FindShape>(entries: F & CheckFind<F>): RuleValue<RowOfFind<F>, P, HeadRecordOf<Classes, F>>
|
|
296
309
|
}
|
|
297
310
|
|
|
@@ -355,6 +368,9 @@ interface RecRuleChain<
|
|
|
355
368
|
name: string,
|
|
356
369
|
bindings: B & CheckInteriorBindings<B>
|
|
357
370
|
): RecRuleChain<Rels, P, Classes>
|
|
371
|
+
find<const F extends Readonly<Record<string, AnyVar>>>(
|
|
372
|
+
entries: F
|
|
373
|
+
): RuleValue<RowOfFind<F>, P, HeadRecordOf<Classes, F>>
|
|
358
374
|
find<const F extends FindShape>(entries: F & CheckRecFind<F>): RuleValue<RowOfFind<F>, P, HeadRecordOf<Classes, F>>
|
|
359
375
|
}
|
|
360
376
|
|
|
@@ -388,22 +404,23 @@ interface Query<
|
|
|
388
404
|
Rels extends SchemaRelations,
|
|
389
405
|
Row,
|
|
390
406
|
Params extends ParamsRecord,
|
|
391
|
-
Classes extends SchemaClasses = SchemaClasses
|
|
407
|
+
Classes extends SchemaClasses = SchemaClasses,
|
|
408
|
+
Head extends HeadShape = HeadShape
|
|
392
409
|
> {
|
|
393
410
|
readonly schema: Schema<Rels, Classes>
|
|
394
411
|
readonly data: QueryData
|
|
395
412
|
|
|
396
413
|
rule<RV extends AnyRuleValue>(
|
|
397
414
|
build: (r: QueryRuleScope<Rels, Classes>) => RV
|
|
398
|
-
): Query<Rels, Row | RowOf<RV>, Flatten<Params & ParamsOf<RV>>, Classes
|
|
415
|
+
): Query<Rels, Row | RowOf<RV>, Flatten<Params & ParamsOf<RV>>, Classes, Head | HeadOf<RV>>
|
|
399
416
|
|
|
400
417
|
/** A diagnostic name for composition/tracing — never a schema relation. */
|
|
401
|
-
named(label: string): Query<Rels, Row, Params, Classes>
|
|
418
|
+
named(label: string): Query<Rels, Row, Params, Classes, Head>
|
|
402
419
|
|
|
403
420
|
interior(name: string, ...builds: never[]): never
|
|
404
421
|
|
|
405
422
|
reach(name: string, arms: never): never
|
|
406
|
-
readonly [inferred]?: { readonly row: Row; readonly params: Params }
|
|
423
|
+
readonly [inferred]?: { readonly row: Row; readonly params: Params; readonly head: Head }
|
|
407
424
|
}
|
|
408
425
|
|
|
409
426
|
interface AnyQuery {
|
|
@@ -422,7 +439,7 @@ type QueryStart<
|
|
|
422
439
|
> = {
|
|
423
440
|
rule<RV extends AnyRuleValue>(
|
|
424
441
|
build: (r: QueryRuleScope<Rels, Classes>) => RV
|
|
425
|
-
): Query<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes
|
|
442
|
+
): Query<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes, HeadOf<RV>>
|
|
426
443
|
interior<const Builds extends readonly InteriorBuild<Rels, Classes>[]>(
|
|
427
444
|
name: string,
|
|
428
445
|
...builds: Builds
|
|
@@ -440,7 +457,7 @@ type QueryReachStart<
|
|
|
440
457
|
> = {
|
|
441
458
|
rule<RV extends AnyRuleValue>(
|
|
442
459
|
build: (r: QueryRuleScope<Rels, Classes>) => RV
|
|
443
|
-
): Query<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes
|
|
460
|
+
): Query<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes, HeadOf<RV>>
|
|
444
461
|
}
|
|
445
462
|
|
|
446
463
|
const termOps: TermOps = Object.freeze({
|
|
@@ -462,7 +479,9 @@ const termOps: TermOps = Object.freeze({
|
|
|
462
479
|
mean,
|
|
463
480
|
min,
|
|
464
481
|
max,
|
|
465
|
-
pack
|
|
482
|
+
pack,
|
|
483
|
+
intersection,
|
|
484
|
+
difference
|
|
466
485
|
})
|
|
467
486
|
|
|
468
487
|
interface RuleBuildState {
|
|
@@ -851,6 +870,8 @@ function findColumnOf(name: string, entry: unknown): FindColumn {
|
|
|
851
870
|
message: `find ${name}: a ${entry[term]} is not projectable — find takes variables, aggregates or Compute expressions`
|
|
852
871
|
})
|
|
853
872
|
}
|
|
873
|
+
if (isSegments(entry))
|
|
874
|
+
return Object.freeze({ name, entry: Object.freeze({ ...entry }), closed: undefined, slot: undefined })
|
|
854
875
|
if (isComputeExpr(entry)) {
|
|
855
876
|
return Object.freeze({
|
|
856
877
|
name,
|
|
@@ -926,6 +947,7 @@ function assertNumeric(where: string, position: string, ref: AnyVar): void {
|
|
|
926
947
|
*/
|
|
927
948
|
function findColumnSlotOf(context: ChainContext, column: FindColumn): ClassedField | undefined {
|
|
928
949
|
const entry = column.entry
|
|
950
|
+
if (entry.kind === "segments") return { field: segmentField(entry), class: undefined }
|
|
929
951
|
if (entry.kind === "var") {
|
|
930
952
|
return mintSlotOf(context, entry.over)
|
|
931
953
|
}
|
|
@@ -943,13 +965,21 @@ function findColumnSlotOf(context: ChainContext, column: FindColumn): ClassedFie
|
|
|
943
965
|
return { field: agg.over.field, class: undefined }
|
|
944
966
|
}
|
|
945
967
|
case "pack":
|
|
946
|
-
return {
|
|
968
|
+
return {
|
|
969
|
+
field: { kind: "interval", element: (agg.over.field as IntervalField).element, width: undefined },
|
|
970
|
+
class: undefined
|
|
971
|
+
}
|
|
947
972
|
}
|
|
948
973
|
}
|
|
949
974
|
|
|
950
975
|
function validateColumn(context: ChainContext, bound: ReadonlySet<AnyVar>, column: FindColumn): void {
|
|
951
976
|
const where = `${contextLabel(context)} find ${column.name}`
|
|
952
977
|
const entry = column.entry
|
|
978
|
+
if (entry.kind === "segments") {
|
|
979
|
+
assertBound(where, bound, entry.left)
|
|
980
|
+
assertBound(where, bound, entry.right)
|
|
981
|
+
return
|
|
982
|
+
}
|
|
953
983
|
if (entry.kind === "var") {
|
|
954
984
|
assertBound(where, bound, entry.over)
|
|
955
985
|
return
|
|
@@ -1071,6 +1101,13 @@ function completeRule(context: ChainContext, state: RuleBuildState, rawColumns:
|
|
|
1071
1101
|
if (rawColumns.length === 0) {
|
|
1072
1102
|
throw new AuthoringError({ message: `${label}: a find needs at least one entry` })
|
|
1073
1103
|
}
|
|
1104
|
+
const aggregates = rawColumns.flatMap((column) => (column.entry.kind === "aggregate" ? [column.entry.agg] : []))
|
|
1105
|
+
const packs = aggregates.filter((agg) => agg.op === "pack").length
|
|
1106
|
+
if (packs > 1) throw new AuthoringError({ message: "a query stage can pack one interval column" })
|
|
1107
|
+
if (packs !== 0 && aggregates.length !== packs)
|
|
1108
|
+
throw new AuthoringError({ message: "pack intervals and compute numeric aggregates in separate query stages" })
|
|
1109
|
+
if (rawColumns.some((c) => c.entry.kind === "segments") && aggregates.length !== 0)
|
|
1110
|
+
throw new AuthoringError({ message: "aggregate generated segments in a following query stage" })
|
|
1074
1111
|
const columns = rawColumns.map(function enrichColumn(column): FindColumn {
|
|
1075
1112
|
assertDeclarationOrderKey(`${label} find column`, column.name)
|
|
1076
1113
|
validateColumn(context, state.bound, column)
|
|
@@ -1507,6 +1544,7 @@ function renderClosedSlice(closed: AnyClosedRoster | undefined): string {
|
|
|
1507
1544
|
|
|
1508
1545
|
function headSignature(column: FindColumn): string {
|
|
1509
1546
|
const entry = column.entry
|
|
1547
|
+
if (entry.kind === "segments") return `${column.name}:compute`
|
|
1510
1548
|
if (entry.kind === "var") {
|
|
1511
1549
|
return `${column.name}:var`
|
|
1512
1550
|
}
|
|
@@ -1774,13 +1812,19 @@ function makeRawQuery(
|
|
|
1774
1812
|
return value
|
|
1775
1813
|
}
|
|
1776
1814
|
|
|
1777
|
-
function makeQuery<
|
|
1815
|
+
function makeQuery<
|
|
1816
|
+
Rels extends SchemaRelations,
|
|
1817
|
+
Row,
|
|
1818
|
+
P extends ParamsRecord,
|
|
1819
|
+
Classes extends SchemaClasses,
|
|
1820
|
+
Head extends HeadShape
|
|
1821
|
+
>(
|
|
1778
1822
|
theory: Schema<Rels, Classes>,
|
|
1779
1823
|
interiors: readonly InteriorData[],
|
|
1780
1824
|
rec: RecData | undefined,
|
|
1781
1825
|
rules: readonly RuleData[]
|
|
1782
|
-
): Query<Rels, Row, P, Classes> {
|
|
1783
|
-
return makeRawQuery(theory, interiors, rec, rules) as unknown as Query<Rels, Row, P, Classes>
|
|
1826
|
+
): Query<Rels, Row, P, Classes, Head> {
|
|
1827
|
+
return makeRawQuery(theory, interiors, rec, rules) as unknown as Query<Rels, Row, P, Classes, Head>
|
|
1784
1828
|
}
|
|
1785
1829
|
|
|
1786
1830
|
function collectInterior<Rels extends SchemaRelations, Classes extends SchemaClasses>(
|
|
@@ -1899,9 +1943,11 @@ function makeQueryStart<Rels extends SchemaRelations, Classes extends SchemaClas
|
|
|
1899
1943
|
},
|
|
1900
1944
|
rule<RV extends AnyRuleValue>(
|
|
1901
1945
|
build: (r: QueryRuleScope<Rels, Classes>) => RV
|
|
1902
|
-
): Query<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes
|
|
1946
|
+
): Query<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes, HeadOf<RV>> {
|
|
1903
1947
|
const built = build(makeQueryRuleScope<Rels, Classes>(theory, env))
|
|
1904
|
-
return makeQuery<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes
|
|
1948
|
+
return makeQuery<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes, HeadOf<RV>>(theory, interiors, undefined, [
|
|
1949
|
+
built.rule
|
|
1950
|
+
])
|
|
1905
1951
|
}
|
|
1906
1952
|
}
|
|
1907
1953
|
Object.freeze(start)
|
|
@@ -1917,9 +1963,11 @@ function makeQueryReachStart<Rels extends SchemaRelations, Classes extends Schem
|
|
|
1917
1963
|
const start = {
|
|
1918
1964
|
rule<RV extends AnyRuleValue>(
|
|
1919
1965
|
build: (r: QueryRuleScope<Rels, Classes>) => RV
|
|
1920
|
-
): Query<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes
|
|
1966
|
+
): Query<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes, HeadOf<RV>> {
|
|
1921
1967
|
const built = build(makeQueryRuleScope<Rels, Classes>(theory, env))
|
|
1922
|
-
return makeQuery<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes
|
|
1968
|
+
return makeQuery<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes, HeadOf<RV>>(theory, interiors, rec, [
|
|
1969
|
+
built.rule
|
|
1970
|
+
])
|
|
1923
1971
|
}
|
|
1924
1972
|
}
|
|
1925
1973
|
Object.freeze(start)
|
|
@@ -2162,34 +2210,15 @@ function lowerComputeNode(node: QueryNode, ids: VarIds): ScalarExprIr {
|
|
|
2162
2210
|
}
|
|
2163
2211
|
|
|
2164
2212
|
function lowerComputeGrammar(node: QueryNode, ids: VarIds): ScalarExprIr {
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
return { kind: "literal", value: literalWireOf(node.value) }
|
|
2170
|
-
case "negate":
|
|
2171
|
-
case "isNaN":
|
|
2172
|
-
case "isFinite":
|
|
2173
|
-
return { kind: node.kind, expr: lowerComputeGrammar(node.expr, ids) }
|
|
2174
|
-
case "cast":
|
|
2175
|
-
return { kind: "cast", cast: node.cast, expr: lowerComputeGrammar(node.expr, ids) }
|
|
2176
|
-
case "add":
|
|
2177
|
-
case "subtract":
|
|
2178
|
-
case "multiply":
|
|
2179
|
-
case "divide":
|
|
2180
|
-
return {
|
|
2181
|
-
kind: node.kind,
|
|
2182
|
-
left: lowerComputeGrammar(node.left, ids),
|
|
2183
|
-
right: lowerComputeGrammar(node.right, ids)
|
|
2184
|
-
}
|
|
2185
|
-
default:
|
|
2186
|
-
throw new AuthoringError({
|
|
2187
|
-
message: "query lowering: a query-var tree cannot carry a source-field leaf — use Compute over bound variables"
|
|
2188
|
-
})
|
|
2189
|
-
}
|
|
2213
|
+
return scalarWire(node, (leaf) => {
|
|
2214
|
+
if (leaf.kind !== "var") throw new AuthoringError({ message: "query expressions require bound variables" })
|
|
2215
|
+
return ids.of(leaf.ref as AnyVar)
|
|
2216
|
+
})
|
|
2190
2217
|
}
|
|
2191
2218
|
|
|
2192
2219
|
function lowerFind(entry: FindEntryData, ids: VarIds): FindTermIr {
|
|
2220
|
+
if (entry.kind === "segments")
|
|
2221
|
+
return { kind: entry.kind, op: entry.op, left: ids.of(entry.left), right: ids.of(entry.right) }
|
|
2193
2222
|
if (entry.kind === "var") {
|
|
2194
2223
|
return { kind: "var", var: ids.of(entry.over) }
|
|
2195
2224
|
}
|
|
@@ -2220,6 +2249,7 @@ function headOpOf(agg: AggData): HeadOpIr {
|
|
|
2220
2249
|
|
|
2221
2250
|
function headTermOf(column: FindColumn): HeadTermIr {
|
|
2222
2251
|
const entry = column.entry
|
|
2252
|
+
if (entry.kind === "segments") return { kind: "compute" }
|
|
2223
2253
|
if (entry.kind === "var") {
|
|
2224
2254
|
return { kind: "var" }
|
|
2225
2255
|
}
|
package/src/query/parse-ir.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
ScalarExprIr,
|
|
12
12
|
TermIr
|
|
13
13
|
} from "#native.ts"
|
|
14
|
+
import { roundingMode } from "#scalar.ts"
|
|
14
15
|
import { arrayValue as array, recordValue, valueDescriptor } from "#values.ts"
|
|
15
16
|
|
|
16
17
|
function fail(context: string, expected: string): never {
|
|
@@ -59,6 +60,7 @@ function scalar(context: string, input: unknown, depth = 1): ScalarExprIr {
|
|
|
59
60
|
case "literal":
|
|
60
61
|
recordValue(context, raw, ["kind", "value"])
|
|
61
62
|
return Object.freeze({ kind: raw.kind, value: valueDescriptor(`${context}.value`, raw.value) })
|
|
63
|
+
case "measure":
|
|
62
64
|
case "negate":
|
|
63
65
|
case "isNaN":
|
|
64
66
|
case "isFinite":
|
|
@@ -74,6 +76,15 @@ function scalar(context: string, input: unknown, depth = 1): ScalarExprIr {
|
|
|
74
76
|
left: scalar(`${context}.left`, raw.left, depth + 1),
|
|
75
77
|
right: scalar(`${context}.right`, raw.right, depth + 1)
|
|
76
78
|
})
|
|
79
|
+
case "mulDiv":
|
|
80
|
+
recordValue(context, raw, ["kind", "a", "b", "divisor", "rounding"])
|
|
81
|
+
return Object.freeze({
|
|
82
|
+
kind: raw.kind,
|
|
83
|
+
a: scalar(`${context}.a`, raw.a, depth + 1),
|
|
84
|
+
b: scalar(`${context}.b`, raw.b, depth + 1),
|
|
85
|
+
divisor: scalar(`${context}.divisor`, raw.divisor, depth + 1),
|
|
86
|
+
rounding: roundingMode(raw.rounding)
|
|
87
|
+
})
|
|
77
88
|
case "cast": {
|
|
78
89
|
recordValue(context, raw, ["kind", "cast", "expr"])
|
|
79
90
|
const cast = raw.cast
|
|
@@ -102,6 +113,16 @@ function headTerm(context: string, input: unknown): HeadTermIr {
|
|
|
102
113
|
|
|
103
114
|
function find(context: string, input: unknown): FindTermIr {
|
|
104
115
|
const raw = tagged(context, input)
|
|
116
|
+
if (raw.kind === "segments") {
|
|
117
|
+
recordValue(context, raw, ["kind", "op", "left", "right"])
|
|
118
|
+
if (raw.op !== "intersection" && raw.op !== "difference") return fail(context, "unknown segment operator")
|
|
119
|
+
return Object.freeze({
|
|
120
|
+
kind: raw.kind,
|
|
121
|
+
op: raw.op,
|
|
122
|
+
left: ordinal(context, raw.left),
|
|
123
|
+
right: ordinal(context, raw.right)
|
|
124
|
+
})
|
|
125
|
+
}
|
|
105
126
|
switch (raw.kind) {
|
|
106
127
|
case "var":
|
|
107
128
|
recordValue(context, raw, ["kind", "var"])
|
|
@@ -210,7 +231,8 @@ function align(context: string, head: readonly HeadTermIr[], rules: readonly Rul
|
|
|
210
231
|
if (rule.finds.length !== head.length) fail(`${context}.rules[${index}]`, "finds width does not match head width")
|
|
211
232
|
for (const [position, find] of rule.finds.entries()) {
|
|
212
233
|
const term = head[position]
|
|
213
|
-
const
|
|
234
|
+
const plainFamily = find.kind === "var" || find.kind === "compute" ? find.kind : "aggregate"
|
|
235
|
+
const family = find.kind === "segments" ? "compute" : plainFamily
|
|
214
236
|
if (term?.kind !== family)
|
|
215
237
|
fail(`${context}.rules[${index}].finds[${position}]`, "find family does not match head")
|
|
216
238
|
if (term.kind === "aggregate") {
|