@bjornpagen/bumbledb 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COOKBOOK.md +1450 -0
- package/README.md +69 -30
- package/dist/closed.d.ts +157 -38
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +232 -47
- package/dist/closed.js.map +1 -1
- package/dist/count.d.ts +64 -17
- package/dist/count.d.ts.map +1 -1
- package/dist/count.js +37 -14
- package/dist/count.js.map +1 -1
- package/dist/db.d.ts +27 -8
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +33 -10
- package/dist/db.js.map +1 -1
- package/dist/exhume.d.ts +12 -11
- package/dist/exhume.d.ts.map +1 -1
- package/dist/exhume.js +7 -6
- package/dist/exhume.js.map +1 -1
- package/dist/face.d.ts +135 -30
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +44 -19
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +130 -130
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +60 -94
- package/dist/fields.js.map +1 -1
- package/dist/index.d.ts +29 -24
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -14
- package/dist/index.js.map +1 -1
- package/dist/law.d.ts +224 -0
- package/dist/law.d.ts.map +1 -0
- package/dist/law.js +224 -0
- package/dist/law.js.map +1 -0
- package/dist/lower.d.ts +17 -7
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +66 -24
- package/dist/lower.js.map +1 -1
- package/dist/marshal.d.ts +36 -21
- package/dist/marshal.d.ts.map +1 -1
- package/dist/marshal.js +41 -25
- package/dist/marshal.js.map +1 -1
- package/dist/native.d.ts +6 -2
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +2 -2
- package/dist/native.js.map +1 -1
- package/dist/query/atom.d.ts +370 -174
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +75 -172
- package/dist/query/atom.js.map +1 -1
- package/dist/query/lower.d.ts +312 -109
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +906 -387
- package/dist/query/lower.js.map +1 -1
- package/dist/query/predicate.d.ts +70 -88
- package/dist/query/predicate.d.ts.map +1 -1
- package/dist/query/predicate.js +118 -72
- package/dist/query/predicate.js.map +1 -1
- package/dist/query/run.d.ts +19 -16
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +31 -24
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +178 -127
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +111 -108
- package/dist/query/scope.js.map +1 -1
- package/dist/query/select.d.ts +102 -80
- package/dist/query/select.d.ts.map +1 -1
- package/dist/query/select.js +39 -34
- package/dist/query/select.js.map +1 -1
- package/dist/relation.d.ts +48 -38
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +24 -19
- package/dist/relation.js.map +1 -1
- package/dist/schema.d.ts +41 -3
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +34 -9
- package/dist/schema.js.map +1 -1
- package/dist/spec.d.ts +7 -5
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js.map +1 -1
- package/dist/statements.d.ts +91 -36
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +42 -12
- package/dist/statements.js.map +1 -1
- package/package.json +4 -3
- package/src/closed.ts +448 -95
- package/src/count.ts +112 -18
- package/src/db.ts +45 -17
- package/src/exhume.ts +12 -11
- package/src/face.ts +211 -41
- package/src/fields.ts +199 -234
- package/src/index.ts +78 -61
- package/src/law.ts +519 -0
- package/src/lower.ts +82 -27
- package/src/marshal.ts +48 -30
- package/src/native.ts +8 -4
- package/src/query/atom.ts +525 -374
- package/src/query/lower.ts +1469 -538
- package/src/query/predicate.ts +208 -161
- package/src/query/run.ts +35 -25
- package/src/query/scope.ts +264 -218
- package/src/query/select.ts +168 -93
- package/src/relation.ts +47 -47
- package/src/schema.ts +69 -18
- package/src/spec.ts +7 -5
- package/src/statements.ts +124 -41
- package/dist/brand.d.ts +0 -59
- package/dist/brand.d.ts.map +0 -1
- package/dist/brand.js +0 -47
- package/dist/brand.js.map +0 -1
- package/src/brand.ts +0 -82
package/src/query/atom.ts
CHANGED
|
@@ -1,303 +1,346 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* mirroring the engine IR variant for variant
|
|
2
|
+
* Atoms and conditions, STRUCTURAL edition — the body vocabulary of a
|
|
3
|
+
* rule, mirroring the engine IR variant for variant
|
|
4
4
|
* (`bumbledb/crates/bumbledb/src/ir.rs`, the bijection target;
|
|
5
|
-
* `docs/architecture/20-query-ir.md` normative)
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* and `
|
|
12
|
-
*
|
|
5
|
+
* `docs/architecture/20-query-ir.md` normative). A `match` binding record
|
|
6
|
+
* binds fields to vars, params, ∈-set params, or bare structural literals
|
|
7
|
+
* (unmentioned fields ARE the wildcard — no wildcard value exists);
|
|
8
|
+
* `not(Rel, {...})` is negation-as-position (anti-join); `eq`/`ne` and the
|
|
9
|
+
* order roster, `pointIn`/`covers` (both spellings of `ir::CmpOp::PointIn`,
|
|
10
|
+
* always lowered interval-left), `allen` (the 13-bit mask pair
|
|
11
|
+
* comparison), and `and`/`or` (the input condition-tree grammar) complete
|
|
12
|
+
* the roster. Nothing beyond the IR exists here — and the walls the engine
|
|
13
|
+
* enforces at prepare are TYPES first: a var joins only domain-equal
|
|
14
|
+
* fields (`JoinOk`, checked against the rule environment AND against the
|
|
15
|
+
* binding record's own same-named siblings — two first occurrences of one
|
|
16
|
+
* name inside one record are a join too), an
|
|
17
|
+
* interval-typed var under a non-`pointIn` comparison is unwritable, and a
|
|
18
|
+
* negated atom's variables must be positively bound (env membership IS the
|
|
19
|
+
* safety rule). Every condition value carries its operands raw — the
|
|
20
|
+
* runtime representation is the type-inference carrier, no phantoms.
|
|
21
|
+
*
|
|
22
|
+
* This module also owns the plain runtime DATA a built rule is made of
|
|
23
|
+
* (`RuleData`/`RecData` and friends): frozen values the lowering walks —
|
|
24
|
+
* pure data, so lowering stays a pure, stable function of the query value.
|
|
13
25
|
*/
|
|
14
26
|
|
|
15
27
|
import * as errors from "@superbuilders/errors"
|
|
16
|
-
import type {
|
|
17
|
-
import {
|
|
18
|
-
import type {
|
|
19
|
-
import type { FieldData, FieldValue } from "#fields.ts"
|
|
20
|
-
import type { PredicateData } from "#query/predicate.ts"
|
|
28
|
+
import type { AnyClosed } from "#closed.ts"
|
|
29
|
+
import type { AnyField, ClosedIdField, Infer, IntervalValue } from "#fields.ts"
|
|
30
|
+
import type { ClassLookup, ClassRecordOf, SchemaClasses } from "#law.ts"
|
|
21
31
|
import type {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
32
|
+
ClassedField,
|
|
33
|
+
Duration,
|
|
34
|
+
EnvShape,
|
|
35
|
+
JoinOk,
|
|
25
36
|
MaskParam,
|
|
26
37
|
Param,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
TermContribution,
|
|
38
|
+
ParamValueAt,
|
|
39
|
+
SetParam,
|
|
40
|
+
ShapeOf,
|
|
31
41
|
Var
|
|
32
42
|
} from "#query/scope.ts"
|
|
33
|
-
import { isTerm
|
|
34
|
-
import type { AnyRelation, FieldsShape,
|
|
43
|
+
import { isTerm } from "#query/scope.ts"
|
|
44
|
+
import type { AnyRelation, FieldsShape, RelationFields } from "#relation.ts"
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* What a query atom matches over: an ordinary relation or a CLOSED
|
|
48
|
+
* vocabulary (ψ query atoms — the engine folds a resolvable closed atom
|
|
49
|
+
* into a plan-constant member set at prepare, or joins the L1-resident
|
|
50
|
+
* virtual image when the shape does not fold; the SDK never pre-folds and
|
|
51
|
+
* never knows which — transparency is the contract).
|
|
52
|
+
*/
|
|
53
|
+
type MatchOwner = AnyRelation | AnyClosed
|
|
35
54
|
|
|
36
55
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
56
|
+
* The matchable field block of an atom owner: a relation's declared
|
|
57
|
+
* fields; a closed relation's SEALED shape — the synthetic `id` (the
|
|
58
|
+
* roster-carrying descriptor) first, then the declared payload columns
|
|
59
|
+
* read through the typed `columns` carrier (the one source of payload
|
|
60
|
+
* typing — no parallel column table exists). The runtime twin is
|
|
61
|
+
* `matchFieldsOf` in `#query/lower.ts`; the id-first ordinal shift the two
|
|
62
|
+
* tiers share is pinned by the lowering golden.
|
|
43
63
|
*/
|
|
44
|
-
type
|
|
45
|
-
|
|
64
|
+
type MatchFields<R extends MatchOwner> = R extends AnyClosed
|
|
65
|
+
? { readonly id: ClosedIdField } & R["columns"]
|
|
66
|
+
: R extends AnyRelation
|
|
67
|
+
? RelationFields<R>
|
|
68
|
+
: never
|
|
69
|
+
|
|
70
|
+
/** One atom-binding position as runtime data. */
|
|
71
|
+
type BindingTermData =
|
|
72
|
+
| { readonly kind: "var"; readonly name: string }
|
|
73
|
+
| { readonly kind: "param"; readonly name: string }
|
|
74
|
+
| { readonly kind: "setParam"; readonly name: string }
|
|
46
75
|
| { readonly kind: "literal"; readonly value: unknown }
|
|
47
|
-
| { readonly kind: "oneOf"; readonly values: readonly unknown[] }
|
|
48
76
|
|
|
49
|
-
/** One resolved binding: the field's name, its
|
|
77
|
+
/** One resolved binding: the field's name, its descriptor, its law-computed class, and the term. */
|
|
50
78
|
interface BindingEntry {
|
|
51
79
|
readonly field: string
|
|
52
|
-
readonly data:
|
|
53
|
-
readonly
|
|
80
|
+
readonly data: AnyField
|
|
81
|
+
readonly class: string | undefined
|
|
82
|
+
readonly term: BindingTermData
|
|
54
83
|
}
|
|
55
84
|
|
|
56
|
-
/**
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
| { readonly kind: "predicate"; readonly pred: PredicateData }
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* One atom value (positive or negated — negation is a position in the
|
|
63
|
-
* rule, not a kind of atom, exactly as the IR reuses `Atom` unchanged).
|
|
64
|
-
* The phantom carries the params object the atom's bindings contribute.
|
|
65
|
-
*/
|
|
66
|
-
interface MatchAtom<P extends ParamsRecord> {
|
|
67
|
-
readonly item: "atom"
|
|
68
|
-
readonly negated: boolean
|
|
69
|
-
readonly source: AtomSourceData
|
|
85
|
+
/** One EDB atom as runtime data (either polarity — polarity is the rule item's; a closed owner is a ψ atom). */
|
|
86
|
+
interface AtomData {
|
|
87
|
+
readonly relation: MatchOwner
|
|
70
88
|
readonly bindings: readonly BindingEntry[]
|
|
71
|
-
readonly [phantom]?: P
|
|
72
89
|
}
|
|
73
90
|
|
|
91
|
+
/** One comparison operator name (mirrors `ir::CmpOp`). */
|
|
92
|
+
type CmpKind = "eq" | "ne" | "lt" | "le" | "gt" | "ge" | "pointIn" | "allen"
|
|
93
|
+
|
|
74
94
|
/** One comparison side as runtime data. */
|
|
75
|
-
type
|
|
76
|
-
| { readonly kind: "
|
|
95
|
+
type CmpTermData =
|
|
96
|
+
| { readonly kind: "var"; readonly name: string }
|
|
97
|
+
| { readonly kind: "param"; readonly name: string }
|
|
98
|
+
| { readonly kind: "setParam"; readonly name: string }
|
|
99
|
+
| { readonly kind: "measure"; readonly name: string }
|
|
77
100
|
| { readonly kind: "literal"; readonly value: unknown }
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
| { readonly kind: "lt" }
|
|
90
|
-
| { readonly kind: "le" }
|
|
91
|
-
| { readonly kind: "gt" }
|
|
92
|
-
| { readonly kind: "ge" }
|
|
93
|
-
| { readonly kind: "allen"; readonly mask: MaskData }
|
|
94
|
-
| { readonly kind: "pointIn" }
|
|
95
|
-
|
|
96
|
-
/** One comparison condition value. */
|
|
97
|
-
interface ComparisonItem<P extends ParamsRecord> {
|
|
98
|
-
readonly item: "cmp"
|
|
99
|
-
readonly op: CmpOpData
|
|
100
|
-
readonly lhs: CmpTerm
|
|
101
|
-
readonly rhs: CmpTerm
|
|
102
|
-
readonly [phantom]?: P
|
|
101
|
+
|
|
102
|
+
/** The `allen` mask position as runtime data. */
|
|
103
|
+
type MaskData = { readonly kind: "literal"; readonly mask: number } | { readonly kind: "param"; readonly name: string }
|
|
104
|
+
|
|
105
|
+
/** One comparison condition as runtime data (`mask` present exactly for `allen`). */
|
|
106
|
+
interface CmpData {
|
|
107
|
+
readonly kind: "cmp"
|
|
108
|
+
readonly op: CmpKind
|
|
109
|
+
readonly mask: MaskData | undefined
|
|
110
|
+
readonly lhs: CmpTermData
|
|
111
|
+
readonly rhs: CmpTermData
|
|
103
112
|
}
|
|
104
113
|
|
|
105
|
-
/**
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
* rules; the surface admits exactly what the IR admits.
|
|
109
|
-
*/
|
|
110
|
-
interface ConditionTreeItem<P extends ParamsRecord> {
|
|
111
|
-
readonly item: "tree"
|
|
114
|
+
/** One condition-tree node as runtime data (`ir::ConditionTree`). */
|
|
115
|
+
interface TreeData {
|
|
116
|
+
readonly kind: "tree"
|
|
112
117
|
readonly op: "and" | "or"
|
|
113
|
-
readonly children: readonly
|
|
114
|
-
readonly [phantom]?: P
|
|
118
|
+
readonly children: readonly CondData[]
|
|
115
119
|
}
|
|
116
120
|
|
|
117
|
-
/** Any condition
|
|
118
|
-
type
|
|
121
|
+
/** Any condition node as runtime data. */
|
|
122
|
+
type CondData = CmpData | TreeData
|
|
123
|
+
|
|
124
|
+
/** One aggregate's runtime description (select vocabulary, over var NAMES). */
|
|
125
|
+
type AggData =
|
|
126
|
+
| { readonly op: "count" }
|
|
127
|
+
| { readonly op: "countDistinct"; readonly over: string }
|
|
128
|
+
| { readonly op: "fold"; readonly fold: "sum" | "min" | "max"; readonly over: string | { readonly duration: string } }
|
|
129
|
+
| { readonly op: "arg"; readonly direction: "argMax" | "argMin"; readonly over: string; readonly key: string }
|
|
130
|
+
| { readonly op: "pack"; readonly over: string }
|
|
131
|
+
|
|
132
|
+
/** One classified select entry as runtime data. */
|
|
133
|
+
type SelectEntryData =
|
|
134
|
+
| { readonly kind: "var"; readonly over: string }
|
|
135
|
+
| { readonly kind: "measure"; readonly over: string }
|
|
136
|
+
| { readonly kind: "aggregate"; readonly agg: AggData }
|
|
137
|
+
|
|
138
|
+
/** One answer column: its name (the row object key) and its entry. */
|
|
139
|
+
interface SelectColumn {
|
|
140
|
+
readonly name: string
|
|
141
|
+
readonly entry: SelectEntryData
|
|
142
|
+
}
|
|
119
143
|
|
|
120
|
-
/**
|
|
121
|
-
type
|
|
144
|
+
/** One body item of a rule, in written order. */
|
|
145
|
+
type RuleItem =
|
|
146
|
+
| { readonly kind: "atom"; readonly atom: AtomData }
|
|
147
|
+
| { readonly kind: "negated"; readonly atom: AtomData }
|
|
148
|
+
| { readonly kind: "idb"; readonly rec: RecData; readonly vars: readonly string[] }
|
|
149
|
+
| { readonly kind: "cond"; readonly cond: CondData }
|
|
122
150
|
|
|
123
151
|
/**
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
* `sum`/`min`/`max`; every other position is unwritable here exactly as
|
|
128
|
-
* the IR rejects it typed. A ray has no finite measure — the engine's
|
|
129
|
-
* `MeasureOfRay` execution error; exclude rays first (`allen` against a
|
|
130
|
-
* bounded window).
|
|
152
|
+
* One use of a parameter inside a rule, in written order: the census the
|
|
153
|
+
* query-level registry folds (first use mints the dense `ParamId`, first
|
|
154
|
+
* FIELD-ANCHORED use types the wire).
|
|
131
155
|
*/
|
|
132
|
-
interface
|
|
133
|
-
readonly
|
|
134
|
-
readonly
|
|
156
|
+
interface ParamUse {
|
|
157
|
+
readonly name: string
|
|
158
|
+
readonly shape: "value" | "set" | "mask"
|
|
159
|
+
readonly anchor: AnyField | "measure" | undefined
|
|
160
|
+
readonly op: "binding" | CmpKind
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** One complete rule as runtime data. */
|
|
164
|
+
interface RuleData {
|
|
165
|
+
readonly items: readonly RuleItem[]
|
|
166
|
+
readonly select: readonly SelectColumn[]
|
|
167
|
+
/** Variable name → the classed slot its FIRST positive binding carries (the runtime env — descriptor + class). */
|
|
168
|
+
readonly varFields: Readonly<Record<string, ClassedField>>
|
|
169
|
+
readonly paramUses: readonly ParamUse[]
|
|
135
170
|
}
|
|
136
171
|
|
|
137
172
|
/**
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
* IR's membership typing rule: an element-typed term at an interval field
|
|
142
|
-
* is point membership; an interval-typed term is value equality).
|
|
173
|
+
* One recursive predicate's runtime description — identity keys the dense
|
|
174
|
+
* `PredId` at lowering. `rules` is appended by `rec.rule(...)` and sealed
|
|
175
|
+
* (frozen) when the program's output is declared.
|
|
143
176
|
*/
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
| Param<string, V>
|
|
149
|
-
| ParamSet<string, V>
|
|
150
|
-
| (V extends IntervalValue ? PointTermInput : never)
|
|
177
|
+
interface RecData {
|
|
178
|
+
readonly name: string
|
|
179
|
+
readonly rules: RuleData[]
|
|
180
|
+
}
|
|
151
181
|
|
|
152
|
-
/**
|
|
153
|
-
|
|
182
|
+
/**
|
|
183
|
+
* What a binding position of field `F` accepts: a bare structural literal
|
|
184
|
+
* of the field's value type, a var/param/∈-set-param term — and, when the
|
|
185
|
+
* field is interval-typed, a bare point literal (the IR's membership
|
|
186
|
+
* typing rule: an element-typed term at an interval field is point
|
|
187
|
+
* membership; an interval-typed term is value equality).
|
|
188
|
+
*/
|
|
189
|
+
type BindingInput<F extends AnyField> =
|
|
190
|
+
| Infer<F>
|
|
191
|
+
| (F extends { readonly kind: "interval" } ? bigint : never)
|
|
192
|
+
| Var<string>
|
|
193
|
+
| Param<string>
|
|
194
|
+
| SetParam<string>
|
|
154
195
|
|
|
155
196
|
/**
|
|
156
|
-
* The `match` bindings record: per field, a term of that
|
|
157
|
-
* unmentioned fields are wildcards (absence IS
|
|
158
|
-
* no wildcard variant to spell).
|
|
197
|
+
* The `match`/`not` bindings record: per field, a term or literal of that
|
|
198
|
+
* field's structural type; unmentioned fields are wildcards (absence IS
|
|
199
|
+
* the wildcard — the IR has no wildcard variant to spell).
|
|
159
200
|
*/
|
|
160
|
-
type
|
|
161
|
-
readonly [K in keyof
|
|
201
|
+
type MatchShape<F extends FieldsShape> = {
|
|
202
|
+
readonly [K in keyof F]?: BindingInput<F[K]>
|
|
162
203
|
}
|
|
163
204
|
|
|
164
|
-
/**
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
205
|
+
/**
|
|
206
|
+
* One field position of a bindings record as a classed slot: the declared
|
|
207
|
+
* descriptor plus the slot's law-computed class, read off the relation's
|
|
208
|
+
* class record (`CR` — the schema class map's entry for the atom's
|
|
209
|
+
* relation). The one shape every join judgment compares.
|
|
210
|
+
*/
|
|
211
|
+
type SlotAt<F extends FieldsShape, CR, K> = {
|
|
212
|
+
readonly field: F[K & keyof F]
|
|
213
|
+
readonly class: ClassLookup<CR, K>
|
|
170
214
|
}
|
|
171
215
|
|
|
172
|
-
/**
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
return Object.freeze({ kind: "term" as const, value })
|
|
181
|
-
}
|
|
182
|
-
if (isOneOf(value)) {
|
|
183
|
-
return Object.freeze({ kind: "oneOf" as const, values: Object.freeze([...value.literals]) })
|
|
184
|
-
}
|
|
185
|
-
return Object.freeze({ kind: "literal" as const, value })
|
|
186
|
-
}
|
|
216
|
+
/**
|
|
217
|
+
* The var binding's judgment against the incoming rule environment: a name
|
|
218
|
+
* already bound must land on a class-equal slot (bare pairs only with bare).
|
|
219
|
+
*/
|
|
220
|
+
type EnvJoinOk<Env extends EnvShape, F extends FieldsShape, CR, K, N extends string> = N extends keyof Env
|
|
221
|
+
? JoinOk<Env[N], SlotAt<F, CR, K>>
|
|
222
|
+
: true
|
|
187
223
|
|
|
188
224
|
/**
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
* the
|
|
225
|
+
* The var binding's judgment against its OWN record's siblings: two
|
|
226
|
+
* bindings of one var name inside a single bindings record are the same
|
|
227
|
+
* join the environment check judges across atoms, so every same-named
|
|
228
|
+
* sibling must be class-equal too. Without this arm two FIRST occurrences
|
|
229
|
+
* of one name (a record the environment has not seen yet) would meet no
|
|
230
|
+
* check at all — the intra-atom join would silently cross classes.
|
|
192
231
|
*/
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const entries: BindingEntry[] = []
|
|
199
|
-
for (const [fieldName, value] of Object.entries(bindings)) {
|
|
200
|
-
if (value === undefined) {
|
|
201
|
-
continue
|
|
202
|
-
}
|
|
203
|
-
const declared = fields.find(function byName(candidate) {
|
|
204
|
-
return candidate.name === fieldName
|
|
205
|
-
})
|
|
206
|
-
if (declared === undefined) {
|
|
207
|
-
throw errors.new(`${context} has no field ${fieldName}`)
|
|
208
|
-
}
|
|
209
|
-
entries.push(
|
|
210
|
-
Object.freeze({
|
|
211
|
-
field: fieldName,
|
|
212
|
-
data: declared.field,
|
|
213
|
-
term: bindingTermOf(`${context}.${fieldName}`, value)
|
|
214
|
-
})
|
|
215
|
-
)
|
|
216
|
-
}
|
|
217
|
-
return Object.freeze(entries)
|
|
218
|
-
}
|
|
232
|
+
type SiblingJoinOk<F extends FieldsShape, CR, B, K extends keyof B, N extends string> = false extends {
|
|
233
|
+
[K2 in Exclude<keyof B & keyof F, K>]: B[K2] extends Var<N> ? JoinOk<SlotAt<F, CR, K2>, SlotAt<F, CR, K>> : true
|
|
234
|
+
}[Exclude<keyof B & keyof F, K>]
|
|
235
|
+
? false
|
|
236
|
+
: true
|
|
219
237
|
|
|
220
238
|
/**
|
|
221
|
-
* The
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
239
|
+
* The per-property join judgment of a bindings record: a var binding must
|
|
240
|
+
* be class-equal to the rule environment's binding of the name AND to
|
|
241
|
+
* every same-named sibling of its own record (a cross-class reuse maps
|
|
242
|
+
* the property to `never` — the compile error the old value brand carried,
|
|
243
|
+
* now law-born off the schema type's class map).
|
|
225
244
|
*/
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
relation.data.fields,
|
|
237
|
-
Object.fromEntries(Object.entries(bindings))
|
|
238
|
-
)
|
|
239
|
-
})
|
|
245
|
+
type BindingOk<Env extends EnvShape, F extends FieldsShape, CR, B, K extends keyof B> =
|
|
246
|
+
B[K] extends Var<infer N extends string>
|
|
247
|
+
? [EnvJoinOk<Env, F, CR, K, N>, SiblingJoinOk<F, CR, B, K, N>] extends [true, true]
|
|
248
|
+
? true
|
|
249
|
+
: false
|
|
250
|
+
: true
|
|
251
|
+
|
|
252
|
+
/** The validated bindings record (intersect with the inferred `B` — errors land on the offending property). */
|
|
253
|
+
type CheckBindings<Env extends EnvShape, F extends FieldsShape, CR, B> = {
|
|
254
|
+
readonly [K in keyof B]: K extends keyof F ? (BindingOk<Env, F, CR, B, K> extends true ? B[K] : never) : never
|
|
240
255
|
}
|
|
241
256
|
|
|
257
|
+
/** The environment a bindings record contributes: var name → the bound slot (descriptor + class). */
|
|
258
|
+
type BindEnv<F extends FieldsShape, CR, B> = {
|
|
259
|
+
readonly [K in keyof B & keyof F as B[K] extends Var<infer N extends string> ? N : never]: SlotAt<F, CR, K>
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** The params-object fragments a bindings record contributes (one union member per param use). */
|
|
263
|
+
type BindParams<F extends FieldsShape, B> = {
|
|
264
|
+
[K in keyof B & keyof F]: B[K] extends Param<infer P extends string>
|
|
265
|
+
? { readonly [Q in P]: ParamValueAt<F[K]> }
|
|
266
|
+
: B[K] extends SetParam<infer P extends string>
|
|
267
|
+
? { readonly [Q in P]: readonly ParamValueAt<F[K]>[] }
|
|
268
|
+
: never
|
|
269
|
+
}[keyof B & keyof F]
|
|
270
|
+
|
|
242
271
|
/**
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
* binding is refused HERE, at construction: it lowers to a synthetic
|
|
248
|
-
* variable bound only inside the atom plus a rule-level OR — which is
|
|
249
|
-
* exactly the shape the safety rule rejects, and semantically wrong for
|
|
250
|
-
* negation anyway (¬∃(f = a ∨ f = b) is a CONJUNCTION of negated atoms,
|
|
251
|
-
* ¬∃(f = a) ∧ ¬∃(f = b), never one negated atom's OR).
|
|
272
|
+
* One comparison VALUE: op plus its operands, raw — the runtime
|
|
273
|
+
* representation carries the operand types, so `.where`'s environment
|
|
274
|
+
* check and the params inference both read the value itself (no phantom).
|
|
275
|
+
* `mask` is populated exactly for `allen`.
|
|
252
276
|
*/
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const source = atom.source.kind === "relation" ? atom.source.relation.name : `predicate ${atom.source.pred.name}`
|
|
260
|
-
throw errors.new(
|
|
261
|
-
`negated ${source} atom binds ${binding.field} with oneOf(...) — ¬∃(${binding.field} = a ∨ ${binding.field} = b) means ¬∃(${binding.field} = a) ∧ ¬∃(${binding.field} = b): write one not(match(...)) per literal, or bind a paramSet`
|
|
262
|
-
)
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
return Object.freeze({
|
|
266
|
-
item: "atom" as const,
|
|
267
|
-
negated: true,
|
|
268
|
-
source: atom.source,
|
|
269
|
-
bindings: atom.bindings
|
|
270
|
-
})
|
|
277
|
+
interface Cmp<Op extends CmpKind, L, R, M = undefined> {
|
|
278
|
+
readonly cond: "cmp"
|
|
279
|
+
readonly op: Op
|
|
280
|
+
readonly lhs: L
|
|
281
|
+
readonly rhs: R
|
|
282
|
+
readonly mask: M
|
|
271
283
|
}
|
|
272
284
|
|
|
273
|
-
/**
|
|
274
|
-
|
|
275
|
-
|
|
285
|
+
/** One condition-tree VALUE (`and`/`or` over comparisons and nested trees). */
|
|
286
|
+
interface Tree<Ch extends readonly AnyTreeChild[]> {
|
|
287
|
+
readonly cond: "tree"
|
|
288
|
+
readonly op: "and" | "or"
|
|
289
|
+
readonly children: Ch
|
|
276
290
|
}
|
|
277
291
|
|
|
278
|
-
/**
|
|
279
|
-
|
|
280
|
-
|
|
292
|
+
/**
|
|
293
|
+
* One negated-atom VALUE — negation is a position in the rule (anti-join
|
|
294
|
+
* over sets, no null trick): a binding satisfies it iff NO fact matches.
|
|
295
|
+
* Its variables must be positively bound in the rule — environment
|
|
296
|
+
* membership at `.where` IS the safety rule, a compile error before it is
|
|
297
|
+
* the engine's refusal.
|
|
298
|
+
*/
|
|
299
|
+
interface NotAtom<R extends MatchOwner, B> {
|
|
300
|
+
readonly cond: "not"
|
|
301
|
+
readonly relation: R
|
|
302
|
+
readonly bindings: B
|
|
281
303
|
}
|
|
282
304
|
|
|
283
|
-
/**
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
305
|
+
/** Any comparison value. */
|
|
306
|
+
type AnyCmp = Cmp<CmpKind, unknown, unknown, unknown>
|
|
307
|
+
|
|
308
|
+
/** Any condition-tree child (trees hold comparisons and trees — never atoms). */
|
|
309
|
+
type AnyTreeChild = AnyCmp | Tree<readonly AnyTreeChild[]>
|
|
310
|
+
|
|
311
|
+
/** Any negated-atom value. */
|
|
312
|
+
type AnyNotAtom = NotAtom<MatchOwner, unknown>
|
|
313
|
+
|
|
314
|
+
/** Any `.where` input: a comparison, a condition tree, or a negated atom. */
|
|
315
|
+
type AnyCond = AnyCmp | Tree<readonly AnyTreeChild[]> | AnyNotAtom
|
|
316
|
+
|
|
317
|
+
/** What `eq`'s right side accepts (`ParamSet` is `Eq`-only — the IR's rule). */
|
|
318
|
+
type EqRight = Var<string> | Param<string> | SetParam<string> | bigint | string | boolean | Uint8Array | IntervalValue
|
|
319
|
+
|
|
320
|
+
/** What `ne`'s right side accepts. */
|
|
321
|
+
type NeRight = Var<string> | Param<string> | bigint | string | boolean | Uint8Array | IntervalValue
|
|
322
|
+
|
|
323
|
+
/** One side of an order comparison: orderable terms only (the IR's comparison rules). */
|
|
324
|
+
type OrderSide = Var<string> | Param<string> | Duration<string> | bigint
|
|
325
|
+
|
|
326
|
+
/** The point side of `pointIn`/`covers`. */
|
|
327
|
+
type PointSide = Var<string> | Param<string> | bigint
|
|
328
|
+
|
|
329
|
+
/** The interval side of `pointIn`/`covers`/`allen`. */
|
|
330
|
+
type IntervalSide = Var<string> | Param<string> | IntervalValue
|
|
331
|
+
|
|
332
|
+
/** Builds one comparison value. */
|
|
333
|
+
function comparison<Op extends CmpKind, L, R, M>(op: Op, lhs: L, rhs: R, mask: M): Cmp<Op, L, R, M> {
|
|
334
|
+
return Object.freeze({ cond: "cmp", op, lhs, rhs, mask })
|
|
292
335
|
}
|
|
293
336
|
|
|
294
337
|
/**
|
|
295
338
|
* Rejects a comparison with no term side: it is constant-valued, the
|
|
296
|
-
* engine's own validation refuses it, and the lowering has no
|
|
339
|
+
* engine's own validation refuses it, and the lowering has no anchored
|
|
297
340
|
* position to type the literals by — fail here with the same verdict.
|
|
298
341
|
*/
|
|
299
|
-
function assertTermSide(op: string, lhs:
|
|
300
|
-
if (lhs
|
|
342
|
+
function assertTermSide(op: string, lhs: unknown, rhs: unknown): void {
|
|
343
|
+
if (!isTerm(lhs) && !isTerm(rhs)) {
|
|
301
344
|
throw errors.new(
|
|
302
345
|
`${op}: a comparison without a variable or parameter side is constant-valued — write the query you mean`
|
|
303
346
|
)
|
|
@@ -305,100 +348,73 @@ function assertTermSide(op: string, lhs: CmpTerm, rhs: CmpTerm): void {
|
|
|
305
348
|
}
|
|
306
349
|
|
|
307
350
|
/**
|
|
308
|
-
* The equality
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
*
|
|
351
|
+
* The equality comparison (`ir::CmpOp::Eq`) — a bound variable against a
|
|
352
|
+
* variable (var-to-var unification, domain-equal by the environment
|
|
353
|
+
* check), a param (typed by the variable), an ∈-set param (`Eq`-only, the
|
|
354
|
+
* IR's set rule), or a bare literal of the variable's own value type.
|
|
355
|
+
* Prefer direct placement in `match` where punning applies.
|
|
313
356
|
*/
|
|
314
|
-
function
|
|
315
|
-
left
|
|
316
|
-
right: R
|
|
317
|
-
): ComparisonItem<ParamsShape<TermContribution<R>>> {
|
|
318
|
-
return comparison(Object.freeze({ kind: "eq" as const }), cmpTermOf(left), cmpTermOf(right))
|
|
357
|
+
function eq<L extends Var<string>, const R extends EqRight>(left: L, right: R): Cmp<"eq", L, R> {
|
|
358
|
+
return comparison("eq", left, right, undefined)
|
|
319
359
|
}
|
|
320
360
|
|
|
321
361
|
/** Typed disequality (`ir::CmpOp::Ne`). "Not in set" has no operator — write a negated atom. */
|
|
322
|
-
function ne<
|
|
323
|
-
left
|
|
324
|
-
right: R
|
|
325
|
-
): ComparisonItem<ParamsShape<TermContribution<R>>> {
|
|
326
|
-
return comparison(Object.freeze({ kind: "ne" as const }), cmpTermOf(left), cmpTermOf(right))
|
|
362
|
+
function ne<L extends Var<string>, const R extends NeRight>(left: L, right: R): Cmp<"ne", L, R> {
|
|
363
|
+
return comparison("ne", left, right, undefined)
|
|
327
364
|
}
|
|
328
365
|
|
|
329
|
-
/**
|
|
330
|
-
* One side of an order comparison: a u64/i64-typed var or param, a bigint
|
|
331
|
-
* literal, or the measure (`duration(v)`) — order operators are legal for
|
|
332
|
-
* the orderable types only, never intervals/bytes/strings/bools (the IR's
|
|
333
|
-
* comparison rules; each refusal is the engine's own typed diagnostic).
|
|
334
|
-
*/
|
|
335
|
-
type OrderInput = Var<bigint> | Param<string, bigint> | bigint | Duration
|
|
336
|
-
|
|
337
366
|
/** The shared order-comparison constructor. */
|
|
338
|
-
function order<const L extends
|
|
339
|
-
op:
|
|
367
|
+
function order<Op extends "lt" | "le" | "gt" | "ge", const L extends OrderSide, const R extends OrderSide>(
|
|
368
|
+
op: Op,
|
|
340
369
|
left: L,
|
|
341
370
|
right: R
|
|
342
|
-
):
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
assertTermSide(op, lhs, rhs)
|
|
346
|
-
return comparison(Object.freeze({ kind: op }), lhs, rhs)
|
|
371
|
+
): Cmp<Op, L, R> {
|
|
372
|
+
assertTermSide(op, left, right)
|
|
373
|
+
return comparison(op, left, right, undefined)
|
|
347
374
|
}
|
|
348
375
|
|
|
349
|
-
/** Strict less-than (`ir::CmpOp::Lt`). */
|
|
350
|
-
function lt<const L extends
|
|
351
|
-
left: L,
|
|
352
|
-
right: R
|
|
353
|
-
): ComparisonItem<ParamsShape<TermContribution<L> | TermContribution<R>>> {
|
|
376
|
+
/** Strict less-than (`ir::CmpOp::Lt`) — orderable sides only, never intervals/bytes/strings/bools. */
|
|
377
|
+
function lt<const L extends OrderSide, const R extends OrderSide>(left: L, right: R): Cmp<"lt", L, R> {
|
|
354
378
|
return order("lt", left, right)
|
|
355
379
|
}
|
|
356
380
|
|
|
357
381
|
/** Less-or-equal (`ir::CmpOp::Le`). */
|
|
358
|
-
function le<const L extends
|
|
359
|
-
left: L,
|
|
360
|
-
right: R
|
|
361
|
-
): ComparisonItem<ParamsShape<TermContribution<L> | TermContribution<R>>> {
|
|
382
|
+
function le<const L extends OrderSide, const R extends OrderSide>(left: L, right: R): Cmp<"le", L, R> {
|
|
362
383
|
return order("le", left, right)
|
|
363
384
|
}
|
|
364
385
|
|
|
365
386
|
/** Strict greater-than (`ir::CmpOp::Gt`). */
|
|
366
|
-
function gt<const L extends
|
|
367
|
-
left: L,
|
|
368
|
-
right: R
|
|
369
|
-
): ComparisonItem<ParamsShape<TermContribution<L> | TermContribution<R>>> {
|
|
387
|
+
function gt<const L extends OrderSide, const R extends OrderSide>(left: L, right: R): Cmp<"gt", L, R> {
|
|
370
388
|
return order("gt", left, right)
|
|
371
389
|
}
|
|
372
390
|
|
|
373
391
|
/** Greater-or-equal (`ir::CmpOp::Ge`). */
|
|
374
|
-
function ge<const L extends
|
|
375
|
-
left: L,
|
|
376
|
-
right: R
|
|
377
|
-
): ComparisonItem<ParamsShape<TermContribution<L> | TermContribution<R>>> {
|
|
392
|
+
function ge<const L extends OrderSide, const R extends OrderSide>(left: L, right: R): Cmp<"ge", L, R> {
|
|
378
393
|
return order("ge", left, right)
|
|
379
394
|
}
|
|
380
395
|
|
|
381
|
-
/**
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
396
|
+
/**
|
|
397
|
+
* Point membership as a predicate (`ir::CmpOp::PointIn`), membership
|
|
398
|
+
* spelling: `pointIn(t, w)` holds iff `w.start ≤ t < w.end`. The IR
|
|
399
|
+
* orders the operands interval-left, point-right; the value stores them
|
|
400
|
+
* that way whatever the surface spelling.
|
|
401
|
+
*/
|
|
402
|
+
function pointIn<const P extends PointSide, const I extends IntervalSide>(point: P, interval: I): Cmp<"pointIn", I, P> {
|
|
403
|
+
assertTermSide("pointIn", point, interval)
|
|
404
|
+
return comparison("pointIn", interval, point, undefined)
|
|
405
|
+
}
|
|
386
406
|
|
|
387
407
|
/**
|
|
388
|
-
* Point membership
|
|
389
|
-
*
|
|
390
|
-
*
|
|
391
|
-
*
|
|
392
|
-
*
|
|
408
|
+
* Point membership, coverage spelling — `covers(w, t)` is `pointIn(t, w)`
|
|
409
|
+
* with the interval written first (the IR's own operand order; a literal
|
|
410
|
+
* `span(...)` left operand is legal and tags by the point sibling's
|
|
411
|
+
* element domain — the bug-hunt fix, now also a type-level guarantee).
|
|
412
|
+
* Interval ⊇ interval is NOT this operator; that predicate is
|
|
413
|
+
* `allen(a, ALLEN.covers, b)`.
|
|
393
414
|
*/
|
|
394
|
-
function covers<const I extends
|
|
395
|
-
interval
|
|
396
|
-
point
|
|
397
|
-
): ComparisonItem<ParamsShape<TermContribution<I> | TermContribution<T>>> {
|
|
398
|
-
const lhs = cmpTermOf(interval)
|
|
399
|
-
const rhs = cmpTermOf(point)
|
|
400
|
-
assertTermSide("covers", lhs, rhs)
|
|
401
|
-
return comparison(Object.freeze({ kind: "pointIn" as const }), lhs, rhs)
|
|
415
|
+
function covers<const I extends IntervalSide, const P extends PointSide>(interval: I, point: P): Cmp<"pointIn", I, P> {
|
|
416
|
+
assertTermSide("covers", interval, point)
|
|
417
|
+
return comparison("pointIn", interval, point, undefined)
|
|
402
418
|
}
|
|
403
419
|
|
|
404
420
|
/**
|
|
@@ -444,46 +460,22 @@ const ALLEN = Object.freeze({
|
|
|
444
460
|
* THE interval-pair comparison (`ir::CmpOp::Allen`): two interval terms of
|
|
445
461
|
* one element type, satisfied iff the pair's classification is in the
|
|
446
462
|
* 13-bit mask — a literal built from the `ALLEN` constants, or a mask
|
|
447
|
-
* parameter (
|
|
463
|
+
* parameter (`r.maskParam`). Vacuous masks (empty/full) are the engine's
|
|
448
464
|
* two distinct typed rejections; nothing is pre-judged here beyond the
|
|
449
465
|
* representable bit range.
|
|
450
466
|
*/
|
|
451
|
-
function allen<
|
|
452
|
-
const A extends IntervalInput,
|
|
453
|
-
const M extends number | MaskParam<string>,
|
|
454
|
-
const B extends IntervalInput
|
|
455
|
-
>(
|
|
467
|
+
function allen<const A extends IntervalSide, const M extends number | MaskParam<string>, const B extends IntervalSide>(
|
|
456
468
|
left: A,
|
|
457
469
|
mask: M,
|
|
458
470
|
right: B
|
|
459
|
-
):
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
if (typeof maskValue === "number") {
|
|
465
|
-
if (!Number.isInteger(maskValue) || maskValue < 0 || maskValue > ALLEN_ALL_BITS) {
|
|
466
|
-
throw errors.new(
|
|
467
|
-
`allen mask ${maskValue} is not a 13-bit mask — build masks from the ALLEN constants (bumbledb allen.rs: bits above the low 13 are unrepresentable)`
|
|
468
|
-
)
|
|
469
|
-
}
|
|
470
|
-
return comparison(
|
|
471
|
-
Object.freeze({
|
|
472
|
-
kind: "allen" as const,
|
|
473
|
-
mask: Object.freeze({ kind: "literal" as const, mask: maskValue })
|
|
474
|
-
}),
|
|
475
|
-
lhs,
|
|
476
|
-
rhs
|
|
471
|
+
): Cmp<"allen", A, B, M> {
|
|
472
|
+
assertTermSide("allen", left, right)
|
|
473
|
+
if (typeof mask === "number" && (!Number.isInteger(mask) || mask < 0 || mask > ALLEN_ALL_BITS)) {
|
|
474
|
+
throw errors.new(
|
|
475
|
+
`allen mask ${mask} is not a 13-bit mask — build masks from the ALLEN constants (bumbledb allen.rs: bits above the low 13 are unrepresentable)`
|
|
477
476
|
)
|
|
478
477
|
}
|
|
479
|
-
return comparison(
|
|
480
|
-
Object.freeze({
|
|
481
|
-
kind: "allen" as const,
|
|
482
|
-
mask: Object.freeze({ kind: "param" as const, param: maskValue })
|
|
483
|
-
}),
|
|
484
|
-
lhs,
|
|
485
|
-
rhs
|
|
486
|
-
)
|
|
478
|
+
return comparison("allen", left, right, mask)
|
|
487
479
|
}
|
|
488
480
|
|
|
489
481
|
/**
|
|
@@ -492,14 +484,8 @@ function allen<
|
|
|
492
484
|
* nesting under `or`, and the empty combination keeps the IR's algebraic
|
|
493
485
|
* reading (`And([])` is true).
|
|
494
486
|
*/
|
|
495
|
-
function and<const C extends readonly
|
|
496
|
-
|
|
497
|
-
): ConditionTreeItem<ParamsShape<ItemParams<C[number]>>> {
|
|
498
|
-
return Object.freeze({
|
|
499
|
-
item: "tree" as const,
|
|
500
|
-
op: "and" as const,
|
|
501
|
-
children: Object.freeze([...children])
|
|
502
|
-
})
|
|
487
|
+
function and<const C extends readonly AnyTreeChild[]>(...children: C): Tree<C> {
|
|
488
|
+
return Object.freeze({ cond: "tree", op: "and", children: Object.freeze(children) })
|
|
503
489
|
}
|
|
504
490
|
|
|
505
491
|
/**
|
|
@@ -508,49 +494,214 @@ function and<const C extends readonly AnyCondition[]>(
|
|
|
508
494
|
* to DNF rules engine-side (OR is data or it is nothing). `Or([])` keeps
|
|
509
495
|
* its algebraic reading (false: the rule denotes nothing).
|
|
510
496
|
*/
|
|
511
|
-
function or<const C extends readonly
|
|
512
|
-
|
|
513
|
-
): ConditionTreeItem<ParamsShape<ItemParams<C[number]>>> {
|
|
514
|
-
return Object.freeze({
|
|
515
|
-
item: "tree" as const,
|
|
516
|
-
op: "or" as const,
|
|
517
|
-
children: Object.freeze([...children])
|
|
518
|
-
})
|
|
497
|
+
function or<const C extends readonly AnyTreeChild[]>(...children: C): Tree<C> {
|
|
498
|
+
return Object.freeze({ cond: "tree", op: "or", children: Object.freeze(children) })
|
|
519
499
|
}
|
|
520
500
|
|
|
521
501
|
/**
|
|
522
|
-
*
|
|
523
|
-
*
|
|
524
|
-
*
|
|
502
|
+
* Negation — anti-join over sets: `not(Rel, { field: r.var("x"), ... })`
|
|
503
|
+
* rejects every binding some matching fact extends. A negated atom binds
|
|
504
|
+
* nothing, only rejects: every variable it names must be positively bound
|
|
505
|
+
* in the rule, which `.where`'s environment check makes a COMPILE error
|
|
506
|
+
* (the engine's safety refusal stands behind it). A CLOSED owner is legal
|
|
507
|
+
* here too — the engine folds a resolvable negated closed atom to the
|
|
508
|
+
* COMPLEMENT of its member set (domain-witness guarded), and the SDK's
|
|
509
|
+
* negation rules apply to it unchanged.
|
|
525
510
|
*/
|
|
526
|
-
function
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
return Object.freeze({ measure: over })
|
|
511
|
+
function not<R extends MatchOwner, const B extends MatchShape<MatchFields<R>>>(
|
|
512
|
+
relation: R,
|
|
513
|
+
bindings: B
|
|
514
|
+
): NotAtom<R, B> {
|
|
515
|
+
const value: NotAtom<R, B> = { cond: "not", relation, bindings }
|
|
516
|
+
return Object.freeze(value)
|
|
533
517
|
}
|
|
534
518
|
|
|
519
|
+
/** Whether a var name is bound in the environment at an orderable (u64/i64) field. */
|
|
520
|
+
type OrderVarOk<Env extends EnvShape, N extends string> = N extends keyof Env
|
|
521
|
+
? Env[N]["field"]["kind"] extends "u64" | "i64"
|
|
522
|
+
? true
|
|
523
|
+
: false
|
|
524
|
+
: false
|
|
525
|
+
|
|
526
|
+
/** Whether a var name is bound at an interval field. */
|
|
527
|
+
type IntervalVarOk<Env extends EnvShape, N extends string> = N extends keyof Env
|
|
528
|
+
? Env[N]["field"]["kind"] extends "interval"
|
|
529
|
+
? true
|
|
530
|
+
: false
|
|
531
|
+
: false
|
|
532
|
+
|
|
533
|
+
/** One order-comparison side's judgment against the environment. */
|
|
534
|
+
type OrderSideOk<Env extends EnvShape, T> =
|
|
535
|
+
T extends Var<infer N extends string>
|
|
536
|
+
? OrderVarOk<Env, N>
|
|
537
|
+
: T extends Duration<infer N extends string>
|
|
538
|
+
? IntervalVarOk<Env, N>
|
|
539
|
+
: true
|
|
540
|
+
|
|
541
|
+
/** One point side's judgment. */
|
|
542
|
+
type PointSideOk<Env extends EnvShape, T> = T extends Var<infer N extends string> ? OrderVarOk<Env, N> : true
|
|
543
|
+
|
|
544
|
+
/** One interval side's judgment. */
|
|
545
|
+
type IntervalSideOk<Env extends EnvShape, T> = T extends Var<infer N extends string> ? IntervalVarOk<Env, N> : true
|
|
546
|
+
|
|
547
|
+
/** The `eq`/`ne` judgment: left var bound; right joins it (class-equal var, param, or an exact-type literal). */
|
|
548
|
+
type EqOk<Env extends EnvShape, L, R> =
|
|
549
|
+
L extends Var<infer N extends string>
|
|
550
|
+
? N extends keyof Env
|
|
551
|
+
? R extends Var<infer M extends string>
|
|
552
|
+
? M extends keyof Env
|
|
553
|
+
? JoinOk<Env[N], Env[M]>
|
|
554
|
+
: false
|
|
555
|
+
: R extends Param<string> | SetParam<string>
|
|
556
|
+
? true
|
|
557
|
+
: [R] extends [Infer<Env[N]["field"]>]
|
|
558
|
+
? true
|
|
559
|
+
: false
|
|
560
|
+
: false
|
|
561
|
+
: false
|
|
562
|
+
|
|
563
|
+
/** One negated-atom binding's judgment: a var must be positively bound (safety) AND class-equal. */
|
|
564
|
+
type NotBindingOk<Env extends EnvShape, S extends ClassedField, T> =
|
|
565
|
+
T extends Var<infer N extends string> ? (N extends keyof Env ? JoinOk<Env[N], S> : false) : true
|
|
566
|
+
|
|
567
|
+
/** The whole negated atom's judgment (`CR` — the negated relation's class record off the schema class map). */
|
|
568
|
+
type NotOk<Env extends EnvShape, F extends FieldsShape, CR, B> = false extends {
|
|
569
|
+
[K in keyof B]: NotBindingOk<Env, SlotAt<F, CR, K>, B[K]>
|
|
570
|
+
}[keyof B]
|
|
571
|
+
? false
|
|
572
|
+
: true
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* One condition's judgment against the rule environment — the type-level
|
|
576
|
+
* twin of the engine's comparison roster: class-equal joins (off the
|
|
577
|
+
* schema type's class map), orderable order sides (an interval var under a
|
|
578
|
+
* non-`pointIn` op is exactly here refused), kind-correct
|
|
579
|
+
* `pointIn`/`covers`/`allen` sides, and negated-atom safety (the negated
|
|
580
|
+
* relation's class record is resolved through `Classes` by its name). The
|
|
581
|
+
* leading `[AnyTreeChild] extends [C]` arm is the recursion's base case:
|
|
582
|
+
* at an UNRESOLVED constraint (the whole condition union — or a tree's
|
|
583
|
+
* child union, which is the union itself) the judgment is vacuously true —
|
|
584
|
+
* without it the constraint instantiation recurses into itself.
|
|
585
|
+
*/
|
|
586
|
+
type CondOkBool<Env extends EnvShape, Classes extends SchemaClasses, C> = [AnyTreeChild] extends [C]
|
|
587
|
+
? true
|
|
588
|
+
: C extends Cmp<infer Op, infer L, infer R, unknown>
|
|
589
|
+
? Op extends "eq" | "ne"
|
|
590
|
+
? EqOk<Env, L, R>
|
|
591
|
+
: Op extends "lt" | "le" | "gt" | "ge"
|
|
592
|
+
? [OrderSideOk<Env, L>, OrderSideOk<Env, R>] extends [true, true]
|
|
593
|
+
? true
|
|
594
|
+
: false
|
|
595
|
+
: Op extends "pointIn"
|
|
596
|
+
? [IntervalSideOk<Env, L>, PointSideOk<Env, R>] extends [true, true]
|
|
597
|
+
? true
|
|
598
|
+
: false
|
|
599
|
+
: Op extends "allen"
|
|
600
|
+
? [IntervalSideOk<Env, L>, IntervalSideOk<Env, R>] extends [true, true]
|
|
601
|
+
? true
|
|
602
|
+
: false
|
|
603
|
+
: false
|
|
604
|
+
: C extends Tree<infer Ch extends readonly AnyTreeChild[]>
|
|
605
|
+
? false extends CondOkBool<Env, Classes, Ch[number]>
|
|
606
|
+
? false
|
|
607
|
+
: true
|
|
608
|
+
: C extends NotAtom<infer R extends MatchOwner, infer B>
|
|
609
|
+
? NotOk<Env, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>
|
|
610
|
+
: false
|
|
611
|
+
|
|
612
|
+
/** The validated `.where` argument (intersect with the inferred condition type). */
|
|
613
|
+
type CheckCond<Env extends EnvShape, Classes extends SchemaClasses, C> =
|
|
614
|
+
CondOkBool<Env, Classes, C> extends true ? C : never
|
|
615
|
+
|
|
616
|
+
/** The `eq`/`ne` params contribution: the param typed by the left variable's field. */
|
|
617
|
+
type EqParams<Env extends EnvShape, L, R> =
|
|
618
|
+
L extends Var<infer N extends string>
|
|
619
|
+
? R extends Param<infer P extends string>
|
|
620
|
+
? { readonly [Q in P]: Infer<Env[N & keyof Env]["field"]> }
|
|
621
|
+
: R extends SetParam<infer P extends string>
|
|
622
|
+
? { readonly [Q in P]: readonly Infer<Env[N & keyof Env]["field"]>[] }
|
|
623
|
+
: never
|
|
624
|
+
: never
|
|
625
|
+
|
|
626
|
+
/** An order side's params contribution (order params are always `bigint`). */
|
|
627
|
+
type OrderSideParams<T> = T extends Param<infer P extends string> ? { readonly [Q in P]: bigint } : never
|
|
628
|
+
|
|
629
|
+
/** An interval side's params contribution. */
|
|
630
|
+
type IntervalSideParams<T> = T extends Param<infer P extends string> ? { readonly [Q in P]: IntervalValue } : never
|
|
631
|
+
|
|
632
|
+
/** The mask position's params contribution. */
|
|
633
|
+
type MaskParams<M> = M extends MaskParam<infer P extends string> ? { readonly [Q in P]: number } : never
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* One condition's params-object fragments (a union; the rule builder folds
|
|
637
|
+
* them into the inferred `Params` record) — every param typed by its use.
|
|
638
|
+
* The leading arm is the same base case as {@link CondOkBool}'s: the
|
|
639
|
+
* unresolved constraint contributes nothing.
|
|
640
|
+
*/
|
|
641
|
+
type CondParams<Env extends EnvShape, C> = [AnyTreeChild] extends [C]
|
|
642
|
+
? never
|
|
643
|
+
: C extends Cmp<infer Op, infer L, infer R, infer M>
|
|
644
|
+
? Op extends "eq" | "ne"
|
|
645
|
+
? EqParams<Env, L, R>
|
|
646
|
+
: Op extends "lt" | "le" | "gt" | "ge"
|
|
647
|
+
? OrderSideParams<L> | OrderSideParams<R>
|
|
648
|
+
: Op extends "pointIn"
|
|
649
|
+
? IntervalSideParams<L> | OrderSideParams<R>
|
|
650
|
+
: Op extends "allen"
|
|
651
|
+
? IntervalSideParams<L> | IntervalSideParams<R> | MaskParams<M>
|
|
652
|
+
: never
|
|
653
|
+
: C extends Tree<infer Ch extends readonly AnyTreeChild[]>
|
|
654
|
+
? CondParams<Env, Ch[number]>
|
|
655
|
+
: C extends NotAtom<infer R extends MatchOwner, infer B>
|
|
656
|
+
? BindParams<MatchFields<R>, B>
|
|
657
|
+
: never
|
|
658
|
+
|
|
659
|
+
/** The flattened params record one bindings record contributes. */
|
|
660
|
+
type BindParamsShape<F extends FieldsShape, B> = ShapeOf<BindParams<F, B>>
|
|
661
|
+
|
|
662
|
+
/** The flattened params record one condition contributes. */
|
|
663
|
+
type CondParamsShape<Env extends EnvShape, C> = ShapeOf<CondParams<Env, C>>
|
|
664
|
+
|
|
535
665
|
export type {
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
666
|
+
AggData,
|
|
667
|
+
AnyCmp,
|
|
668
|
+
AnyCond,
|
|
669
|
+
AnyNotAtom,
|
|
670
|
+
AnyTreeChild,
|
|
671
|
+
AtomData,
|
|
672
|
+
BindEnv,
|
|
539
673
|
BindingEntry,
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
674
|
+
BindingInput,
|
|
675
|
+
BindingTermData,
|
|
676
|
+
BindParams,
|
|
677
|
+
BindParamsShape,
|
|
678
|
+
CheckBindings,
|
|
679
|
+
CheckCond,
|
|
680
|
+
Cmp,
|
|
681
|
+
CmpData,
|
|
682
|
+
CmpKind,
|
|
683
|
+
CmpTermData,
|
|
684
|
+
CondData,
|
|
685
|
+
CondOkBool,
|
|
686
|
+
CondParams,
|
|
687
|
+
CondParamsShape,
|
|
688
|
+
IntervalSide,
|
|
689
|
+
IntervalVarOk,
|
|
548
690
|
MaskData,
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
691
|
+
MatchFields,
|
|
692
|
+
MatchOwner,
|
|
693
|
+
MatchShape,
|
|
694
|
+
NotAtom,
|
|
695
|
+
OrderSide,
|
|
696
|
+
OrderVarOk,
|
|
697
|
+
ParamUse,
|
|
698
|
+
PointSide,
|
|
699
|
+
RecData,
|
|
700
|
+
RuleData,
|
|
701
|
+
RuleItem,
|
|
702
|
+
SelectColumn,
|
|
703
|
+
SelectEntryData,
|
|
704
|
+
Tree,
|
|
705
|
+
TreeData
|
|
555
706
|
}
|
|
556
|
-
export { ALLEN, allen, and, covers,
|
|
707
|
+
export { ALLEN, allen, and, comparison, covers, eq, ge, gt, le, lt, ne, not, or, pointIn }
|