@bjornpagen/bumbledb 1.2.1 → 1.3.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 +240 -9
- package/README.md +9 -6
- 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/changes.d.ts +0 -1
- package/dist/changes.d.ts.map +1 -1
- package/dist/changes.js +2 -8
- package/dist/changes.js.map +1 -1
- package/dist/codec.d.ts +8 -1
- package/dist/codec.d.ts.map +1 -1
- package/dist/codec.js +29 -2
- package/dist/codec.js.map +1 -1
- package/dist/db-native.d.ts +2 -9
- package/dist/db-native.d.ts.map +1 -1
- package/dist/db-native.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/internal/log.d.ts +4 -4
- package/dist/internal/log.d.ts.map +1 -1
- package/dist/internal/log.js +2 -2
- package/dist/internal/log.js.map +1 -1
- package/dist/native.d.ts +23 -9
- 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 +19 -4
- package/dist/query/compute.d.ts.map +1 -1
- package/dist/query/compute.js +17 -13
- 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 +14 -5
- package/dist/query/description.js.map +1 -1
- package/dist/query/find.d.ts +13 -6
- 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 +14 -2
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +64 -50
- package/dist/query/lower.js.map +1 -1
- package/dist/query/parse-ir.d.ts.map +1 -1
- package/dist/query/parse-ir.js +26 -3
- package/dist/query/parse-ir.js.map +1 -1
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +6 -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/runtime-codes.d.ts +1 -1
- package/dist/runtime-codes.d.ts.map +1 -1
- package/dist/runtime-codes.js +1 -0
- package/dist/runtime-codes.js.map +1 -1
- package/dist/runtime-errors.d.ts +23 -3
- package/dist/runtime-errors.d.ts.map +1 -1
- package/dist/runtime-errors.js +16 -3
- package/dist/runtime-errors.js.map +1 -1
- package/dist/runtime-native.d.ts +3 -8
- package/dist/runtime-native.d.ts.map +1 -1
- package/dist/runtime-native.js.map +1 -1
- package/dist/runtime.d.ts +0 -23
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +0 -36
- package/dist/runtime.js.map +1 -1
- package/dist/scalar.d.ts +46 -94
- package/dist/scalar.d.ts.map +1 -1
- package/dist/scalar.js +69 -156
- package/dist/scalar.js.map +1 -1
- package/dist/schema-file.d.ts +7 -0
- package/dist/schema-file.d.ts.map +1 -0
- package/dist/schema-file.js +24 -0
- package/dist/schema-file.js.map +1 -0
- package/pack-provenance.json +4 -4
- package/package.json +4 -4
- package/src/alternatives.ts +95 -0
- package/src/changes.ts +2 -8
- package/src/codec.ts +43 -3
- package/src/db-native.ts +2 -10
- package/src/index.ts +23 -6
- package/src/internal/log.ts +2 -4
- package/src/native.ts +21 -7
- package/src/query/atom.ts +2 -0
- package/src/query/compute.ts +46 -16
- package/src/query/description.ts +38 -8
- package/src/query/find.ts +37 -26
- package/src/query/lower.ts +89 -52
- package/src/query/parse-ir.ts +27 -3
- package/src/query/scope.ts +8 -1
- package/src/query/segments.ts +61 -0
- package/src/runtime-codes.ts +1 -0
- package/src/runtime-errors.ts +16 -3
- package/src/runtime-native.ts +1 -9
- package/src/runtime.ts +1 -70
- package/src/scalar.ts +138 -285
- package/src/schema-file.ts +38 -0
- package/dist/migration.d.ts +0 -31
- package/dist/migration.d.ts.map +0 -1
- package/dist/migration.js +0 -47
- package/dist/migration.js.map +0 -1
- package/src/migration.ts +0 -61
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")
|
|
@@ -248,17 +269,21 @@ function replayRule(
|
|
|
248
269
|
const value = input.value
|
|
249
270
|
switch (value.kind) {
|
|
250
271
|
case "u64":
|
|
251
|
-
return scalarLiteral(
|
|
272
|
+
return scalarLiteral({ u64: value.value })
|
|
252
273
|
case "i64":
|
|
253
|
-
return scalarLiteral(
|
|
274
|
+
return scalarLiteral({ i64: value.value })
|
|
254
275
|
case "f64":
|
|
255
|
-
return scalarLiteral(
|
|
276
|
+
return scalarLiteral({ f64: value.value })
|
|
256
277
|
case "bool":
|
|
257
|
-
return scalarLiteral(
|
|
278
|
+
return scalarLiteral({ bool: value.value })
|
|
258
279
|
default:
|
|
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 { BoolField, F64Field, I64Field, Infer, U64Field } from "#fields.ts"
|
|
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,17 +82,20 @@ 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
|
|
|
@@ -108,17 +112,24 @@ type ComputeFields = {
|
|
|
108
112
|
* produce bare values, matching findColumnSlotOf at runtime.
|
|
109
113
|
*/
|
|
110
114
|
type HeadRecordOf<Classes extends SchemaClasses, F extends FindShape> = {
|
|
111
|
-
readonly [K in keyof F]: F[K] extends
|
|
112
|
-
?
|
|
113
|
-
: F[K] extends
|
|
114
|
-
?
|
|
115
|
-
: F[K] extends
|
|
116
|
-
? { readonly field:
|
|
117
|
-
: F[K] extends
|
|
118
|
-
? { readonly field:
|
|
119
|
-
: F[K] extends Agg<
|
|
120
|
-
? { readonly field:
|
|
121
|
-
:
|
|
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
|
|
122
133
|
}
|
|
123
134
|
|
|
124
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,
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
u64 as u64Field
|
|
14
14
|
} from "#fields.ts"
|
|
15
15
|
import { snapshotData } from "#immutable.ts"
|
|
16
|
+
import type { Same } from "#judgment.ts"
|
|
16
17
|
import type { ClassRecordOf, SchemaClasses } from "#law.ts"
|
|
17
18
|
import type {
|
|
18
19
|
AtomIr,
|
|
@@ -87,7 +88,8 @@ import {
|
|
|
87
88
|
renderFieldKind,
|
|
88
89
|
term
|
|
89
90
|
} from "#query/scope.ts"
|
|
90
|
-
import {
|
|
91
|
+
import { difference, intersection, isSegments, segmentField } from "#query/segments.ts"
|
|
92
|
+
import { scalarWire } from "#scalar.ts"
|
|
91
93
|
import type { AnySchema, Schema, SchemaRelations } from "#schema.ts"
|
|
92
94
|
import { schemaDescriptor, schemasAgree } from "#schema.ts"
|
|
93
95
|
import { arrayValue, recordValue, taggedValueOf } from "#values.ts"
|
|
@@ -102,6 +104,25 @@ type HeadShape = Readonly<Record<string, ClassedField>> | undefined
|
|
|
102
104
|
|
|
103
105
|
type HeadOf<T> = InferredOf<T> extends { readonly head: infer H extends HeadShape } ? H : HeadShape
|
|
104
106
|
|
|
107
|
+
type HeadFieldMeet<A extends AnyField, B extends AnyField> = A extends IntervalField
|
|
108
|
+
? B extends IntervalField
|
|
109
|
+
? IntervalField<A["element"], Same<A["width"], B["width"]> extends true ? A["width"] : undefined>
|
|
110
|
+
: A
|
|
111
|
+
: A
|
|
112
|
+
|
|
113
|
+
/** The common refinements of the same output column across union arms. */
|
|
114
|
+
type HeadMeet<A extends HeadShape, B extends HeadShape> =
|
|
115
|
+
A extends Readonly<Record<string, ClassedField>>
|
|
116
|
+
? B extends Readonly<Record<string, ClassedField>>
|
|
117
|
+
? {
|
|
118
|
+
readonly [K in keyof A & keyof B]: {
|
|
119
|
+
readonly field: HeadFieldMeet<A[K]["field"], B[K]["field"]>
|
|
120
|
+
readonly class: Same<A[K]["class"], B[K]["class"]> extends true ? A[K]["class"] : undefined
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
: HeadShape
|
|
124
|
+
: HeadShape
|
|
125
|
+
|
|
105
126
|
interface RuleValue<Row, P extends ParamsRecord, Head extends HeadShape = undefined> {
|
|
106
127
|
readonly rule: RuleData
|
|
107
128
|
readonly [inferred]?: { readonly row: Row; readonly params: P; readonly head: Head }
|
|
@@ -147,6 +168,8 @@ interface TermOps {
|
|
|
147
168
|
readonly mean: typeof mean
|
|
148
169
|
readonly min: typeof min
|
|
149
170
|
readonly max: typeof max
|
|
171
|
+
readonly intersection: typeof intersection
|
|
172
|
+
readonly difference: typeof difference
|
|
150
173
|
readonly pack: typeof pack
|
|
151
174
|
}
|
|
152
175
|
|
|
@@ -409,7 +432,7 @@ interface Query<
|
|
|
409
432
|
|
|
410
433
|
rule<RV extends AnyRuleValue>(
|
|
411
434
|
build: (r: QueryRuleScope<Rels, Classes>) => RV
|
|
412
|
-
): Query<Rels, Row | RowOf<RV>, Flatten<Params & ParamsOf<RV>>, Classes, Head
|
|
435
|
+
): Query<Rels, Row | RowOf<RV>, Flatten<Params & ParamsOf<RV>>, Classes, HeadMeet<Head, HeadOf<RV>>>
|
|
413
436
|
|
|
414
437
|
/** A diagnostic name for composition/tracing — never a schema relation. */
|
|
415
438
|
named(label: string): Query<Rels, Row, Params, Classes, Head>
|
|
@@ -476,7 +499,9 @@ const termOps: TermOps = Object.freeze({
|
|
|
476
499
|
mean,
|
|
477
500
|
min,
|
|
478
501
|
max,
|
|
479
|
-
pack
|
|
502
|
+
pack,
|
|
503
|
+
intersection,
|
|
504
|
+
difference
|
|
480
505
|
})
|
|
481
506
|
|
|
482
507
|
interface RuleBuildState {
|
|
@@ -865,6 +890,8 @@ function findColumnOf(name: string, entry: unknown): FindColumn {
|
|
|
865
890
|
message: `find ${name}: a ${entry[term]} is not projectable — find takes variables, aggregates or Compute expressions`
|
|
866
891
|
})
|
|
867
892
|
}
|
|
893
|
+
if (isSegments(entry))
|
|
894
|
+
return Object.freeze({ name, entry: Object.freeze({ ...entry }), closed: undefined, slot: undefined })
|
|
868
895
|
if (isComputeExpr(entry)) {
|
|
869
896
|
return Object.freeze({
|
|
870
897
|
name,
|
|
@@ -940,6 +967,7 @@ function assertNumeric(where: string, position: string, ref: AnyVar): void {
|
|
|
940
967
|
*/
|
|
941
968
|
function findColumnSlotOf(context: ChainContext, column: FindColumn): ClassedField | undefined {
|
|
942
969
|
const entry = column.entry
|
|
970
|
+
if (entry.kind === "segments") return { field: segmentField(entry), class: undefined }
|
|
943
971
|
if (entry.kind === "var") {
|
|
944
972
|
return mintSlotOf(context, entry.over)
|
|
945
973
|
}
|
|
@@ -957,13 +985,21 @@ function findColumnSlotOf(context: ChainContext, column: FindColumn): ClassedFie
|
|
|
957
985
|
return { field: agg.over.field, class: undefined }
|
|
958
986
|
}
|
|
959
987
|
case "pack":
|
|
960
|
-
return {
|
|
988
|
+
return {
|
|
989
|
+
field: { kind: "interval", element: (agg.over.field as IntervalField).element, width: undefined },
|
|
990
|
+
class: undefined
|
|
991
|
+
}
|
|
961
992
|
}
|
|
962
993
|
}
|
|
963
994
|
|
|
964
995
|
function validateColumn(context: ChainContext, bound: ReadonlySet<AnyVar>, column: FindColumn): void {
|
|
965
996
|
const where = `${contextLabel(context)} find ${column.name}`
|
|
966
997
|
const entry = column.entry
|
|
998
|
+
if (entry.kind === "segments") {
|
|
999
|
+
assertBound(where, bound, entry.left)
|
|
1000
|
+
assertBound(where, bound, entry.right)
|
|
1001
|
+
return
|
|
1002
|
+
}
|
|
967
1003
|
if (entry.kind === "var") {
|
|
968
1004
|
assertBound(where, bound, entry.over)
|
|
969
1005
|
return
|
|
@@ -1085,6 +1121,13 @@ function completeRule(context: ChainContext, state: RuleBuildState, rawColumns:
|
|
|
1085
1121
|
if (rawColumns.length === 0) {
|
|
1086
1122
|
throw new AuthoringError({ message: `${label}: a find needs at least one entry` })
|
|
1087
1123
|
}
|
|
1124
|
+
const aggregates = rawColumns.flatMap((column) => (column.entry.kind === "aggregate" ? [column.entry.agg] : []))
|
|
1125
|
+
const packs = aggregates.filter((agg) => agg.op === "pack").length
|
|
1126
|
+
if (packs > 1) throw new AuthoringError({ message: "a query stage can pack one interval column" })
|
|
1127
|
+
if (packs !== 0 && aggregates.length !== packs)
|
|
1128
|
+
throw new AuthoringError({ message: "pack intervals and compute numeric aggregates in separate query stages" })
|
|
1129
|
+
if (rawColumns.some((c) => c.entry.kind === "segments") && aggregates.length !== 0)
|
|
1130
|
+
throw new AuthoringError({ message: "aggregate generated segments in a following query stage" })
|
|
1088
1131
|
const columns = rawColumns.map(function enrichColumn(column): FindColumn {
|
|
1089
1132
|
assertDeclarationOrderKey(`${label} find column`, column.name)
|
|
1090
1133
|
validateColumn(context, state.bound, column)
|
|
@@ -1519,19 +1562,8 @@ function renderClosedSlice(closed: AnyClosedRoster | undefined): string {
|
|
|
1519
1562
|
return closed === undefined ? "a bare value" : `a ${closed.name} reference`
|
|
1520
1563
|
}
|
|
1521
1564
|
|
|
1522
|
-
function
|
|
1523
|
-
|
|
1524
|
-
if (entry.kind === "var") {
|
|
1525
|
-
return `${column.name}:var`
|
|
1526
|
-
}
|
|
1527
|
-
if (entry.kind === "compute") {
|
|
1528
|
-
return `${column.name}:compute`
|
|
1529
|
-
}
|
|
1530
|
-
const agg = entry.agg
|
|
1531
|
-
if (agg.op === "fold") {
|
|
1532
|
-
return `${column.name}:${agg.fold}`
|
|
1533
|
-
}
|
|
1534
|
-
return `${column.name}:${agg.op}`
|
|
1565
|
+
function headOperation(column: FindColumn): HeadOpIr | undefined {
|
|
1566
|
+
return column.entry.kind === "aggregate" ? headOpOf(column.entry.agg) : undefined
|
|
1535
1567
|
}
|
|
1536
1568
|
|
|
1537
1569
|
function renderParamAnchor(roster: AnyClosedRoster | undefined): string {
|
|
@@ -1668,12 +1700,22 @@ function alignedHeadOf(label: string, rules: readonly RuleData[]): readonly Find
|
|
|
1668
1700
|
if (first === undefined) {
|
|
1669
1701
|
throw new AuthoringError({ message: `${label} needs at least one rule` })
|
|
1670
1702
|
}
|
|
1671
|
-
|
|
1703
|
+
// Bare u64 admits either carrier, but does not make two different
|
|
1704
|
+
// carriers compatible. Retain one original classed slot per column;
|
|
1705
|
+
// checking only the first arm (or its weakened meet) loses that proof.
|
|
1706
|
+
const classed = new Map<number, { readonly slot: ClassedField; readonly rule: number }>()
|
|
1672
1707
|
rules.forEach(function verifyHead(rule, index) {
|
|
1673
|
-
|
|
1674
|
-
|
|
1708
|
+
if (
|
|
1709
|
+
rule.finds.length !== first.finds.length ||
|
|
1710
|
+
rule.finds.some((column, position) => {
|
|
1711
|
+
const lead = first.finds[position]
|
|
1712
|
+
return lead === undefined || lead.name !== column.name || headOperation(lead) !== headOperation(column)
|
|
1713
|
+
})
|
|
1714
|
+
) {
|
|
1715
|
+
const render = (columns: readonly FindColumn[]) =>
|
|
1716
|
+
columns.map((column) => `${column.name}:${headOperation(column) ?? "project"}`).join(", ")
|
|
1675
1717
|
throw new AuthoringError({
|
|
1676
|
-
message: `every rule of ${label} derives the same head — rule 0 finds (${
|
|
1718
|
+
message: `every rule of ${label} derives the same head — rule 0 finds (${render(first.finds)}), rule ${index} finds (${render(rule.finds)})`
|
|
1677
1719
|
})
|
|
1678
1720
|
}
|
|
1679
1721
|
rule.finds.forEach(function verifyClosedSlice(column, position) {
|
|
@@ -1686,30 +1728,44 @@ function alignedHeadOf(label: string, rules: readonly RuleData[]): readonly Find
|
|
|
1686
1728
|
if (lead === undefined) {
|
|
1687
1729
|
return
|
|
1688
1730
|
}
|
|
1689
|
-
|
|
1731
|
+
const prior = classed.get(position) ?? (lead.slot === undefined ? undefined : { slot: lead.slot, rule: 0 })
|
|
1732
|
+
if (prior !== undefined && column.slot !== undefined && !headFieldJoins(prior.slot, column.slot)) {
|
|
1690
1733
|
throw new AuthoringError({
|
|
1691
|
-
message: `every rule of ${label} derives the same head — the head column ${lead.name} is bound at ${renderFieldKind(
|
|
1734
|
+
message: `every rule of ${label} derives the same head — the head column ${lead.name} is bound at ${renderFieldKind(prior.slot)} in rule ${prior.rule} but at ${renderFieldKind(column.slot)} in rule ${index} (a head column joins class-equal slots; same-wire u64 admits a bare side, and the merged head is bare)`
|
|
1692
1735
|
})
|
|
1693
1736
|
}
|
|
1737
|
+
if (column.slot?.class !== undefined && !classed.has(position)) {
|
|
1738
|
+
classed.set(position, { slot: column.slot, rule: index })
|
|
1739
|
+
}
|
|
1694
1740
|
})
|
|
1695
1741
|
})
|
|
1696
1742
|
const merged = first.finds.map(function meetColumn(lead, position) {
|
|
1697
1743
|
const slot = lead.slot
|
|
1698
|
-
if (slot === undefined
|
|
1744
|
+
if (slot === undefined) {
|
|
1699
1745
|
return lead
|
|
1700
1746
|
}
|
|
1701
1747
|
const demoted = rules.some(function bareElsewhere(rule) {
|
|
1702
1748
|
const column = rule.finds[position]
|
|
1703
1749
|
return column !== undefined && column.slot !== undefined && column.slot.class !== slot.class
|
|
1704
1750
|
})
|
|
1705
|
-
|
|
1751
|
+
const field = slot.field
|
|
1752
|
+
const widened =
|
|
1753
|
+
field.kind === "interval" &&
|
|
1754
|
+
rules.some((rule) => {
|
|
1755
|
+
const other = rule.finds[position]?.slot?.field
|
|
1756
|
+
return other?.kind === "interval" && other.width !== field.width
|
|
1757
|
+
})
|
|
1758
|
+
if (!demoted && !widened) {
|
|
1706
1759
|
return lead
|
|
1707
1760
|
}
|
|
1708
1761
|
return Object.freeze({
|
|
1709
1762
|
name: lead.name,
|
|
1710
1763
|
entry: lead.entry,
|
|
1711
1764
|
closed: lead.closed,
|
|
1712
|
-
slot: Object.freeze({
|
|
1765
|
+
slot: Object.freeze({
|
|
1766
|
+
field: widened ? Object.freeze({ ...field, width: undefined }) : field,
|
|
1767
|
+
class: demoted ? undefined : slot.class
|
|
1768
|
+
})
|
|
1713
1769
|
})
|
|
1714
1770
|
})
|
|
1715
1771
|
return Object.freeze(merged)
|
|
@@ -2186,34 +2242,14 @@ function lowerComputeNode(node: QueryNode, ids: VarIds): ScalarExprIr {
|
|
|
2186
2242
|
}
|
|
2187
2243
|
|
|
2188
2244
|
function lowerComputeGrammar(node: QueryNode, ids: VarIds): ScalarExprIr {
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
case "literal":
|
|
2193
|
-
return { kind: "literal", value: literalWireOf(node.value) }
|
|
2194
|
-
case "negate":
|
|
2195
|
-
case "isNaN":
|
|
2196
|
-
case "isFinite":
|
|
2197
|
-
return { kind: node.kind, expr: lowerComputeGrammar(node.expr, ids) }
|
|
2198
|
-
case "cast":
|
|
2199
|
-
return { kind: "cast", cast: node.cast, expr: lowerComputeGrammar(node.expr, ids) }
|
|
2200
|
-
case "add":
|
|
2201
|
-
case "subtract":
|
|
2202
|
-
case "multiply":
|
|
2203
|
-
case "divide":
|
|
2204
|
-
return {
|
|
2205
|
-
kind: node.kind,
|
|
2206
|
-
left: lowerComputeGrammar(node.left, ids),
|
|
2207
|
-
right: lowerComputeGrammar(node.right, ids)
|
|
2208
|
-
}
|
|
2209
|
-
default:
|
|
2210
|
-
throw new AuthoringError({
|
|
2211
|
-
message: "query lowering: a query-var tree cannot carry a source-field leaf — use Compute over bound variables"
|
|
2212
|
-
})
|
|
2213
|
-
}
|
|
2245
|
+
return scalarWire(node, (leaf) => {
|
|
2246
|
+
return ids.of(leaf.ref as AnyVar)
|
|
2247
|
+
})
|
|
2214
2248
|
}
|
|
2215
2249
|
|
|
2216
2250
|
function lowerFind(entry: FindEntryData, ids: VarIds): FindTermIr {
|
|
2251
|
+
if (entry.kind === "segments")
|
|
2252
|
+
return { kind: entry.kind, op: entry.op, left: ids.of(entry.left), right: ids.of(entry.right) }
|
|
2217
2253
|
if (entry.kind === "var") {
|
|
2218
2254
|
return { kind: "var", var: ids.of(entry.over) }
|
|
2219
2255
|
}
|
|
@@ -2244,6 +2280,7 @@ function headOpOf(agg: AggData): HeadOpIr {
|
|
|
2244
2280
|
|
|
2245
2281
|
function headTermOf(column: FindColumn): HeadTermIr {
|
|
2246
2282
|
const entry = column.entry
|
|
2283
|
+
if (entry.kind === "segments") return { kind: "compute" }
|
|
2247
2284
|
if (entry.kind === "var") {
|
|
2248
2285
|
return { kind: "var" }
|
|
2249
2286
|
}
|
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,8 +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
|
|
214
|
-
if (term
|
|
234
|
+
const projects = find.kind === "var" || find.kind === "compute" || find.kind === "segments"
|
|
235
|
+
if (term === undefined || (term.kind === "aggregate") === projects)
|
|
215
236
|
fail(`${context}.rules[${index}].finds[${position}]`, "find family does not match head")
|
|
216
237
|
if (term.kind === "aggregate") {
|
|
217
238
|
const op = find.kind === "aggregate" ? find.op.kind : find.kind
|
|
@@ -258,7 +279,10 @@ function parseQueryIr(input: unknown): ParsedQuery {
|
|
|
258
279
|
if (rec.length === 0) return fail("parseQueryIr", "rec step is empty")
|
|
259
280
|
align("rec base", headRec, base)
|
|
260
281
|
align("rec step", headRec, rec)
|
|
261
|
-
if (
|
|
282
|
+
if (
|
|
283
|
+
headRec.some((term) => term.kind !== "var") ||
|
|
284
|
+
[...base, ...rec].some((rule) => rule.negated.length !== 0 || rule.finds.some((find) => find.kind !== "var"))
|
|
285
|
+
)
|
|
262
286
|
return fail("query.rec", "recursion requires projection-only heads and no negation")
|
|
263
287
|
owned = { kind: raw.kind, interiors, head, rules, rec: Object.freeze({ head: headRec, base, rec }) }
|
|
264
288
|
}
|
package/src/query/scope.ts
CHANGED
|
@@ -369,7 +369,14 @@ function fieldAntiJoins(a: ClassedField, b: ClassedField): boolean {
|
|
|
369
369
|
* joins cannot inherit provenance a rule never proved.
|
|
370
370
|
*/
|
|
371
371
|
function headFieldJoins(a: ClassedField, b: ClassedField): boolean {
|
|
372
|
-
return
|
|
372
|
+
return (
|
|
373
|
+
fieldJoins(a, b) ||
|
|
374
|
+
u64BareWirePair(a, b) ||
|
|
375
|
+
(a.class === b.class &&
|
|
376
|
+
a.field.kind === "interval" &&
|
|
377
|
+
b.field.kind === "interval" &&
|
|
378
|
+
a.field.element === b.field.element)
|
|
379
|
+
)
|
|
373
380
|
}
|
|
374
381
|
|
|
375
382
|
function renderFieldKind(slot: ClassedField): string {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { AuthoringError } from "#errors.ts"
|
|
2
|
+
import type { IntervalElementKind, IntervalField } from "#fields.ts"
|
|
3
|
+
import { type AnyVar, isTerm, term } from "#query/scope.ts"
|
|
4
|
+
import { recordValue } from "#values.ts"
|
|
5
|
+
|
|
6
|
+
type IntervalVar<E extends IntervalElementKind = IntervalElementKind> = AnyVar & { readonly field: IntervalField<E> }
|
|
7
|
+
type SegmentOp = "intersection" | "difference"
|
|
8
|
+
|
|
9
|
+
/** A relational find output, using the native binary segment operator directly. */
|
|
10
|
+
interface Segments<E extends IntervalElementKind = IntervalElementKind> {
|
|
11
|
+
readonly kind: "segments"
|
|
12
|
+
readonly op: SegmentOp
|
|
13
|
+
readonly left: IntervalVar<E>
|
|
14
|
+
readonly right: IntervalVar<E>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function segments<E extends IntervalElementKind>(
|
|
18
|
+
op: SegmentOp,
|
|
19
|
+
left: IntervalVar<E>,
|
|
20
|
+
right: IntervalVar<NoInfer<E>>
|
|
21
|
+
): Segments<E> {
|
|
22
|
+
if (
|
|
23
|
+
!isTerm(left) ||
|
|
24
|
+
left[term] !== "var" ||
|
|
25
|
+
!isTerm(right) ||
|
|
26
|
+
right[term] !== "var" ||
|
|
27
|
+
left.field.kind !== "interval" ||
|
|
28
|
+
right.field.kind !== "interval" ||
|
|
29
|
+
left.field.element !== right.field.element
|
|
30
|
+
)
|
|
31
|
+
throw new AuthoringError({ message: `${op}: expected interval variables of the same element kind` })
|
|
32
|
+
return Object.freeze({ kind: "segments", op, left, right })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function intersection<E extends IntervalElementKind>(
|
|
36
|
+
left: IntervalVar<E>,
|
|
37
|
+
right: IntervalVar<NoInfer<E>>
|
|
38
|
+
): Segments<E> {
|
|
39
|
+
return segments("intersection", left, right)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function difference<E extends IntervalElementKind>(left: IntervalVar<E>, right: IntervalVar<NoInfer<E>>): Segments<E> {
|
|
43
|
+
return segments("difference", left, right)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isSegments(input: unknown): input is Segments {
|
|
47
|
+
if (typeof input !== "object" || input === null || !("kind" in input) || input.kind !== "segments") return false
|
|
48
|
+
recordValue("segments", input, ["kind", "op", "left", "right"])
|
|
49
|
+
const value = input as Segments
|
|
50
|
+
if (value.op !== "intersection" && value.op !== "difference")
|
|
51
|
+
throw new AuthoringError({ message: "unknown segment operator" })
|
|
52
|
+
segments(value.op, value.left, value.right)
|
|
53
|
+
return true
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function segmentField(value: Segments): IntervalField<IntervalElementKind, undefined> {
|
|
57
|
+
return Object.freeze({ kind: "interval", element: value.left.field.element, width: undefined })
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type { IntervalVar, SegmentOp, Segments }
|
|
61
|
+
export { difference, intersection, isSegments, segmentField }
|