@bjornpagen/bumbledb 1.3.0 → 2.0.0-rc.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 +224 -95
- package/README.md +114 -45
- package/dist/alternatives.d.ts +5 -6
- package/dist/alternatives.d.ts.map +1 -1
- package/dist/alternatives.js +5 -6
- package/dist/alternatives.js.map +1 -1
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +85 -0
- package/dist/bin.js.map +1 -0
- package/dist/capacity.d.ts +14 -17
- package/dist/capacity.d.ts.map +1 -1
- package/dist/capacity.js +7 -7
- package/dist/capacity.js.map +1 -1
- package/dist/changes.d.ts +32 -41
- package/dist/changes.d.ts.map +1 -1
- package/dist/changes.js +86 -110
- package/dist/changes.js.map +1 -1
- package/dist/closed.d.ts +2 -2
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +5 -5
- package/dist/closed.js.map +1 -1
- package/dist/codec.d.ts +20 -27
- package/dist/codec.d.ts.map +1 -1
- package/dist/codec.js +48 -22
- package/dist/codec.js.map +1 -1
- package/dist/compile.d.ts +26 -50
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +75 -64
- package/dist/compile.js.map +1 -1
- package/dist/database/database.d.ts +98 -0
- package/dist/database/database.d.ts.map +1 -0
- package/dist/database/database.js +249 -0
- package/dist/database/database.js.map +1 -0
- package/dist/database/driver.d.ts +40 -0
- package/dist/database/driver.d.ts.map +1 -0
- package/dist/database/driver.js +68 -0
- package/dist/database/driver.js.map +1 -0
- package/dist/database/fs.d.ts +12 -0
- package/dist/database/fs.d.ts.map +1 -0
- package/dist/database/fs.js +120 -0
- package/dist/database/fs.js.map +1 -0
- package/dist/database/generate.d.ts +26 -0
- package/dist/database/generate.d.ts.map +1 -0
- package/dist/database/generate.js +102 -0
- package/dist/database/generate.js.map +1 -0
- package/dist/database/hosted.d.ts +32 -0
- package/dist/database/hosted.d.ts.map +1 -0
- package/dist/database/hosted.js +44 -0
- package/dist/database/hosted.js.map +1 -0
- package/dist/database/io.d.ts +94 -0
- package/dist/database/io.d.ts.map +1 -0
- package/dist/database/io.js +35 -0
- package/dist/database/io.js.map +1 -0
- package/dist/database/mem.d.ts +26 -0
- package/dist/database/mem.d.ts.map +1 -0
- package/dist/database/mem.js +67 -0
- package/dist/database/mem.js.map +1 -0
- package/dist/database/migration.d.ts +55 -0
- package/dist/database/migration.d.ts.map +1 -0
- package/dist/database/migration.js +46 -0
- package/dist/database/migration.js.map +1 -0
- package/dist/database/s3.d.ts +36 -0
- package/dist/database/s3.d.ts.map +1 -0
- package/dist/database/s3.js +141 -0
- package/dist/database/s3.js.map +1 -0
- package/dist/db.d.ts +44 -128
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +163 -287
- package/dist/db.js.map +1 -1
- package/dist/engine.d.ts +7 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +2 -0
- package/dist/engine.js.map +1 -0
- package/dist/errors.d.ts +152 -46
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +107 -19
- package/dist/errors.js.map +1 -1
- package/dist/face.d.ts +5 -5
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +6 -6
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +4 -5
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +3 -3
- package/dist/fields.js.map +1 -1
- package/dist/index.d.ts +60 -58
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -29
- package/dist/index.js.map +1 -1
- package/dist/judgment.d.ts +1 -1
- package/dist/judgment.js +1 -1
- package/dist/law.d.ts +16 -68
- package/dist/law.d.ts.map +1 -1
- package/dist/law.js +3 -3
- package/dist/law.js.map +1 -1
- package/dist/lower.d.ts +3 -3
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +35 -39
- package/dist/lower.js.map +1 -1
- package/dist/native/addon.d.ts +23 -0
- package/dist/native/addon.d.ts.map +1 -0
- package/dist/native/addon.js +15 -0
- package/dist/native/addon.js.map +1 -0
- package/dist/native/binding.d.ts +996 -0
- package/dist/native/json.d.ts +14 -0
- package/dist/native/json.d.ts.map +1 -0
- package/dist/native/json.js +73 -0
- package/dist/native/json.js.map +1 -0
- package/dist/native/load.d.ts +9 -0
- package/dist/native/load.d.ts.map +1 -0
- package/dist/native/load.js +30 -0
- package/dist/native/load.js.map +1 -0
- package/dist/native/op.d.ts +29 -0
- package/dist/native/op.d.ts.map +1 -0
- package/dist/native/op.js +50 -0
- package/dist/native/op.js.map +1 -0
- package/dist/query/atom.d.ts +13 -14
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +2 -2
- package/dist/query/atom.js.map +1 -1
- package/dist/query/compute.d.ts +7 -7
- package/dist/query/compute.d.ts.map +1 -1
- package/dist/query/compute.js +7 -16
- package/dist/query/compute.js.map +1 -1
- package/dist/query/find.d.ts +8 -8
- package/dist/query/find.d.ts.map +1 -1
- package/dist/query/find.js +1 -1
- package/dist/query/lower.d.ts +31 -17
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +260 -368
- package/dist/query/lower.js.map +1 -1
- package/dist/query/run.d.ts +5 -5
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +32 -15
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +20 -28
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +13 -8
- package/dist/query/scope.js.map +1 -1
- package/dist/query/segments.d.ts +2 -2
- package/dist/query/segments.d.ts.map +1 -1
- package/dist/query/segments.js +3 -3
- package/dist/query/segments.js.map +1 -1
- package/dist/relation.d.ts +1 -1
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +4 -4
- package/dist/relation.js.map +1 -1
- package/dist/result.d.ts +15 -22
- package/dist/result.d.ts.map +1 -1
- package/dist/result.js +30 -39
- package/dist/result.js.map +1 -1
- package/dist/rows.d.ts +9 -33
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +8 -12
- package/dist/rows.js.map +1 -1
- package/dist/runtime.d.ts +17 -43
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +41 -241
- package/dist/runtime.js.map +1 -1
- package/dist/scalar.d.ts +36 -106
- package/dist/scalar.d.ts.map +1 -1
- package/dist/scalar.js +36 -185
- package/dist/scalar.js.map +1 -1
- package/dist/schema.d.ts +7 -18
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +18 -228
- package/dist/schema.js.map +1 -1
- package/dist/selection.d.ts +4 -4
- package/dist/selection.d.ts.map +1 -1
- package/dist/selection.js +5 -5
- package/dist/selection.js.map +1 -1
- package/dist/shape.d.ts +5 -5
- package/dist/shape.d.ts.map +1 -1
- package/dist/spec.d.ts +7 -79
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js.map +1 -1
- package/dist/statements.d.ts +3 -3
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +10 -10
- package/dist/statements.js.map +1 -1
- package/dist/uuid.d.ts +1 -1
- package/dist/uuid.d.ts.map +1 -1
- package/dist/uuid.js +1 -1
- package/dist/uuid.js.map +1 -1
- package/dist/values.d.ts +2 -2
- package/dist/values.d.ts.map +1 -1
- package/dist/values.js +2 -2
- package/dist/values.js.map +1 -1
- package/package.json +18 -18
- package/src/alternatives.ts +7 -8
- package/src/bin.ts +82 -0
- package/src/capacity.ts +20 -23
- package/src/changes.ts +134 -197
- package/src/closed.ts +5 -5
- package/src/codec.ts +77 -51
- package/src/compile.ts +91 -113
- package/src/database/database.ts +413 -0
- package/src/database/driver.ts +134 -0
- package/src/database/fs.ts +119 -0
- package/src/database/generate.ts +116 -0
- package/src/database/hosted.ts +102 -0
- package/src/database/io.ts +134 -0
- package/src/database/mem.ts +113 -0
- package/src/database/migration.ts +107 -0
- package/src/database/s3.ts +231 -0
- package/src/db.ts +290 -547
- package/src/engine.ts +14 -0
- package/src/errors.ts +118 -39
- package/src/face.ts +9 -9
- package/src/fields.ts +6 -7
- package/src/index.ts +102 -109
- package/src/judgment.ts +1 -1
- package/src/law.ts +18 -70
- package/src/lower.ts +61 -72
- package/src/native/addon.ts +54 -0
- package/src/native/binding.d.ts +996 -0
- package/src/native/json.ts +100 -0
- package/src/native/load.ts +31 -0
- package/src/native/op.ts +73 -0
- package/src/query/atom.ts +15 -16
- package/src/query/compute.ts +13 -22
- package/src/query/find.ts +8 -8
- package/src/query/lower.ts +323 -414
- package/src/query/run.ts +42 -21
- package/src/query/scope.ts +37 -38
- package/src/query/segments.ts +4 -4
- package/src/relation.ts +4 -4
- package/src/result.ts +53 -80
- package/src/rows.ts +18 -46
- package/src/runtime.ts +50 -346
- package/src/scalar.ts +76 -336
- package/src/schema.ts +23 -284
- package/src/selection.ts +6 -6
- package/src/shape.ts +5 -5
- package/src/spec.ts +7 -85
- package/src/statements.ts +11 -11
- package/src/uuid.ts +1 -1
- package/src/values.ts +4 -4
- package/dist/close.d.ts +0 -24
- package/dist/close.d.ts.map +0 -1
- package/dist/close.js +0 -34
- package/dist/close.js.map +0 -1
- package/dist/db-native.d.ts +0 -196
- package/dist/db-native.d.ts.map +0 -1
- package/dist/db-native.js +0 -13
- package/dist/db-native.js.map +0 -1
- package/dist/internal/log.d.ts +0 -26
- package/dist/internal/log.d.ts.map +0 -1
- package/dist/internal/log.js +0 -12
- package/dist/internal/log.js.map +0 -1
- 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/dist/native.d.ts +0 -562
- package/dist/native.d.ts.map +0 -1
- package/dist/native.js +0 -123
- package/dist/native.js.map +0 -1
- package/dist/query/description.d.ts +0 -41
- package/dist/query/description.d.ts.map +0 -1
- package/dist/query/description.js +0 -311
- package/dist/query/description.js.map +0 -1
- package/dist/query/parse-ir.d.ts +0 -10
- package/dist/query/parse-ir.d.ts.map +0 -1
- package/dist/query/parse-ir.js +0 -280
- package/dist/query/parse-ir.js.map +0 -1
- package/dist/runtime-codes.d.ts +0 -2
- package/dist/runtime-codes.d.ts.map +0 -1
- package/dist/runtime-codes.js +0 -18
- package/dist/runtime-codes.js.map +0 -1
- package/dist/runtime-errors.d.ts +0 -118
- package/dist/runtime-errors.d.ts.map +0 -1
- package/dist/runtime-errors.js +0 -98
- package/dist/runtime-errors.js.map +0 -1
- package/dist/runtime-native.d.ts +0 -141
- package/dist/runtime-native.d.ts.map +0 -1
- package/dist/runtime-native.js +0 -5
- package/dist/runtime-native.js.map +0 -1
- package/pack-provenance.json +0 -7
- package/src/close.ts +0 -53
- package/src/db-native.ts +0 -243
- package/src/internal/log.ts +0 -48
- package/src/migration.ts +0 -61
- package/src/native.ts +0 -736
- package/src/query/description.ts +0 -424
- package/src/query/parse-ir.ts +0 -291
- package/src/runtime-codes.ts +0 -17
- package/src/runtime-errors.ts +0 -109
- package/src/runtime-native.ts +0 -189
package/src/query/lower.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { membersAgree, sealedFieldsOf } from "
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
1
|
+
import { membersAgree, sealedFieldsOf } from "../closed.ts"
|
|
2
|
+
import { compiledOf } from "../compile.ts"
|
|
3
|
+
import { AuthoringError, internalError } from "../errors.ts"
|
|
4
|
+
import type { AnyClosedRoster, AnyField, IntervalField } from "../fields.ts"
|
|
4
5
|
import {
|
|
5
6
|
assertDeclarationOrderKey,
|
|
6
7
|
f64 as f64Field,
|
|
@@ -11,23 +12,30 @@ import {
|
|
|
11
12
|
rosterOf,
|
|
12
13
|
rostersAgree,
|
|
13
14
|
u64 as u64Field
|
|
14
|
-
} from "
|
|
15
|
-
import { snapshotData } from "
|
|
16
|
-
import type {
|
|
15
|
+
} from "../fields.ts"
|
|
16
|
+
import { snapshotData } from "../immutable.ts"
|
|
17
|
+
import type { Same } from "../judgment.ts"
|
|
18
|
+
import type { ClassRecordOf, SchemaClasses } from "../law.ts"
|
|
19
|
+
import type { QueryRef } from "../native/addon.ts"
|
|
20
|
+
import { addon } from "../native/addon.ts"
|
|
17
21
|
import type {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
} from "
|
|
22
|
+
AtomIn,
|
|
23
|
+
BindingIn,
|
|
24
|
+
ConditionIn,
|
|
25
|
+
FindTermIn,
|
|
26
|
+
HeadTermIn,
|
|
27
|
+
QueryDiagnostic,
|
|
28
|
+
QueryIn,
|
|
29
|
+
RecStepIn,
|
|
30
|
+
RuleIn,
|
|
31
|
+
TermIn
|
|
32
|
+
} from "../native/binding.d.ts"
|
|
33
|
+
import { valueIn } from "../native/json.ts"
|
|
34
|
+
import { scalarWire } from "../scalar.ts"
|
|
35
|
+
import type { AnySchema, Schema, SchemaRelations } from "../schema.ts"
|
|
36
|
+
import { schemaDescriptor, schemasAgree } from "../schema.ts"
|
|
37
|
+
import type { QueryParam, TaggedValue } from "../spec.ts"
|
|
38
|
+
import { arrayValue, recordValue, taggedValueOf } from "../values.ts"
|
|
31
39
|
import type {
|
|
32
40
|
AggData,
|
|
33
41
|
AnyCond,
|
|
@@ -36,7 +44,6 @@ import type {
|
|
|
36
44
|
BindParamsShape,
|
|
37
45
|
CheckBindings,
|
|
38
46
|
CheckCond,
|
|
39
|
-
CmpData,
|
|
40
47
|
CmpKind,
|
|
41
48
|
CmpTermData,
|
|
42
49
|
CondData,
|
|
@@ -54,13 +61,11 @@ import type {
|
|
|
54
61
|
RecHead,
|
|
55
62
|
RuleData,
|
|
56
63
|
RuleItem
|
|
57
|
-
} from "
|
|
58
|
-
import { allen, and, eq, ge, gt, le, lt, ne, not, or, pointIn } from "
|
|
59
|
-
import
|
|
60
|
-
import {
|
|
61
|
-
import
|
|
62
|
-
import { count, max, mean, min, pack, sum } from "#query/find.ts"
|
|
63
|
-
import { parseQueryIr } from "#query/parse-ir.ts"
|
|
64
|
+
} from "./atom.ts"
|
|
65
|
+
import { allen, and, eq, ge, gt, le, lt, ne, not, or, pointIn } from "./atom.ts"
|
|
66
|
+
import { computeFieldOf, isComputeExpr, MAX_COMPUTE_DEPTH } from "./compute.ts"
|
|
67
|
+
import type { CheckFind, CheckRecFind, FindShape, HeadRecordOf, RowOfFind } from "./find.ts"
|
|
68
|
+
import { count, max, mean, min, pack, sum } from "./find.ts"
|
|
64
69
|
import type {
|
|
65
70
|
AnyVar,
|
|
66
71
|
ClassedField,
|
|
@@ -71,7 +76,7 @@ import type {
|
|
|
71
76
|
ParamsRecord,
|
|
72
77
|
ShapeOf,
|
|
73
78
|
VarsOf
|
|
74
|
-
} from "
|
|
79
|
+
} from "./scope.ts"
|
|
75
80
|
import {
|
|
76
81
|
fieldAntiJoins,
|
|
77
82
|
fieldJoins,
|
|
@@ -86,12 +91,8 @@ import {
|
|
|
86
91
|
makeSetParam,
|
|
87
92
|
renderFieldKind,
|
|
88
93
|
term
|
|
89
|
-
} from "
|
|
90
|
-
import { difference, intersection, isSegments, segmentField } from "
|
|
91
|
-
import { scalarWire } from "#scalar.ts"
|
|
92
|
-
import type { AnySchema, Schema, SchemaRelations } from "#schema.ts"
|
|
93
|
-
import { schemaDescriptor, schemasAgree } from "#schema.ts"
|
|
94
|
-
import { arrayValue, recordValue, taggedValueOf } from "#values.ts"
|
|
94
|
+
} from "./scope.ts"
|
|
95
|
+
import { difference, intersection, isSegments, segmentField } from "./segments.ts"
|
|
95
96
|
|
|
96
97
|
type QueryRelation<Rels extends SchemaRelations> = Extract<Rels[keyof Rels], MatchOwner>
|
|
97
98
|
|
|
@@ -103,6 +104,25 @@ type HeadShape = Readonly<Record<string, ClassedField>> | undefined
|
|
|
103
104
|
|
|
104
105
|
type HeadOf<T> = InferredOf<T> extends { readonly head: infer H extends HeadShape } ? H : HeadShape
|
|
105
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
|
+
|
|
106
126
|
interface RuleValue<Row, P extends ParamsRecord, Head extends HeadShape = undefined> {
|
|
107
127
|
readonly rule: RuleData
|
|
108
128
|
readonly [inferred]?: { readonly row: Row; readonly params: P; readonly head: Head }
|
|
@@ -299,8 +319,8 @@ interface InteriorRuleChain<
|
|
|
299
319
|
bindings: B & CheckInteriorBindings<B>
|
|
300
320
|
): InteriorRuleChain<Rels, P, Classes>
|
|
301
321
|
/**
|
|
302
|
-
* Nonrecursive derived stages emit aggregate/computed outputs too
|
|
303
|
-
*
|
|
322
|
+
* Nonrecursive derived stages emit aggregate/computed outputs too; only
|
|
323
|
+
* the RECURSIVE head stays projection-only.
|
|
304
324
|
*/
|
|
305
325
|
find<const F extends Readonly<Record<string, AnyVar>>>(
|
|
306
326
|
entries: F
|
|
@@ -325,7 +345,7 @@ interface RecRuleScope<Rels extends SchemaRelations, Classes extends SchemaClass
|
|
|
325
345
|
relation: R,
|
|
326
346
|
bindings: B & CheckBindings<Classes, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>
|
|
327
347
|
): RecRuleChain<Rels, BindParamsShape<MatchFields<R>, B>, Classes>
|
|
328
|
-
/** Frozen finite nonrecursive imports may feed base/step
|
|
348
|
+
/** Frozen finite nonrecursive imports may feed base/step. */
|
|
329
349
|
match<Q extends AnyQuery, const B extends ImportMatchShape<Q>>(
|
|
330
350
|
imported: Q,
|
|
331
351
|
bindings: B
|
|
@@ -412,7 +432,7 @@ interface Query<
|
|
|
412
432
|
|
|
413
433
|
rule<RV extends AnyRuleValue>(
|
|
414
434
|
build: (r: QueryRuleScope<Rels, Classes>) => RV
|
|
415
|
-
): 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>>>
|
|
416
436
|
|
|
417
437
|
/** A diagnostic name for composition/tracing — never a schema relation. */
|
|
418
438
|
named(label: string): Query<Rels, Row, Params, Classes, Head>
|
|
@@ -893,51 +913,6 @@ function findColumnOf(name: string, entry: unknown): FindColumn {
|
|
|
893
913
|
})
|
|
894
914
|
}
|
|
895
915
|
|
|
896
|
-
/**
|
|
897
|
-
* The orderable ban's pointed refusal
|
|
898
|
-
* § orderability): a closed reference is equality-and-membership only.
|
|
899
|
-
*/
|
|
900
|
-
function closedOrderError(context: string, position: string, vocabulary: string): Error {
|
|
901
|
-
return new AuthoringError({
|
|
902
|
-
message: `${context}: ${position} is a ${vocabulary} reference — declaration order is an accident, not semantics: vocabularies do not order (equality, membership, and counting remain)`
|
|
903
|
-
})
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
function isOrderOp(op: CmpKind | "binding"): op is "lt" | "le" | "gt" | "ge" | "pointIn" {
|
|
907
|
-
return op === "lt" || op === "le" || op === "gt" || op === "ge" || op === "pointIn"
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
function assertBound(where: string, bound: ReadonlySet<AnyVar>, ref: AnyVar): void {
|
|
911
|
-
if (!bound.has(ref)) {
|
|
912
|
-
throw new AuthoringError({
|
|
913
|
-
message: `${where}: the variable ${ref.label} is not bound by a relation atom of the rule`
|
|
914
|
-
})
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
function assertInterval(where: string, ref: AnyVar): void {
|
|
919
|
-
if (ref.field.kind !== "interval") {
|
|
920
|
-
throw new AuthoringError({
|
|
921
|
-
message: `${where}: ${ref.label} is not interval-typed — the measure is defined over interval-typed variables only`
|
|
922
|
-
})
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
function assertNotClosed(where: string, position: string, ref: AnyVar): void {
|
|
927
|
-
const roster = rosterOf(ref.field)
|
|
928
|
-
if (roster !== undefined) {
|
|
929
|
-
throw closedOrderError(where, `${position} ${ref.label}`, roster.name)
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
function assertNumeric(where: string, position: string, ref: AnyVar): void {
|
|
934
|
-
if (ref.field.kind !== "u64" && ref.field.kind !== "i64" && ref.field.kind !== "f64") {
|
|
935
|
-
throw new AuthoringError({
|
|
936
|
-
message: `${where}: ${position} ${ref.label} is ${ref.field.kind}, not numeric — a fold reads u64/i64/f64 only`
|
|
937
|
-
})
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
|
|
941
916
|
/**
|
|
942
917
|
* The head column's slot: a projected variable keeps its mint slot; an
|
|
943
918
|
* aggregate output is a NEW derived scalar — its descriptor follows the
|
|
@@ -972,80 +947,24 @@ function findColumnSlotOf(context: ChainContext, column: FindColumn): ClassedFie
|
|
|
972
947
|
}
|
|
973
948
|
}
|
|
974
949
|
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
assertBound(where, bound, entry.left)
|
|
980
|
-
assertBound(where, bound, entry.right)
|
|
981
|
-
return
|
|
982
|
-
}
|
|
983
|
-
if (entry.kind === "var") {
|
|
984
|
-
assertBound(where, bound, entry.over)
|
|
985
|
-
return
|
|
986
|
-
}
|
|
987
|
-
if (entry.kind === "compute") {
|
|
988
|
-
// Kind agreement, closed-reference and depth walls hold at
|
|
989
|
-
// construction (#query/compute.ts); boundness is a RULE property,
|
|
990
|
-
// judged here like every other term position.
|
|
991
|
-
for (const ref of computeVarsOf(entry.expr)) {
|
|
992
|
-
assertBound(where, bound, ref)
|
|
993
|
-
}
|
|
994
|
-
return
|
|
995
|
-
}
|
|
996
|
-
const agg = entry.agg
|
|
997
|
-
switch (agg.op) {
|
|
998
|
-
case "count":
|
|
999
|
-
return
|
|
1000
|
-
case "fold": {
|
|
1001
|
-
assertBound(where, bound, agg.over)
|
|
1002
|
-
assertNotClosed(where, `the ${agg.fold} input`, agg.over)
|
|
1003
|
-
assertNumeric(where, `the ${agg.fold} input`, agg.over)
|
|
1004
|
-
if (agg.fold === "mean" && agg.over.field.kind !== "f64") {
|
|
1005
|
-
throw new AuthoringError({ message: `${where}: mean requires an f64 input; cast integers explicitly` })
|
|
1006
|
-
}
|
|
1007
|
-
return
|
|
1008
|
-
}
|
|
1009
|
-
case "pack":
|
|
1010
|
-
assertBound(where, bound, agg.over)
|
|
1011
|
-
assertInterval(where, agg.over)
|
|
1012
|
-
return
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
function validateCond(context: ChainContext, bound: ReadonlySet<AnyVar>, cond: CondData): void {
|
|
1017
|
-
const label = contextLabel(context)
|
|
1018
|
-
if (cond.kind === "cmp") {
|
|
1019
|
-
for (const side of [cond.lhs, cond.rhs]) {
|
|
1020
|
-
if (side.kind === "var") {
|
|
1021
|
-
assertBound(label, bound, side.ref)
|
|
1022
|
-
const roster = rosterOf(side.ref.field)
|
|
1023
|
-
if (isOrderOp(cond.op.kind) && roster !== undefined) {
|
|
1024
|
-
throw closedOrderError(label, `the ${cond.op.kind} side ${side.ref.label}`, roster.name)
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
if ((cond.op.kind === "eq" || cond.op.kind === "ne") && cond.lhs.kind === "var" && cond.rhs.kind === "var") {
|
|
1029
|
-
assertBound(label, bound, cond.lhs.ref)
|
|
1030
|
-
assertBound(label, bound, cond.rhs.ref)
|
|
1031
|
-
const lhs = mintSlotOf(context, cond.lhs.ref)
|
|
1032
|
-
const rhs = mintSlotOf(context, cond.rhs.ref)
|
|
1033
|
-
if (!fieldJoins(lhs, rhs)) {
|
|
1034
|
-
throw new AuthoringError({
|
|
1035
|
-
message: `${label}: ${cond.op.kind}(${cond.lhs.ref.label}, ${cond.rhs.ref.label}) unifies domain-unequal fields — ${cond.lhs.ref.label} bound at ${renderFieldKind(lhs)}, ${cond.rhs.ref.label} at ${renderFieldKind(rhs)} (a var joins only class-equal slots; bare pairs only with bare)`
|
|
1036
|
-
})
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
950
|
+
/** An equality between two variables joins them, so their slots must join under the class law. */
|
|
951
|
+
function validateCond(context: ChainContext, cond: CondData): void {
|
|
952
|
+
if (cond.kind !== "cmp") {
|
|
953
|
+
for (const child of cond.children) validateCond(context, child)
|
|
1039
954
|
return
|
|
1040
955
|
}
|
|
1041
|
-
|
|
1042
|
-
|
|
956
|
+
if ((cond.op.kind !== "eq" && cond.op.kind !== "ne") || cond.lhs.kind !== "var" || cond.rhs.kind !== "var") return
|
|
957
|
+
const lhs = mintSlotOf(context, cond.lhs.ref)
|
|
958
|
+
const rhs = mintSlotOf(context, cond.rhs.ref)
|
|
959
|
+
if (!fieldJoins(lhs, rhs)) {
|
|
960
|
+
throw new AuthoringError({
|
|
961
|
+
message: `${contextLabel(context)}: ${cond.op.kind}(${cond.lhs.ref.label}, ${cond.rhs.ref.label}) unifies domain-unequal fields — ${cond.lhs.ref.label} bound at ${renderFieldKind(lhs)}, ${cond.rhs.ref.label} at ${renderFieldKind(rhs)} (a var joins only class-equal slots; bare pairs only with bare)`
|
|
962
|
+
})
|
|
1043
963
|
}
|
|
1044
964
|
}
|
|
1045
965
|
|
|
1046
966
|
function validateInterior(
|
|
1047
967
|
context: ChainContext,
|
|
1048
|
-
bound: ReadonlySet<AnyVar>,
|
|
1049
968
|
item: {
|
|
1050
969
|
readonly kind: "interior" | "negatedInterior"
|
|
1051
970
|
readonly target: DerivedTable
|
|
@@ -1076,11 +995,6 @@ function validateInterior(
|
|
|
1076
995
|
}
|
|
1077
996
|
}
|
|
1078
997
|
for (const binding of item.bindings) {
|
|
1079
|
-
if (item.kind === "negatedInterior" && !bound.has(binding.ref)) {
|
|
1080
|
-
throw new AuthoringError({
|
|
1081
|
-
message: `${label}: negated interior ${item.target.name} names the variable ${binding.ref.label}, but no positive atom of the rule binds it — a negated atom binds nothing, only rejects (the safety rule)`
|
|
1082
|
-
})
|
|
1083
|
-
}
|
|
1084
998
|
const headColumn = headColumns.find(function byName(column) {
|
|
1085
999
|
return column.name === binding.key
|
|
1086
1000
|
})
|
|
@@ -1098,38 +1012,14 @@ function validateInterior(
|
|
|
1098
1012
|
|
|
1099
1013
|
function completeRule(context: ChainContext, state: RuleBuildState, rawColumns: readonly FindColumn[]): RuleData {
|
|
1100
1014
|
const label = contextLabel(context)
|
|
1101
|
-
if (rawColumns.length === 0) {
|
|
1102
|
-
throw new AuthoringError({ message: `${label}: a find needs at least one entry` })
|
|
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" })
|
|
1111
1015
|
const columns = rawColumns.map(function enrichColumn(column): FindColumn {
|
|
1112
1016
|
assertDeclarationOrderKey(`${label} find column`, column.name)
|
|
1113
|
-
validateColumn(context, state.bound, column)
|
|
1114
1017
|
const slot = findColumnSlotOf(context, column)
|
|
1115
1018
|
return Object.freeze({ name: column.name, entry: column.entry, slot, closed: rosterOf(slot?.field) })
|
|
1116
1019
|
})
|
|
1117
1020
|
for (const item of state.items) {
|
|
1118
|
-
if (item.kind === "
|
|
1119
|
-
|
|
1120
|
-
if (binding.term.kind === "var" && !state.bound.has(binding.term.ref)) {
|
|
1121
|
-
throw new AuthoringError({
|
|
1122
|
-
message: `${label}: negated ${item.atom.relation.name} atom binds the variable ${binding.term.ref.label} at position ${binding.field}, but no positive atom of the rule binds it — a negated atom binds nothing, only rejects (the safety rule)`
|
|
1123
|
-
})
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
if (item.kind === "interior" || item.kind === "negatedInterior") {
|
|
1128
|
-
validateInterior(context, state.bound, item, columns)
|
|
1129
|
-
}
|
|
1130
|
-
if (item.kind === "cond") {
|
|
1131
|
-
validateCond(context, state.bound, item.cond)
|
|
1132
|
-
}
|
|
1021
|
+
if (item.kind === "interior" || item.kind === "negatedInterior") validateInterior(context, item, columns)
|
|
1022
|
+
if (item.kind === "cond") validateCond(context, item.cond)
|
|
1133
1023
|
}
|
|
1134
1024
|
return Object.freeze({ items: state.items, finds: Object.freeze(columns), paramUses: state.paramUses })
|
|
1135
1025
|
}
|
|
@@ -1221,8 +1111,8 @@ function interiorAdvance(
|
|
|
1221
1111
|
}
|
|
1222
1112
|
|
|
1223
1113
|
/**
|
|
1224
|
-
* Imported query templates as relation-expression sources (
|
|
1225
|
-
*
|
|
1114
|
+
* Imported query templates as relation-expression sources (nonrecursive
|
|
1115
|
+
* composition). The imported CQ's whole body — its inner
|
|
1226
1116
|
* interiors plus its main rules as one derived table — is SPLICED into the
|
|
1227
1117
|
* consuming query's interior roster at assembly ({@link makeRawQuery});
|
|
1228
1118
|
* naming does not materialize anything, and the imported template keeps its
|
|
@@ -1269,7 +1159,7 @@ function importEntryOf(context: ChainContext, imported: AnyQuery): ImportEntry {
|
|
|
1269
1159
|
const data = imported.data
|
|
1270
1160
|
if (data.kind === "reach") {
|
|
1271
1161
|
throw new AuthoringError({
|
|
1272
|
-
message: `${contextLabel(context)}: a recursive query is not importable as a relation expression
|
|
1162
|
+
message: `${contextLabel(context)}: a recursive query is not importable as a relation expression — declare the recursion on the consuming query`
|
|
1273
1163
|
})
|
|
1274
1164
|
}
|
|
1275
1165
|
const userParams = data.params.filter(function userSupplied(entry) {
|
|
@@ -1408,8 +1298,7 @@ function findColumns(context: ChainContext, entries: Readonly<Record<string, unk
|
|
|
1408
1298
|
const columns: FindColumn[] = []
|
|
1409
1299
|
queryRecord("query find", entries)
|
|
1410
1300
|
// Nonrecursive derived stages (interiors) may emit aggregate/computed
|
|
1411
|
-
// outputs
|
|
1412
|
-
// recursive feedback cycle stays projection-only — no aggregate,
|
|
1301
|
+
// outputs; ONLY the recursive feedback cycle stays projection-only — no aggregate,
|
|
1413
1302
|
// arithmetic-created value or negation flows through it.
|
|
1414
1303
|
const recName = context.kind === "rec-base" || context.kind === "rec-arm" ? context.self.name : undefined
|
|
1415
1304
|
for (const [name, entry] of Object.entries(entries)) {
|
|
@@ -1481,7 +1370,7 @@ function makeQueryRuleScope<Rels extends SchemaRelations, Classes extends Schema
|
|
|
1481
1370
|
): QueryRuleScope<Rels, Classes> {
|
|
1482
1371
|
const raw = makeRawScope({ kind: "query", classes: theory.classes, theory, ...env })
|
|
1483
1372
|
if (!isTypedScope<QueryRuleScope<Rels, Classes>>(raw)) {
|
|
1484
|
-
throw
|
|
1373
|
+
throw internalError("query rule builder construction incomplete")
|
|
1485
1374
|
}
|
|
1486
1375
|
return raw
|
|
1487
1376
|
}
|
|
@@ -1493,7 +1382,7 @@ function makeInteriorRuleScope<Rels extends SchemaRelations, Classes extends Sch
|
|
|
1493
1382
|
): InteriorRuleScope<Rels, Classes> {
|
|
1494
1383
|
const raw = makeRawScope({ kind: "interior", self, classes: theory.classes, theory, ...env })
|
|
1495
1384
|
if (!isTypedScope<InteriorRuleScope<Rels, Classes>>(raw)) {
|
|
1496
|
-
throw
|
|
1385
|
+
throw internalError("interior rule builder construction incomplete")
|
|
1497
1386
|
}
|
|
1498
1387
|
return raw
|
|
1499
1388
|
}
|
|
@@ -1533,7 +1422,7 @@ function makeRecRuleScope<Rels extends SchemaRelations, Classes extends SchemaCl
|
|
|
1533
1422
|
...env
|
|
1534
1423
|
})
|
|
1535
1424
|
if (!isTypedScope<RecRuleScope<Rels, Classes>>(raw)) {
|
|
1536
|
-
throw
|
|
1425
|
+
throw internalError("rec rule builder construction incomplete")
|
|
1537
1426
|
}
|
|
1538
1427
|
return raw
|
|
1539
1428
|
}
|
|
@@ -1542,20 +1431,10 @@ function renderClosedSlice(closed: AnyClosedRoster | undefined): string {
|
|
|
1542
1431
|
return closed === undefined ? "a bare value" : `a ${closed.name} reference`
|
|
1543
1432
|
}
|
|
1544
1433
|
|
|
1545
|
-
function
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
return `${column.name}:var`
|
|
1550
|
-
}
|
|
1551
|
-
if (entry.kind === "compute") {
|
|
1552
|
-
return `${column.name}:compute`
|
|
1553
|
-
}
|
|
1554
|
-
const agg = entry.agg
|
|
1555
|
-
if (agg.op === "fold") {
|
|
1556
|
-
return `${column.name}:${agg.fold}`
|
|
1557
|
-
}
|
|
1558
|
-
return `${column.name}:${agg.op}`
|
|
1434
|
+
function headOperation(column: FindColumn): string | undefined {
|
|
1435
|
+
if (column.entry.kind !== "aggregate") return undefined
|
|
1436
|
+
const agg = column.entry.agg
|
|
1437
|
+
return agg.op === "fold" ? agg.fold : agg.op
|
|
1559
1438
|
}
|
|
1560
1439
|
|
|
1561
1440
|
function renderParamAnchor(roster: AnyClosedRoster | undefined): string {
|
|
@@ -1563,16 +1442,10 @@ function renderParamAnchor(roster: AnyClosedRoster | undefined): string {
|
|
|
1563
1442
|
}
|
|
1564
1443
|
|
|
1565
1444
|
/**
|
|
1566
|
-
* Folds every rule's param uses
|
|
1567
|
-
*
|
|
1568
|
-
*
|
|
1569
|
-
*
|
|
1570
|
-
* closedness. The orderable ban needs no registry arm: an order use always
|
|
1571
|
-
* anchors its SIBLING's domain (the no-variable-side spelling is refused
|
|
1572
|
-
* at the comparison constructor), so a closed-anchored param under an
|
|
1573
|
-
* order op dies at the one-domain wall here — and an order use whose
|
|
1574
|
-
* sibling is itself closed-bound dies at the comparison's own var-side
|
|
1575
|
-
* wall first.
|
|
1445
|
+
* Folds every rule's param uses, in lowering order, into the query's registry: the first use mints
|
|
1446
|
+
* the dense `ParamId`, the first field-anchored use types it, and one name keeps one shape and one
|
|
1447
|
+
* closedness. An order use always anchors its sibling's domain, so a closed-anchored param under an
|
|
1448
|
+
* order op fails the one-domain check here.
|
|
1576
1449
|
*/
|
|
1577
1450
|
function paramRegistryOf(
|
|
1578
1451
|
interiors: readonly InteriorData[],
|
|
@@ -1692,12 +1565,22 @@ function alignedHeadOf(label: string, rules: readonly RuleData[]): readonly Find
|
|
|
1692
1565
|
if (first === undefined) {
|
|
1693
1566
|
throw new AuthoringError({ message: `${label} needs at least one rule` })
|
|
1694
1567
|
}
|
|
1695
|
-
|
|
1568
|
+
// Bare u64 admits either carrier, but does not make two different
|
|
1569
|
+
// carriers compatible. Retain one original classed slot per column;
|
|
1570
|
+
// checking only the first arm (or its weakened meet) loses that proof.
|
|
1571
|
+
const classed = new Map<number, { readonly slot: ClassedField; readonly rule: number }>()
|
|
1696
1572
|
rules.forEach(function verifyHead(rule, index) {
|
|
1697
|
-
|
|
1698
|
-
|
|
1573
|
+
if (
|
|
1574
|
+
rule.finds.length !== first.finds.length ||
|
|
1575
|
+
rule.finds.some((column, position) => {
|
|
1576
|
+
const lead = first.finds[position]
|
|
1577
|
+
return lead === undefined || lead.name !== column.name || headOperation(lead) !== headOperation(column)
|
|
1578
|
+
})
|
|
1579
|
+
) {
|
|
1580
|
+
const render = (columns: readonly FindColumn[]) =>
|
|
1581
|
+
columns.map((column) => `${column.name}:${headOperation(column) ?? "project"}`).join(", ")
|
|
1699
1582
|
throw new AuthoringError({
|
|
1700
|
-
message: `every rule of ${label} derives the same head — rule 0 finds (${
|
|
1583
|
+
message: `every rule of ${label} derives the same head — rule 0 finds (${render(first.finds)}), rule ${index} finds (${render(rule.finds)})`
|
|
1701
1584
|
})
|
|
1702
1585
|
}
|
|
1703
1586
|
rule.finds.forEach(function verifyClosedSlice(column, position) {
|
|
@@ -1710,30 +1593,44 @@ function alignedHeadOf(label: string, rules: readonly RuleData[]): readonly Find
|
|
|
1710
1593
|
if (lead === undefined) {
|
|
1711
1594
|
return
|
|
1712
1595
|
}
|
|
1713
|
-
|
|
1596
|
+
const prior = classed.get(position) ?? (lead.slot === undefined ? undefined : { slot: lead.slot, rule: 0 })
|
|
1597
|
+
if (prior !== undefined && column.slot !== undefined && !headFieldJoins(prior.slot, column.slot)) {
|
|
1714
1598
|
throw new AuthoringError({
|
|
1715
|
-
message: `every rule of ${label} derives the same head — the head column ${lead.name} is bound at ${renderFieldKind(
|
|
1599
|
+
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)`
|
|
1716
1600
|
})
|
|
1717
1601
|
}
|
|
1602
|
+
if (column.slot?.class !== undefined && !classed.has(position)) {
|
|
1603
|
+
classed.set(position, { slot: column.slot, rule: index })
|
|
1604
|
+
}
|
|
1718
1605
|
})
|
|
1719
1606
|
})
|
|
1720
1607
|
const merged = first.finds.map(function meetColumn(lead, position) {
|
|
1721
1608
|
const slot = lead.slot
|
|
1722
|
-
if (slot === undefined
|
|
1609
|
+
if (slot === undefined) {
|
|
1723
1610
|
return lead
|
|
1724
1611
|
}
|
|
1725
1612
|
const demoted = rules.some(function bareElsewhere(rule) {
|
|
1726
1613
|
const column = rule.finds[position]
|
|
1727
1614
|
return column !== undefined && column.slot !== undefined && column.slot.class !== slot.class
|
|
1728
1615
|
})
|
|
1729
|
-
|
|
1616
|
+
const field = slot.field
|
|
1617
|
+
const widened =
|
|
1618
|
+
field.kind === "interval" &&
|
|
1619
|
+
rules.some((rule) => {
|
|
1620
|
+
const other = rule.finds[position]?.slot?.field
|
|
1621
|
+
return other?.kind === "interval" && other.width !== field.width
|
|
1622
|
+
})
|
|
1623
|
+
if (!demoted && !widened) {
|
|
1730
1624
|
return lead
|
|
1731
1625
|
}
|
|
1732
1626
|
return Object.freeze({
|
|
1733
1627
|
name: lead.name,
|
|
1734
1628
|
entry: lead.entry,
|
|
1735
1629
|
closed: lead.closed,
|
|
1736
|
-
slot: Object.freeze({
|
|
1630
|
+
slot: Object.freeze({
|
|
1631
|
+
field: widened ? Object.freeze({ ...field, width: undefined }) : field,
|
|
1632
|
+
class: demoted ? undefined : slot.class
|
|
1633
|
+
})
|
|
1737
1634
|
})
|
|
1738
1635
|
})
|
|
1739
1636
|
return Object.freeze(merged)
|
|
@@ -1745,6 +1642,53 @@ function afterMainError(what: string): Error {
|
|
|
1745
1642
|
})
|
|
1746
1643
|
}
|
|
1747
1644
|
|
|
1645
|
+
/** Names the parts of `data` an engine query diagnostic cites. */
|
|
1646
|
+
function explainQuery(data: QueryData, labels: RuleLabels, diagnostic: QueryDiagnostic): string {
|
|
1647
|
+
const tables = [...data.interiors, ...(data.kind === "reach" ? [data.rec] : [])]
|
|
1648
|
+
const table = diagnostic.interior === undefined ? undefined : tables[diagnostic.interior]
|
|
1649
|
+
const ruleLabels =
|
|
1650
|
+
diagnostic.rule === undefined
|
|
1651
|
+
? undefined
|
|
1652
|
+
: (diagnostic.interior === undefined ? labels.main : labels.interiors[diagnostic.interior])?.[diagnostic.rule]
|
|
1653
|
+
const finds = table?.finds ?? data.finds
|
|
1654
|
+
const cited = [
|
|
1655
|
+
table === undefined ? undefined : `in ${table.name}`,
|
|
1656
|
+
diagnostic.rule === undefined ? undefined : `rule ${diagnostic.rule}`,
|
|
1657
|
+
diagnostic.find === undefined ? undefined : `find ${finds[diagnostic.find]?.name ?? diagnostic.find}`,
|
|
1658
|
+
diagnostic.atom === undefined ? undefined : `atom ${diagnostic.atom}`,
|
|
1659
|
+
diagnostic.var === undefined ? undefined : `variable ${ruleLabels?.[diagnostic.var] ?? diagnostic.var}`,
|
|
1660
|
+
diagnostic.param === undefined ? undefined : `param ${data.params[diagnostic.param]?.name ?? diagnostic.param}`,
|
|
1661
|
+
diagnostic.comparison === undefined ? undefined : `comparison ${diagnostic.comparison}`
|
|
1662
|
+
].filter((part) => part !== undefined)
|
|
1663
|
+
return cited.length === 0 ? diagnostic.message : `${diagnostic.message} (${cited.join(", ")})`
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
/** Validates a complete query with the engine and returns its compiled handle. */
|
|
1667
|
+
function validateQueryData(theory: AnySchema, data: QueryData): QueryRef {
|
|
1668
|
+
const lowered = lowerQueryLabelled({ schema: theory, data })
|
|
1669
|
+
const validated = addon.validateQuery(compiledOf(schemaDescriptor(theory)).handle, JSON.stringify(lowered.query))
|
|
1670
|
+
switch (validated._tag) {
|
|
1671
|
+
case "Valid":
|
|
1672
|
+
return validated.query
|
|
1673
|
+
case "Invalid":
|
|
1674
|
+
throw new AuthoringError({
|
|
1675
|
+
message: `query: ${explainQuery(data, lowered.labels, validated.diagnostic)}`,
|
|
1676
|
+
diagnostic: { code: "InvalidQuery", context: validated.diagnostic.code, expected: validated.diagnostic.message }
|
|
1677
|
+
})
|
|
1678
|
+
case "Malformed":
|
|
1679
|
+
throw internalError(`validateQuery: ${validated.path}: ${validated.message}`)
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
const validatedQueries = new WeakMap<object, QueryRef>()
|
|
1684
|
+
|
|
1685
|
+
/** The engine's validated form of a query value `query()` built. */
|
|
1686
|
+
function queryHandleOf(query: AnyQuery): QueryRef {
|
|
1687
|
+
const handle = validatedQueries.get(query)
|
|
1688
|
+
if (handle === undefined) throw internalError("queryHandleOf: the query was not built by query()")
|
|
1689
|
+
return handle
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1748
1692
|
function makeRawQuery(
|
|
1749
1693
|
inputTheory: AnySchema,
|
|
1750
1694
|
inputInteriors: readonly InteriorData[],
|
|
@@ -1784,6 +1728,7 @@ function makeRawQuery(
|
|
|
1784
1728
|
finds: mergedFinds,
|
|
1785
1729
|
params
|
|
1786
1730
|
})
|
|
1731
|
+
const handle = validateQueryData(theory, data)
|
|
1787
1732
|
const value: RawQuery = {
|
|
1788
1733
|
get schema() {
|
|
1789
1734
|
return snapshotData(theory)
|
|
@@ -1809,6 +1754,7 @@ function makeRawQuery(
|
|
|
1809
1754
|
}
|
|
1810
1755
|
}
|
|
1811
1756
|
Object.freeze(value)
|
|
1757
|
+
validatedQueries.set(value, handle)
|
|
1812
1758
|
return value
|
|
1813
1759
|
}
|
|
1814
1760
|
|
|
@@ -2039,6 +1985,8 @@ interface LowerContext {
|
|
|
2039
1985
|
|
|
2040
1986
|
interface VarIds {
|
|
2041
1987
|
of(ref: AnyVar): number
|
|
1988
|
+
/** Each variable's label, by variable id. */
|
|
1989
|
+
labels(): readonly string[]
|
|
2042
1990
|
}
|
|
2043
1991
|
|
|
2044
1992
|
function freshVarIds(): VarIds {
|
|
@@ -2052,6 +2000,9 @@ function freshVarIds(): VarIds {
|
|
|
2052
2000
|
const id = assigned.size
|
|
2053
2001
|
assigned.set(ref, id)
|
|
2054
2002
|
return id
|
|
2003
|
+
},
|
|
2004
|
+
labels() {
|
|
2005
|
+
return [...assigned.keys()].map((ref) => ref.label)
|
|
2055
2006
|
}
|
|
2056
2007
|
}
|
|
2057
2008
|
}
|
|
@@ -2064,257 +2015,221 @@ function paramIdOf(ctx: LowerContext, name: string): number {
|
|
|
2064
2015
|
return id
|
|
2065
2016
|
}
|
|
2066
2017
|
|
|
2067
|
-
function lowerAtom(ctx: LowerContext, atom: AtomData, ids: VarIds):
|
|
2068
|
-
const
|
|
2069
|
-
if (!membersAgree(
|
|
2018
|
+
function lowerAtom(ctx: LowerContext, atom: AtomData, ids: VarIds): AtomIn {
|
|
2019
|
+
const relationId = ctx.relationIds.get(atom.relation.name)
|
|
2020
|
+
if (!membersAgree(ctx.theory.relations[atom.relation.name], atom.relation) || relationId === undefined) {
|
|
2070
2021
|
throw new AuthoringError({
|
|
2071
|
-
message: `query
|
|
2022
|
+
message: `query: relation ${atom.relation.name} is not the relation value schema ${ctx.theory.name} declares`
|
|
2072
2023
|
})
|
|
2073
2024
|
}
|
|
2074
|
-
const relationId = ctx.relationIds.get(atom.relation.name)
|
|
2075
|
-
if (relationId === undefined) {
|
|
2076
|
-
throw new AuthoringError({ message: `query lowering: relation ${atom.relation.name} has no ordinal` })
|
|
2077
|
-
}
|
|
2078
2025
|
const ordered = sealedFieldsOf(atom.relation)
|
|
2079
|
-
const bindings
|
|
2080
|
-
const
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
throw new AuthoringError({
|
|
2085
|
-
message: `query lowering: relation ${atom.relation.name} has no field ${binding.field}`
|
|
2086
|
-
})
|
|
2087
|
-
}
|
|
2088
|
-
return [ordinal, lowerBindingTerm(ctx, `${atom.relation.name}.${binding.field}`, binding, ids)] as const
|
|
2026
|
+
const bindings = atom.bindings.map((binding): BindingIn => {
|
|
2027
|
+
const field = ordered.findIndex((candidate) => candidate.name === binding.field)
|
|
2028
|
+
if (field < 0)
|
|
2029
|
+
throw new AuthoringError({ message: `query: relation ${atom.relation.name} has no field ${binding.field}` })
|
|
2030
|
+
return { field, term: lowerBindingTerm(ctx, `${atom.relation.name}.${binding.field}`, binding, ids) }
|
|
2089
2031
|
})
|
|
2090
|
-
return { source: { kind: "
|
|
2032
|
+
return { source: { kind: "Edb", relation: relationId }, bindings }
|
|
2091
2033
|
}
|
|
2092
2034
|
|
|
2093
|
-
function lowerBindingTerm(ctx: LowerContext, context: string, binding: BindingEntry, ids: VarIds):
|
|
2035
|
+
function lowerBindingTerm(ctx: LowerContext, context: string, binding: BindingEntry, ids: VarIds): TermIn {
|
|
2094
2036
|
const bound = binding.term
|
|
2095
2037
|
switch (bound.kind) {
|
|
2096
2038
|
case "var":
|
|
2097
|
-
return { kind: "
|
|
2039
|
+
return { kind: "Var", var: ids.of(bound.ref) }
|
|
2098
2040
|
case "param":
|
|
2099
|
-
return { kind: "
|
|
2041
|
+
return { kind: "Param", param: paramIdOf(ctx, bound.name) }
|
|
2100
2042
|
case "setParam":
|
|
2101
|
-
return { kind: "paramSet", param: paramIdOf(ctx, bound.name) }
|
|
2102
2043
|
case "literalSet":
|
|
2103
|
-
return { kind: "
|
|
2044
|
+
return { kind: "ParamSet", param: paramIdOf(ctx, bound.name) }
|
|
2104
2045
|
case "literal":
|
|
2105
|
-
return { kind: "
|
|
2046
|
+
return { kind: "Literal", value: valueIn(taggedLiteral(context, binding.data, bound.value)) }
|
|
2106
2047
|
}
|
|
2107
2048
|
}
|
|
2108
2049
|
|
|
2109
|
-
/**
|
|
2110
|
-
* Lowers one interior atom: named bindings placed by HEAD order, `FieldId(i)` =
|
|
2111
|
-
* head position i. Every head column of the table must be bound (a missing key
|
|
2112
|
-
* is refused pointed); the var-id assignment order is head order, so the
|
|
2113
|
-
* first-use numbering matches the name-keyed edition exactly.
|
|
2114
|
-
*/
|
|
2050
|
+
/** An interior atom binds every head column of the table, by head position. */
|
|
2115
2051
|
function lowerInteriorAtom(
|
|
2116
2052
|
ctx: LowerContext,
|
|
2117
2053
|
target: DerivedTable,
|
|
2118
2054
|
bindings: ReadonlyArray<{ readonly key: string; readonly ref: AnyVar }>,
|
|
2119
2055
|
ids: VarIds
|
|
2120
|
-
):
|
|
2056
|
+
): AtomIn {
|
|
2121
2057
|
const interior = ctx.interiorIds.get(target.name)
|
|
2122
2058
|
if (interior === undefined) {
|
|
2123
|
-
throw new AuthoringError({ message: `query
|
|
2124
|
-
}
|
|
2125
|
-
if (target.finds.length === 0) {
|
|
2126
|
-
throw new AuthoringError({ message: `query lowering: derived table ${target.name} has no head` })
|
|
2059
|
+
throw new AuthoringError({ message: `query: derived table ${target.name} was not declared on this query` })
|
|
2127
2060
|
}
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2061
|
+
return {
|
|
2062
|
+
source: { kind: "Interior", interior },
|
|
2063
|
+
bindings: target.finds.map((column, field): BindingIn => {
|
|
2064
|
+
const binding = bindings.find((candidate) => candidate.key === column.name)
|
|
2065
|
+
if (binding === undefined) {
|
|
2066
|
+
throw new AuthoringError({ message: `query: interior ${target.name} omits head column ${column.name}` })
|
|
2067
|
+
}
|
|
2068
|
+
return { field, term: { kind: "Var", var: ids.of(binding.ref) } }
|
|
2131
2069
|
})
|
|
2132
|
-
|
|
2133
|
-
throw new AuthoringError({ message: `query lowering: interior ${target.name} omits head column ${column.name}` })
|
|
2134
|
-
}
|
|
2135
|
-
return [position, { kind: "var", var: ids.of(binding.ref) } as const] as const
|
|
2136
|
-
})
|
|
2137
|
-
return { source: { kind: "interior", interior }, bindings: irBindings }
|
|
2070
|
+
}
|
|
2138
2071
|
}
|
|
2139
2072
|
|
|
2140
|
-
function lowerCmpTerm(ctx: LowerContext, side: CmpTermData, sibling: CmpTermData, ids: VarIds, op: CmpKind):
|
|
2073
|
+
function lowerCmpTerm(ctx: LowerContext, side: CmpTermData, sibling: CmpTermData, ids: VarIds, op: CmpKind): TermIn {
|
|
2141
2074
|
switch (side.kind) {
|
|
2142
2075
|
case "var":
|
|
2143
|
-
return { kind: "
|
|
2076
|
+
return { kind: "Var", var: ids.of(side.ref) }
|
|
2144
2077
|
case "param":
|
|
2145
|
-
return { kind: "
|
|
2078
|
+
return { kind: "Param", param: paramIdOf(ctx, side.name) }
|
|
2146
2079
|
case "setParam":
|
|
2147
|
-
return { kind: "
|
|
2080
|
+
return { kind: "ParamSet", param: paramIdOf(ctx, side.name) }
|
|
2148
2081
|
case "literal": {
|
|
2149
2082
|
const anchor = cmpAnchorOf(ctx, sibling)
|
|
2150
2083
|
if (anchor === undefined) {
|
|
2151
2084
|
throw new AuthoringError({
|
|
2152
|
-
message: "query
|
|
2085
|
+
message: "query: a comparison literal needs a bound-variable or anchored-param sibling to type it"
|
|
2153
2086
|
})
|
|
2154
2087
|
}
|
|
2155
|
-
return { kind: "
|
|
2088
|
+
return { kind: "Literal", value: valueIn(taggedCmpLiteral("comparison literal", anchor, side.value, op)) }
|
|
2156
2089
|
}
|
|
2157
2090
|
}
|
|
2158
2091
|
}
|
|
2159
2092
|
|
|
2160
2093
|
function cmpAnchorOf(ctx: LowerContext, sibling: CmpTermData): AnyField | undefined {
|
|
2161
|
-
if (sibling.kind === "var")
|
|
2162
|
-
|
|
2163
|
-
}
|
|
2164
|
-
if (sibling.kind === "param" || sibling.kind === "setParam") {
|
|
2165
|
-
return ctx.params.get(sibling.name)?.anchor
|
|
2166
|
-
}
|
|
2094
|
+
if (sibling.kind === "var") return sibling.ref.field
|
|
2095
|
+
if (sibling.kind === "param" || sibling.kind === "setParam") return ctx.params.get(sibling.name)?.anchor
|
|
2167
2096
|
return undefined
|
|
2168
2097
|
}
|
|
2169
2098
|
|
|
2170
|
-
|
|
2171
|
-
|
|
2099
|
+
const CMP = { eq: "Eq", ne: "Ne", lt: "Lt", le: "Le", gt: "Gt", ge: "Ge", pointIn: "PointIn" } as const
|
|
2100
|
+
|
|
2101
|
+
function lowerCondition(ctx: LowerContext, cond: CondData, ids: VarIds): ConditionIn {
|
|
2102
|
+
if (cond.kind !== "cmp") {
|
|
2172
2103
|
return {
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
rhs: lowerCmpTerm(ctx, cmp.rhs, cmp.lhs, ids, "allen")
|
|
2104
|
+
kind: cond.op === "and" ? "And" : "Or",
|
|
2105
|
+
children: cond.children.map((child) => lowerCondition(ctx, child, ids))
|
|
2176
2106
|
}
|
|
2177
2107
|
}
|
|
2108
|
+
const kind = cond.op.kind
|
|
2178
2109
|
return {
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2110
|
+
kind: "Leaf",
|
|
2111
|
+
op: cond.op.kind === "allen" ? { kind: "Allen", mask: cond.op.mask } : { kind: CMP[cond.op.kind] },
|
|
2112
|
+
lhs: lowerCmpTerm(ctx, cond.lhs, cond.rhs, ids, kind),
|
|
2113
|
+
rhs: lowerCmpTerm(ctx, cond.rhs, cond.lhs, ids, kind)
|
|
2182
2114
|
}
|
|
2183
2115
|
}
|
|
2184
2116
|
|
|
2185
|
-
|
|
2186
|
-
if (cond.kind === "cmp") {
|
|
2187
|
-
return { kind: "leaf", cmp: lowerComparison(ctx, cond, ids) }
|
|
2188
|
-
}
|
|
2189
|
-
return {
|
|
2190
|
-
kind: cond.op,
|
|
2191
|
-
children: cond.children.map(function lowerChild(child) {
|
|
2192
|
-
return lowerCondition(ctx, child, ids)
|
|
2193
|
-
})
|
|
2194
|
-
}
|
|
2195
|
-
}
|
|
2196
|
-
|
|
2197
|
-
/**
|
|
2198
|
-
* Lowers one computed scalar node to the C05 wire (`#native.ts`'s
|
|
2199
|
-
* `ScalarExprIr`): the shared grammar arms pass through 1:1. Cached
|
|
2200
|
-
* construction depth is the bound; this walk emits wire only and does
|
|
2201
|
-
* not re-judge kinds (L14/native compile owns binding).
|
|
2202
|
-
*/
|
|
2203
|
-
function lowerComputeNode(node: QueryNode, ids: VarIds): ScalarExprIr {
|
|
2204
|
-
if (node.depth > MAX_COMPUTE_DEPTH) {
|
|
2205
|
-
throw new AuthoringError({
|
|
2206
|
-
message: `query lowering: a compute expression is deeper than ${MAX_COMPUTE_DEPTH} nodes (the engine's scalar depth bound)`
|
|
2207
|
-
})
|
|
2208
|
-
}
|
|
2209
|
-
return lowerComputeGrammar(node, ids)
|
|
2210
|
-
}
|
|
2211
|
-
|
|
2212
|
-
function lowerComputeGrammar(node: QueryNode, ids: VarIds): ScalarExprIr {
|
|
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
|
-
})
|
|
2217
|
-
}
|
|
2117
|
+
const FOLD = { sum: "Sum", mean: "Mean", min: "Min", max: "Max" } as const
|
|
2218
2118
|
|
|
2219
|
-
function lowerFind(entry: FindEntryData, ids: VarIds):
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2119
|
+
function lowerFind(entry: FindEntryData, ids: VarIds): FindTermIn {
|
|
2120
|
+
switch (entry.kind) {
|
|
2121
|
+
case "segments":
|
|
2122
|
+
return {
|
|
2123
|
+
kind: "Segments",
|
|
2124
|
+
op: entry.op === "intersection" ? "Intersection" : "Difference",
|
|
2125
|
+
left: ids.of(entry.left),
|
|
2126
|
+
right: ids.of(entry.right)
|
|
2127
|
+
}
|
|
2128
|
+
case "var":
|
|
2129
|
+
return { kind: "Var", var: ids.of(entry.over) }
|
|
2130
|
+
case "compute":
|
|
2131
|
+
if (entry.expr.depth > MAX_COMPUTE_DEPTH) {
|
|
2132
|
+
throw new AuthoringError({
|
|
2133
|
+
message: `query: a compute expression is deeper than ${MAX_COMPUTE_DEPTH} nodes`
|
|
2134
|
+
})
|
|
2135
|
+
}
|
|
2136
|
+
return { kind: "Compute", expr: scalarWire(entry.expr, (leaf) => ids.of(leaf.ref as AnyVar)) }
|
|
2227
2137
|
}
|
|
2228
2138
|
const agg = entry.agg
|
|
2229
2139
|
switch (agg.op) {
|
|
2230
2140
|
case "count":
|
|
2231
|
-
return { kind: "
|
|
2141
|
+
return { kind: "Count" }
|
|
2232
2142
|
case "fold":
|
|
2233
|
-
return { kind: "
|
|
2143
|
+
return { kind: "Aggregate", op: FOLD[agg.fold], over: ids.of(agg.over) }
|
|
2234
2144
|
case "pack":
|
|
2235
|
-
return { kind: "
|
|
2145
|
+
return { kind: "Pack", over: ids.of(agg.over) }
|
|
2236
2146
|
}
|
|
2237
2147
|
}
|
|
2238
2148
|
|
|
2239
|
-
function
|
|
2240
|
-
switch (agg.op) {
|
|
2241
|
-
case "count":
|
|
2242
|
-
return "count"
|
|
2243
|
-
case "fold":
|
|
2244
|
-
return agg.fold
|
|
2245
|
-
case "pack":
|
|
2246
|
-
return "pack"
|
|
2247
|
-
}
|
|
2248
|
-
}
|
|
2249
|
-
|
|
2250
|
-
function headTermOf(column: FindColumn): HeadTermIr {
|
|
2149
|
+
function headTermOf(column: FindColumn): HeadTermIn {
|
|
2251
2150
|
const entry = column.entry
|
|
2252
|
-
if (entry.kind === "segments") return { kind: "
|
|
2253
|
-
if (entry.kind === "var") {
|
|
2254
|
-
|
|
2255
|
-
}
|
|
2256
|
-
if (
|
|
2257
|
-
|
|
2258
|
-
}
|
|
2259
|
-
return { kind: "aggregate", op: headOpOf(entry.agg) }
|
|
2151
|
+
if (entry.kind === "segments" || entry.kind === "compute") return { kind: "Compute" }
|
|
2152
|
+
if (entry.kind === "var") return { kind: "Var" }
|
|
2153
|
+
const agg = entry.agg
|
|
2154
|
+
if (agg.op === "count") return { kind: "Aggregate", op: "Count" }
|
|
2155
|
+
if (agg.op === "pack") return { kind: "Aggregate", op: "Pack" }
|
|
2156
|
+
return { kind: "Aggregate", op: FOLD[agg.fold] }
|
|
2260
2157
|
}
|
|
2261
2158
|
|
|
2262
|
-
function lowerRule(ctx: LowerContext, rule: RuleData):
|
|
2159
|
+
function lowerRule(ctx: LowerContext, rule: RuleData, labels?: (readonly string[])[]): RuleIn {
|
|
2263
2160
|
const ids = freshVarIds()
|
|
2264
|
-
const atoms:
|
|
2265
|
-
const negated:
|
|
2266
|
-
const conditions:
|
|
2161
|
+
const atoms: AtomIn[] = []
|
|
2162
|
+
const negated: AtomIn[] = []
|
|
2163
|
+
const conditions: ConditionIn[] = []
|
|
2267
2164
|
for (const item of rule.items) {
|
|
2268
2165
|
switch (item.kind) {
|
|
2269
|
-
case "atom":
|
|
2166
|
+
case "atom":
|
|
2270
2167
|
atoms.push(lowerAtom(ctx, item.atom, ids))
|
|
2271
2168
|
break
|
|
2272
|
-
|
|
2273
|
-
case "negated": {
|
|
2169
|
+
case "negated":
|
|
2274
2170
|
negated.push(lowerAtom(ctx, item.atom, ids))
|
|
2275
2171
|
break
|
|
2276
|
-
|
|
2277
|
-
case "interior": {
|
|
2172
|
+
case "interior":
|
|
2278
2173
|
atoms.push(lowerInteriorAtom(ctx, item.target, item.bindings, ids))
|
|
2279
2174
|
break
|
|
2280
|
-
|
|
2281
|
-
case "negatedInterior": {
|
|
2175
|
+
case "negatedInterior":
|
|
2282
2176
|
negated.push(lowerInteriorAtom(ctx, item.target, item.bindings, ids))
|
|
2283
2177
|
break
|
|
2284
|
-
|
|
2285
|
-
case "cond": {
|
|
2178
|
+
case "cond":
|
|
2286
2179
|
conditions.push(lowerCondition(ctx, item.cond, ids))
|
|
2287
2180
|
break
|
|
2288
|
-
}
|
|
2289
2181
|
}
|
|
2290
2182
|
}
|
|
2183
|
+
const finds = rule.finds.map((column) => lowerFind(column.entry, ids))
|
|
2184
|
+
labels?.push(ids.labels())
|
|
2185
|
+
return { finds, atoms, negated, conditions }
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
/** A recursive rule projects variables only and negates nothing. */
|
|
2189
|
+
function recFinds(label: string, rule: RuleIn): number[] {
|
|
2190
|
+
if (rule.negated.length > 0) throw new AuthoringError({ message: `query ${label}: a recursive rule cannot negate` })
|
|
2191
|
+
return rule.finds.map((find) => {
|
|
2192
|
+
if (find.kind !== "Var")
|
|
2193
|
+
throw new AuthoringError({ message: `query ${label}: a recursive rule finds variables only` })
|
|
2194
|
+
return find.var
|
|
2195
|
+
})
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
/** A recursive step reads its own previous output through exactly one self-atom. */
|
|
2199
|
+
function recStep(label: string, rule: RuleIn, self: number): RecStepIn {
|
|
2200
|
+
const selfAtoms = rule.atoms.filter((atom) => atom.source.kind === "Interior" && atom.source.interior === self)
|
|
2201
|
+
const [selfAtom] = selfAtoms
|
|
2202
|
+
if (selfAtom === undefined || selfAtoms.length > 1) {
|
|
2203
|
+
throw new AuthoringError({ message: `query ${label}: a recursive step reads the recursion exactly once` })
|
|
2204
|
+
}
|
|
2291
2205
|
return {
|
|
2292
|
-
finds:
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
negated,
|
|
2297
|
-
conditions
|
|
2206
|
+
finds: recFinds(label, rule),
|
|
2207
|
+
selfBindings: selfAtom.bindings,
|
|
2208
|
+
atoms: rule.atoms.filter((atom) => atom !== selfAtom),
|
|
2209
|
+
conditions: rule.conditions
|
|
2298
2210
|
}
|
|
2299
2211
|
}
|
|
2300
2212
|
|
|
2301
|
-
|
|
2213
|
+
/** Variable labels by rule: main rules, then each interior's rules. */
|
|
2214
|
+
interface RuleLabels {
|
|
2215
|
+
readonly main: (readonly string[])[]
|
|
2216
|
+
readonly interiors: (readonly string[])[][]
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
/** Lowers a query to the addon's `QueryIn`. The engine validates it; lowering judges nothing. */
|
|
2220
|
+
function lowerQuery(q: AnyQuery): QueryIn {
|
|
2221
|
+
return lowerQueryLabelled(q).query
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
function lowerQueryLabelled(q: AnyQuery): { readonly query: QueryIn; readonly labels: RuleLabels } {
|
|
2302
2225
|
const theory = q.schema
|
|
2303
2226
|
const data = q.data
|
|
2304
|
-
const relationIds = new Map
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
})
|
|
2308
|
-
const interiorIds = new Map<string, number>()
|
|
2309
|
-
data.interiors.forEach(function assignInteriorId(interior, index) {
|
|
2310
|
-
interiorIds.set(interior.name, index)
|
|
2311
|
-
})
|
|
2312
|
-
if (data.kind === "reach") {
|
|
2313
|
-
interiorIds.set(data.rec.name, data.interiors.length)
|
|
2314
|
-
}
|
|
2227
|
+
const relationIds = new Map(Object.keys(theory.relations).map((name, index) => [name, index] as const))
|
|
2228
|
+
const interiorIds = new Map(data.interiors.map((interior, index) => [interior.name, index] as const))
|
|
2229
|
+
if (data.kind === "reach") interiorIds.set(data.rec.name, data.interiors.length)
|
|
2315
2230
|
const paramIds = new Map<string, number>()
|
|
2316
2231
|
const params = new Map<string, ParamEntry>()
|
|
2317
|
-
data.params.forEach(
|
|
2232
|
+
data.params.forEach((entry, index) => {
|
|
2318
2233
|
if (entry.anchor === undefined) {
|
|
2319
2234
|
throw new AuthoringError({
|
|
2320
2235
|
message: `query param ${entry.name} has no field-anchored use — bind it in an atom or compare it against a bound variable`
|
|
@@ -2324,36 +2239,30 @@ function lowerQuery(q: AnyQuery): ParsedQuery {
|
|
|
2324
2239
|
params.set(entry.name, entry)
|
|
2325
2240
|
})
|
|
2326
2241
|
const ctx: LowerContext = { theory, relationIds, interiorIds, paramIds, params }
|
|
2327
|
-
const
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
rules: interior.rules.map(
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
}
|
|
2335
|
-
|
|
2336
|
-
const
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
return lowerRule(ctx, rule)
|
|
2349
|
-
}),
|
|
2350
|
-
rec: data.rec.rec.map(function lowerRecArm(rule) {
|
|
2351
|
-
return lowerRule(ctx, rule)
|
|
2352
|
-
})
|
|
2242
|
+
const labels: RuleLabels = { main: [], interiors: data.interiors.map(() => []) }
|
|
2243
|
+
const query: QueryIn = {
|
|
2244
|
+
interiors: data.interiors.map((interior, index) => ({
|
|
2245
|
+
rules: interior.rules.map((rule) => lowerRule(ctx, rule, labels.interiors[index]))
|
|
2246
|
+
})),
|
|
2247
|
+
head: data.finds.map(headTermOf),
|
|
2248
|
+
rules: data.rules.map((rule) => lowerRule(ctx, rule, labels.main))
|
|
2249
|
+
}
|
|
2250
|
+
if (data.kind === "cq") return { query, labels }
|
|
2251
|
+
const label = `recursion ${data.rec.name}`
|
|
2252
|
+
const self = data.interiors.length
|
|
2253
|
+
return {
|
|
2254
|
+
query: {
|
|
2255
|
+
...query,
|
|
2256
|
+
rec: {
|
|
2257
|
+
base: data.rec.base.map((rule) => {
|
|
2258
|
+
const lowered = lowerRule(ctx, rule)
|
|
2259
|
+
return { finds: recFinds(label, lowered), atoms: lowered.atoms, conditions: lowered.conditions }
|
|
2260
|
+
}),
|
|
2261
|
+
rec: data.rec.rec.map((rule) => recStep(label, lowerRule(ctx, rule), self))
|
|
2262
|
+
}
|
|
2353
2263
|
},
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
})
|
|
2264
|
+
labels
|
|
2265
|
+
}
|
|
2357
2266
|
}
|
|
2358
2267
|
|
|
2359
2268
|
export type {
|
|
@@ -2374,4 +2283,4 @@ export type {
|
|
|
2374
2283
|
RuleValue,
|
|
2375
2284
|
TermOps
|
|
2376
2285
|
}
|
|
2377
|
-
export { alignedHeadOf, EMPTY_RULE, lowerQuery, makeRawChain, makeRawQuery, query, taggedCmpLiteral }
|
|
2286
|
+
export { alignedHeadOf, EMPTY_RULE, lowerQuery, makeRawChain, makeRawQuery, query, queryHandleOf, taggedCmpLiteral }
|