@bjornpagen/bumbledb 0.2.0 → 0.4.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 +503 -427
- package/README.md +84 -36
- package/dist/closed.d.ts +111 -38
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +94 -99
- package/dist/closed.js.map +1 -1
- package/dist/db.d.ts +16 -2
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +46 -8
- package/dist/db.js.map +1 -1
- package/dist/face.d.ts +114 -69
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +38 -21
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +72 -87
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +35 -67
- package/dist/fields.js.map +1 -1
- package/dist/index.d.ts +18 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -7
- 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 -10
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +34 -23
- package/dist/lower.js.map +1 -1
- package/dist/marshal.d.ts +33 -6
- package/dist/marshal.d.ts.map +1 -1
- package/dist/marshal.js +67 -6
- 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.map +1 -1
- package/dist/query/atom.d.ts +139 -56
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +7 -1
- package/dist/query/atom.js.map +1 -1
- package/dist/query/lower.d.ts +71 -56
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +341 -69
- package/dist/query/lower.js.map +1 -1
- package/dist/query/predicate.d.ts +10 -9
- package/dist/query/predicate.d.ts.map +1 -1
- package/dist/query/predicate.js +2 -2
- package/dist/query/predicate.js.map +1 -1
- package/dist/query/run.d.ts +15 -5
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +26 -6
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +100 -43
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +89 -30
- package/dist/query/scope.js.map +1 -1
- package/dist/query/select.d.ts +5 -5
- package/dist/query/select.d.ts.map +1 -1
- package/dist/relation.d.ts +29 -15
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +45 -17
- 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 +16 -2
- package/dist/schema.js.map +1 -1
- package/dist/spec.d.ts +10 -8
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js.map +1 -1
- package/dist/statements.d.ts +67 -31
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +97 -20
- package/dist/statements.js.map +1 -1
- package/package.json +2 -2
- package/src/closed.ts +214 -146
- package/src/db.ts +65 -10
- package/src/face.ts +169 -102
- package/src/fields.ts +97 -164
- package/src/index.ts +43 -18
- package/src/law.ts +519 -0
- package/src/lower.ts +36 -23
- package/src/marshal.ts +74 -7
- package/src/native.ts +6 -2
- package/src/query/atom.ts +155 -65
- package/src/query/lower.ts +572 -167
- package/src/query/predicate.ts +43 -33
- package/src/query/run.ts +26 -6
- package/src/query/scope.ts +161 -51
- package/src/query/select.ts +5 -5
- package/src/relation.ts +60 -26
- package/src/schema.ts +48 -7
- package/src/spec.ts +10 -8
- package/src/statements.ts +165 -46
package/dist/query/lower.js
CHANGED
|
@@ -27,11 +27,12 @@
|
|
|
27
27
|
import * as errors from "@superbuilders/errors";
|
|
28
28
|
import { assertDeclarationOrderKey } from "#fields.ts";
|
|
29
29
|
import { allen, and, covers, eq, ge, gt, le, lt, ne, not, or, pointIn } from "#query/atom.ts";
|
|
30
|
-
import { fieldJoins, inferred, isTerm, makeDuration, makeMaskParam, makeParam, makeSetParam, makeVar, renderFieldKind, term } from "#query/scope.ts";
|
|
30
|
+
import { fieldJoins, inferred, isTerm, makeDuration, makeMaskParam, makeParam, makeSetParam, makeVar, makeVars, renderFieldKind, term } from "#query/scope.ts";
|
|
31
31
|
import { argMax, argMin, count, countDistinct, max, min, pack, sum } from "#query/select.ts";
|
|
32
32
|
/** The frozen constructor vocabulary every rule builder spreads. */
|
|
33
33
|
const termOps = Object.freeze({
|
|
34
34
|
var: makeVar,
|
|
35
|
+
vars: makeVars,
|
|
35
36
|
param: makeParam,
|
|
36
37
|
inSet: makeSetParam,
|
|
37
38
|
maskParam: makeMaskParam,
|
|
@@ -64,41 +65,113 @@ const EMPTY_RULE = Object.freeze({
|
|
|
64
65
|
paramUses: Object.freeze([])
|
|
65
66
|
});
|
|
66
67
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
68
|
+
* The ordered matchable fields of an atom owner — the runtime twin of the
|
|
69
|
+
* type tier's `MatchFields`: a relation's declared fields; a closed
|
|
70
|
+
* relation's SEALED shape, the synthetic `id` (the value's own
|
|
71
|
+
* roster-carrying descriptor, by identity) at ordinal 0 and the declared
|
|
72
|
+
* payload columns at declared index + 1 (the sealed shift, mirroring
|
|
73
|
+
* `spec.rs`'s resolver — a `ClosedColumn` is structurally a
|
|
74
|
+
* {@link RelationField}). The lowering golden pins this mapping.
|
|
71
75
|
*/
|
|
72
|
-
function
|
|
76
|
+
function matchFieldsOf(owner) {
|
|
77
|
+
if ("axioms" in owner) {
|
|
78
|
+
return [Object.freeze({ name: "id", field: owner.id }), ...owner.data.columns];
|
|
79
|
+
}
|
|
80
|
+
return owner.data.fields;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Judges one membership ARRAY at a binding position — legal exactly at a
|
|
84
|
+
* CLOSED-reference field (the owner ruling: ordinary u64/str membership is
|
|
85
|
+
* spelled through `r.inSet` params; literal arrays are the closed
|
|
86
|
+
* vocabulary's spelling), holding ≥ 2 DISTINCT handle names (the
|
|
87
|
+
* degenerate sets are refusals: empty selects nothing, one element is the
|
|
88
|
+
* bare literal respelled, and a duplicate member is the same respelling in
|
|
89
|
+
* disguise — write each member once). The returned name is
|
|
90
|
+
* CONTENT-ADDRESSED (vocabulary + the member SET — the key sorts a copy,
|
|
91
|
+
* so two spellings of one set, reordered or not, share one dense
|
|
92
|
+
* `ParamId`); the members are shape-checked strings here and
|
|
93
|
+
* roster-verified at the one verification point (`taggedHandleId`) when
|
|
94
|
+
* the SDK supplies the set at execute — the same moment a bound `r.inSet`
|
|
95
|
+
* param's members are judged.
|
|
96
|
+
*/
|
|
97
|
+
function membershipSet(context, field, value) {
|
|
98
|
+
if (!("closed" in field)) {
|
|
99
|
+
throw errors.new(`${context}: a membership array is the closed-reference spelling — ordinary field membership is a bound ∈-set param (r.inSet)`);
|
|
100
|
+
}
|
|
101
|
+
if (value.length === 0) {
|
|
102
|
+
throw errors.new(`${context}: an empty membership array selects nothing — write the query you mean`);
|
|
103
|
+
}
|
|
104
|
+
if (value.length === 1) {
|
|
105
|
+
throw errors.new(`${context}: a one-element membership array is the bare literal respelled — write the literal (the canonical-utterance law: one meaning, one spelling)`);
|
|
106
|
+
}
|
|
107
|
+
const seen = new Set();
|
|
108
|
+
const members = value.map(function memberName(member) {
|
|
109
|
+
if (typeof member !== "string") {
|
|
110
|
+
throw literalShapeError(context, `a ${field.closed.name} handle name (string)`, member);
|
|
111
|
+
}
|
|
112
|
+
if (seen.has(member)) {
|
|
113
|
+
throw errors.new(`${context}: the membership array spells ${member} twice — write it once (the canonical-utterance law: one meaning, one spelling)`);
|
|
114
|
+
}
|
|
115
|
+
seen.add(member);
|
|
116
|
+
return member;
|
|
117
|
+
});
|
|
118
|
+
const key = [...members].sort();
|
|
119
|
+
return { name: `∈ ${field.closed.name} ${JSON.stringify(key)}`, members: Object.freeze(members) };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Resolves a bindings record against an atom owner's matchable fields (a
|
|
123
|
+
* relation's declared fields; a closed relation's sealed id + columns), in
|
|
124
|
+
* the record's written order: terms classify by their runtime tag,
|
|
125
|
+
* everything else is a bare literal (typed by the FIELD at lowering — the
|
|
126
|
+
* membership typing rule included). Every bound field carries its
|
|
127
|
+
* law-computed class, read off the schema value's frozen class map — the
|
|
128
|
+
* runtime twin of the type tier's `SlotAt` lookups.
|
|
129
|
+
*/
|
|
130
|
+
function resolveBindings(context, relation, bindings, classes) {
|
|
73
131
|
const entries = [];
|
|
74
132
|
const vars = [];
|
|
75
133
|
const uses = [];
|
|
134
|
+
const relationClasses = classes[relation.name];
|
|
135
|
+
const ordered = matchFieldsOf(relation);
|
|
76
136
|
for (const [fieldName, value] of Object.entries(bindings)) {
|
|
77
137
|
if (value === undefined) {
|
|
78
138
|
continue;
|
|
79
139
|
}
|
|
80
|
-
const declared =
|
|
140
|
+
const declared = ordered.find(function byName(candidate) {
|
|
81
141
|
return candidate.name === fieldName;
|
|
82
142
|
});
|
|
83
143
|
if (declared === undefined) {
|
|
84
144
|
throw errors.new(`${context} has no field ${fieldName}`);
|
|
85
145
|
}
|
|
146
|
+
const fieldClass = relationClasses?.[fieldName];
|
|
86
147
|
let bound;
|
|
87
148
|
if (isTerm(value)) {
|
|
88
149
|
switch (value[term]) {
|
|
89
150
|
case "var": {
|
|
90
151
|
bound = Object.freeze({ kind: "var", name: value.name });
|
|
91
|
-
vars.push(Object.freeze({ name: value.name, field: declared.field }));
|
|
152
|
+
vars.push(Object.freeze({ name: value.name, slot: Object.freeze({ field: declared.field, class: fieldClass }) }));
|
|
92
153
|
break;
|
|
93
154
|
}
|
|
94
155
|
case "param": {
|
|
95
156
|
bound = Object.freeze({ kind: "param", name: value.name });
|
|
96
|
-
uses.push(Object.freeze({
|
|
157
|
+
uses.push(Object.freeze({
|
|
158
|
+
name: value.name,
|
|
159
|
+
shape: "value",
|
|
160
|
+
anchor: declared.field,
|
|
161
|
+
op: "binding",
|
|
162
|
+
members: undefined
|
|
163
|
+
}));
|
|
97
164
|
break;
|
|
98
165
|
}
|
|
99
166
|
case "setParam": {
|
|
100
167
|
bound = Object.freeze({ kind: "setParam", name: value.name });
|
|
101
|
-
uses.push(Object.freeze({
|
|
168
|
+
uses.push(Object.freeze({
|
|
169
|
+
name: value.name,
|
|
170
|
+
shape: "set",
|
|
171
|
+
anchor: declared.field,
|
|
172
|
+
op: "binding",
|
|
173
|
+
members: undefined
|
|
174
|
+
}));
|
|
102
175
|
break;
|
|
103
176
|
}
|
|
104
177
|
case "maskParam":
|
|
@@ -107,10 +180,21 @@ function resolveBindings(context, relation, bindings) {
|
|
|
107
180
|
throw errors.new(`${context}.${fieldName}: the measure is not a field-typed value — it lives in comparisons and select entries`);
|
|
108
181
|
}
|
|
109
182
|
}
|
|
183
|
+
else if (Array.isArray(value)) {
|
|
184
|
+
const set = membershipSet(`${context}.${fieldName}`, declared.field, value);
|
|
185
|
+
bound = Object.freeze({ kind: "literalSet", name: set.name, members: set.members });
|
|
186
|
+
uses.push(Object.freeze({
|
|
187
|
+
name: set.name,
|
|
188
|
+
shape: "set",
|
|
189
|
+
anchor: declared.field,
|
|
190
|
+
op: "binding",
|
|
191
|
+
members: set.members
|
|
192
|
+
}));
|
|
193
|
+
}
|
|
110
194
|
else {
|
|
111
195
|
bound = Object.freeze({ kind: "literal", value });
|
|
112
196
|
}
|
|
113
|
-
entries.push(Object.freeze({ field: fieldName, data: declared.field, term: bound }));
|
|
197
|
+
entries.push(Object.freeze({ field: fieldName, data: declared.field, class: fieldClass, term: bound }));
|
|
114
198
|
}
|
|
115
199
|
return {
|
|
116
200
|
atom: Object.freeze({ relation, bindings: Object.freeze(entries) }),
|
|
@@ -121,20 +205,20 @@ function resolveBindings(context, relation, bindings) {
|
|
|
121
205
|
/**
|
|
122
206
|
* Extends a rule state with one positive atom. Vars bind on first
|
|
123
207
|
* occurrence; every LATER occurrence (a later atom's field or a same-record
|
|
124
|
-
* sibling) is a join and must be
|
|
125
|
-
* of the type tier's `JoinOk
|
|
126
|
-
* callers too.
|
|
208
|
+
* sibling) is a join and must be class-equal — the construction-time twin
|
|
209
|
+
* of the type tier's `JoinOk` (bare pairs only with bare), so the domain
|
|
210
|
+
* wall holds for untyped callers too.
|
|
127
211
|
*/
|
|
128
|
-
function advanceMatch(state, relation, bindings) {
|
|
129
|
-
const resolved = resolveBindings(`relation ${relation.name}`, relation, bindings);
|
|
212
|
+
function advanceMatch(state, relation, bindings, classes) {
|
|
213
|
+
const resolved = resolveBindings(`relation ${relation.name}`, relation, bindings, classes);
|
|
130
214
|
const varFields = { ...state.varFields };
|
|
131
215
|
for (const bound of resolved.vars) {
|
|
132
216
|
const existing = varFields[bound.name];
|
|
133
217
|
if (existing === undefined) {
|
|
134
|
-
varFields[bound.name] = bound.
|
|
218
|
+
varFields[bound.name] = bound.slot;
|
|
135
219
|
}
|
|
136
|
-
else if (!fieldJoins(existing, bound.
|
|
137
|
-
throw errors.new(`relation ${relation.name}: the variable ${bound.name} joins domain-unequal fields — first bound at ${renderFieldKind(existing)}, reused at ${renderFieldKind(bound.
|
|
220
|
+
else if (!fieldJoins(existing, bound.slot)) {
|
|
221
|
+
throw errors.new(`relation ${relation.name}: the variable ${bound.name} joins domain-unequal fields — first bound at ${renderFieldKind(existing)}, reused at ${renderFieldKind(bound.slot)} (a var joins only class-equal slots; bare pairs only with bare)`);
|
|
138
222
|
}
|
|
139
223
|
}
|
|
140
224
|
return {
|
|
@@ -173,7 +257,7 @@ function sideUses(op, side, sibling, varFields, uses) {
|
|
|
173
257
|
}
|
|
174
258
|
let anchor;
|
|
175
259
|
if (sibling.kind === "var") {
|
|
176
|
-
anchor = varFields[sibling.name];
|
|
260
|
+
anchor = varFields[sibling.name]?.field;
|
|
177
261
|
}
|
|
178
262
|
else if (sibling.kind === "measure") {
|
|
179
263
|
anchor = "measure";
|
|
@@ -185,7 +269,8 @@ function sideUses(op, side, sibling, varFields, uses) {
|
|
|
185
269
|
name: side.name,
|
|
186
270
|
shape: side.kind === "param" ? "value" : "set",
|
|
187
271
|
anchor,
|
|
188
|
-
op
|
|
272
|
+
op,
|
|
273
|
+
members: undefined
|
|
189
274
|
}));
|
|
190
275
|
}
|
|
191
276
|
/** Lowers one condition VALUE to its runtime data, recording param uses. */
|
|
@@ -203,7 +288,13 @@ function condDataOf(cond, varFields, uses) {
|
|
|
203
288
|
}
|
|
204
289
|
else if (isTerm(maskValue) && maskValue[term] === "maskParam") {
|
|
205
290
|
mask = Object.freeze({ kind: "param", name: maskValue.name });
|
|
206
|
-
uses.push(Object.freeze({
|
|
291
|
+
uses.push(Object.freeze({
|
|
292
|
+
name: maskValue.name,
|
|
293
|
+
shape: "mask",
|
|
294
|
+
anchor: undefined,
|
|
295
|
+
op: "allen",
|
|
296
|
+
members: undefined
|
|
297
|
+
}));
|
|
207
298
|
}
|
|
208
299
|
else {
|
|
209
300
|
throw errors.new("allen: the mask position takes a 13-bit mask number or a maskParam");
|
|
@@ -222,7 +313,7 @@ function condDataOf(cond, varFields, uses) {
|
|
|
222
313
|
throw errors.new("a negated atom is not a condition-tree node — pass not(...) to where() directly, never inside and()/or()");
|
|
223
314
|
}
|
|
224
315
|
/** Extends a rule state with one `.where` item (a condition or a negated atom). */
|
|
225
|
-
function advanceWhere(state, cond) {
|
|
316
|
+
function advanceWhere(state, cond, classes) {
|
|
226
317
|
if (typeof cond !== "object" || cond === null || !("cond" in cond)) {
|
|
227
318
|
throw errors.new("where() takes a comparison, an and()/or() tree, or a negated atom");
|
|
228
319
|
}
|
|
@@ -231,7 +322,7 @@ function advanceWhere(state, cond) {
|
|
|
231
322
|
const bindings = Object.fromEntries(Object.entries(cond.bindings ?? {}).filter(function defined([, value]) {
|
|
232
323
|
return value !== undefined;
|
|
233
324
|
}));
|
|
234
|
-
const resolved = resolveBindings(`negated relation ${relation.name}`, relation, bindings);
|
|
325
|
+
const resolved = resolveBindings(`negated relation ${relation.name}`, relation, bindings, classes);
|
|
235
326
|
return {
|
|
236
327
|
items: Object.freeze([...state.items, Object.freeze({ kind: "negated", atom: resolved.atom })]),
|
|
237
328
|
varFields: state.varFields,
|
|
@@ -264,14 +355,27 @@ function advanceIdb(state, rec, vars) {
|
|
|
264
355
|
function isAggregateEntry(value) {
|
|
265
356
|
return typeof value === "object" && value !== null && "agg" in value;
|
|
266
357
|
}
|
|
267
|
-
/**
|
|
358
|
+
/**
|
|
359
|
+
* Classifies one select entry into its named answer column. The `closed`
|
|
360
|
+
* slice is resolved LATER, at rule completion (`completeRule`), where the
|
|
361
|
+
* rule's `varFields` are in hand — until then every column is provisionally
|
|
362
|
+
* bare.
|
|
363
|
+
*/
|
|
268
364
|
function selectColumnOf(entry) {
|
|
269
365
|
if (typeof entry === "string") {
|
|
270
|
-
return Object.freeze({
|
|
366
|
+
return Object.freeze({
|
|
367
|
+
name: entry,
|
|
368
|
+
entry: Object.freeze({ kind: "var", over: entry }),
|
|
369
|
+
closed: undefined
|
|
370
|
+
});
|
|
271
371
|
}
|
|
272
372
|
if (isTerm(entry)) {
|
|
273
373
|
if (entry[term] === "duration") {
|
|
274
|
-
return Object.freeze({
|
|
374
|
+
return Object.freeze({
|
|
375
|
+
name: entry.name,
|
|
376
|
+
entry: Object.freeze({ kind: "measure", over: entry.name }),
|
|
377
|
+
closed: undefined
|
|
378
|
+
});
|
|
275
379
|
}
|
|
276
380
|
throw errors.new(`query select: a ${entry[term]} is not projectable — select takes variable names, duration(v), or aggregates`);
|
|
277
381
|
}
|
|
@@ -283,7 +387,11 @@ function selectColumnOf(entry) {
|
|
|
283
387
|
/** Classifies one aggregate select entry. */
|
|
284
388
|
function aggregateColumnOf(entry) {
|
|
285
389
|
function column(name, agg) {
|
|
286
|
-
return Object.freeze({
|
|
390
|
+
return Object.freeze({
|
|
391
|
+
name,
|
|
392
|
+
entry: Object.freeze({ kind: "aggregate", agg: Object.freeze(agg) }),
|
|
393
|
+
closed: undefined
|
|
394
|
+
});
|
|
287
395
|
}
|
|
288
396
|
const over = entry.over;
|
|
289
397
|
switch (entry.agg) {
|
|
@@ -323,32 +431,66 @@ function aggregateColumnOf(entry) {
|
|
|
323
431
|
throw errors.new(`unknown aggregate ${entry.agg}`);
|
|
324
432
|
}
|
|
325
433
|
}
|
|
434
|
+
/**
|
|
435
|
+
* The orderable ban's pointed refusal (`docs/architecture/10-data-model.md`
|
|
436
|
+
* § orderability): a closed reference is equality-and-membership only —
|
|
437
|
+
* its declaration-id order is an encoding accident, so every
|
|
438
|
+
* order-comparison and fold position refuses it. The construction-time
|
|
439
|
+
* twin of the type tier's `OrderVarOk` exclusion, so the wall holds for
|
|
440
|
+
* untyped callers too (the engine cannot backstop this one: the wire IR
|
|
441
|
+
* carries plain u64s, no rosters).
|
|
442
|
+
*/
|
|
443
|
+
function closedOrderError(context, position, vocabulary) {
|
|
444
|
+
return errors.new(`${context}: ${position} is a ${vocabulary} reference — declaration order is an accident, not semantics: vocabularies do not order (docs/architecture/10-data-model.md; equality, membership, and counting remain)`);
|
|
445
|
+
}
|
|
446
|
+
/** The comparison ops the orderable ban covers (order roster + point membership — every order-comparison position). */
|
|
447
|
+
function isOrderOp(op) {
|
|
448
|
+
return op === "lt" || op === "le" || op === "gt" || op === "ge" || op === "pointIn";
|
|
449
|
+
}
|
|
326
450
|
/** Requires a var name to be bound by a relation atom of the rule. */
|
|
327
451
|
function assertBound(context, varFields, name) {
|
|
328
|
-
const
|
|
329
|
-
if (
|
|
452
|
+
const slot = varFields[name];
|
|
453
|
+
if (slot === undefined) {
|
|
330
454
|
throw errors.new(`${context}: the variable ${name} is not bound by a relation atom of the rule`);
|
|
331
455
|
}
|
|
332
|
-
return
|
|
456
|
+
return slot;
|
|
333
457
|
}
|
|
334
458
|
/** Requires a var name to be bound at an interval field (the measure's and pack's domain). */
|
|
335
459
|
function assertIntervalBound(context, varFields, name) {
|
|
336
|
-
const
|
|
337
|
-
if (field.kind !== "interval") {
|
|
460
|
+
const slot = assertBound(context, varFields, name);
|
|
461
|
+
if (slot.field.kind !== "interval") {
|
|
338
462
|
throw errors.new(`${context}: ${name} is not interval-typed — the measure is defined over interval-typed variables only`);
|
|
339
463
|
}
|
|
340
464
|
}
|
|
341
|
-
/**
|
|
465
|
+
/**
|
|
466
|
+
* Validates one condition's variable references against the rule's bound
|
|
467
|
+
* names — and, for `eq`/`ne` over two variables, holds the class wall: the
|
|
468
|
+
* unification IS a join, so the two slots must be class-equal exactly as a
|
|
469
|
+
* match-reuse join must be (the construction-time twin of the type tier's
|
|
470
|
+
* `EqOk` → `JoinOk`; bare pairs only with bare). The engine cannot backstop
|
|
471
|
+
* this one — the query IR carries no domains — so the wall lives here for
|
|
472
|
+
* untyped callers too.
|
|
473
|
+
*/
|
|
342
474
|
function validateCond(context, varFields, cond) {
|
|
343
475
|
if (cond.kind === "cmp") {
|
|
344
476
|
for (const side of [cond.lhs, cond.rhs]) {
|
|
345
477
|
if (side.kind === "var") {
|
|
346
|
-
assertBound(context, varFields, side.name);
|
|
478
|
+
const slot = assertBound(context, varFields, side.name);
|
|
479
|
+
if (isOrderOp(cond.op) && "closed" in slot.field) {
|
|
480
|
+
throw closedOrderError(context, `the ${cond.op} side ${side.name}`, slot.field.closed.name);
|
|
481
|
+
}
|
|
347
482
|
}
|
|
348
483
|
if (side.kind === "measure") {
|
|
349
484
|
assertIntervalBound(context, varFields, side.name);
|
|
350
485
|
}
|
|
351
486
|
}
|
|
487
|
+
if ((cond.op === "eq" || cond.op === "ne") && cond.lhs.kind === "var" && cond.rhs.kind === "var") {
|
|
488
|
+
const lhs = assertBound(context, varFields, cond.lhs.name);
|
|
489
|
+
const rhs = assertBound(context, varFields, cond.rhs.name);
|
|
490
|
+
if (!fieldJoins(lhs, rhs)) {
|
|
491
|
+
throw errors.new(`${context}: ${cond.op}(${cond.lhs.name}, ${cond.rhs.name}) unifies domain-unequal fields — ${cond.lhs.name} bound at ${renderFieldKind(lhs)}, ${cond.rhs.name} at ${renderFieldKind(rhs)} (a var joins only class-equal slots; bare pairs only with bare)`);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
352
494
|
return;
|
|
353
495
|
}
|
|
354
496
|
for (const child of cond.children) {
|
|
@@ -375,21 +517,58 @@ function validateColumn(context, varFields, column) {
|
|
|
375
517
|
return;
|
|
376
518
|
case "fold": {
|
|
377
519
|
if (typeof agg.over === "string") {
|
|
378
|
-
assertBound(`${context} select ${column.name}`, varFields, agg.over);
|
|
520
|
+
const slot = assertBound(`${context} select ${column.name}`, varFields, agg.over);
|
|
521
|
+
if ("closed" in slot.field) {
|
|
522
|
+
throw closedOrderError(`${context} select ${column.name}`, `the ${agg.fold} input ${agg.over}`, slot.field.closed.name);
|
|
523
|
+
}
|
|
379
524
|
return;
|
|
380
525
|
}
|
|
381
526
|
assertIntervalBound(`${context} select ${column.name}`, varFields, agg.over.duration);
|
|
382
527
|
return;
|
|
383
528
|
}
|
|
384
|
-
case "arg":
|
|
529
|
+
case "arg": {
|
|
385
530
|
assertBound(`${context} select ${column.name}`, varFields, agg.over);
|
|
386
|
-
assertBound(`${context} select ${column.name}`, varFields, agg.key);
|
|
531
|
+
const key = assertBound(`${context} select ${column.name}`, varFields, agg.key);
|
|
532
|
+
if ("closed" in key.field) {
|
|
533
|
+
throw closedOrderError(`${context} select ${column.name}`, `the ${agg.direction} key ${agg.key}`, key.field.closed.name);
|
|
534
|
+
}
|
|
387
535
|
return;
|
|
536
|
+
}
|
|
388
537
|
case "pack":
|
|
389
538
|
assertIntervalBound(`${context} select ${column.name}`, varFields, agg.over);
|
|
390
539
|
return;
|
|
391
540
|
}
|
|
392
541
|
}
|
|
542
|
+
/**
|
|
543
|
+
* Resolves the roster one select column decodes through: a projected var,
|
|
544
|
+
* or an Arg-carried payload, bound at a closed-referencing field carries
|
|
545
|
+
* that field's roster (read off `varFields` — the same slot the domain
|
|
546
|
+
* machinery reads), and `decodeAnswers` lifts the column's row ids back to
|
|
547
|
+
* handle NAMES through it — the runtime twin of the row type's `Infer`
|
|
548
|
+
* claim. Every other entry decodes bare: counts are counts, the measure
|
|
549
|
+
* and `pack` are never closed, and a closed FOLD is banned outright
|
|
550
|
+
* ({@link closedOrderError}) before this resolution runs.
|
|
551
|
+
*/
|
|
552
|
+
function selectClosedOf(varFields, entry) {
|
|
553
|
+
let over;
|
|
554
|
+
if (entry.kind === "var") {
|
|
555
|
+
over = entry.over;
|
|
556
|
+
}
|
|
557
|
+
else if (entry.kind === "aggregate" && entry.agg.op === "arg") {
|
|
558
|
+
over = entry.agg.over;
|
|
559
|
+
}
|
|
560
|
+
else {
|
|
561
|
+
over = undefined;
|
|
562
|
+
}
|
|
563
|
+
if (over === undefined) {
|
|
564
|
+
return undefined;
|
|
565
|
+
}
|
|
566
|
+
const field = varFields[over]?.field;
|
|
567
|
+
if (field !== undefined && "closed" in field) {
|
|
568
|
+
return field.closed;
|
|
569
|
+
}
|
|
570
|
+
return undefined;
|
|
571
|
+
}
|
|
393
572
|
/**
|
|
394
573
|
* Completes one rule: classifies the select record (written order = answer
|
|
395
574
|
* column order, names must be declaration-order-safe keys), and validates
|
|
@@ -418,8 +597,9 @@ function completeRule(context, state, columns) {
|
|
|
418
597
|
if (bound === undefined) {
|
|
419
598
|
throw errors.new(`${context}: negated ${item.atom.relation.name} atom binds the variable ${binding.term.name} at position ${binding.field}, but no positive atom of the rule binds it — a negated atom binds nothing, only rejects (the safety rule)`);
|
|
420
599
|
}
|
|
421
|
-
|
|
422
|
-
|
|
600
|
+
const negatedSlot = { field: binding.data, class: binding.class };
|
|
601
|
+
if (!fieldJoins(bound, negatedSlot)) {
|
|
602
|
+
throw errors.new(`${context}: negated ${item.atom.relation.name} atom reuses the variable ${binding.term.name} at ${binding.field} (${renderFieldKind(negatedSlot)}), but the rule binds it at ${renderFieldKind(bound)} — a var joins only class-equal slots; bare pairs only with bare`);
|
|
423
603
|
}
|
|
424
604
|
}
|
|
425
605
|
}
|
|
@@ -435,9 +615,9 @@ function completeRule(context, state, columns) {
|
|
|
435
615
|
if (column === undefined || column.entry.kind !== "var") {
|
|
436
616
|
return;
|
|
437
617
|
}
|
|
438
|
-
const
|
|
439
|
-
if (
|
|
440
|
-
throw errors.new(`${context}: idb ${item.rec.name} joins the variable ${name} (${renderFieldKind(bound)}) at head position ${position} (${column.name}: ${renderFieldKind(
|
|
618
|
+
const headSlot = head?.varFields[column.entry.over];
|
|
619
|
+
if (headSlot !== undefined && !fieldJoins(headSlot, bound)) {
|
|
620
|
+
throw errors.new(`${context}: idb ${item.rec.name} joins the variable ${name} (${renderFieldKind(bound)}) at head position ${position} (${column.name}: ${renderFieldKind(headSlot)}) — a var joins only class-equal slots; bare pairs only with bare`);
|
|
441
621
|
}
|
|
442
622
|
});
|
|
443
623
|
}
|
|
@@ -447,7 +627,13 @@ function completeRule(context, state, columns) {
|
|
|
447
627
|
}
|
|
448
628
|
return Object.freeze({
|
|
449
629
|
items: state.items,
|
|
450
|
-
select: Object.freeze(
|
|
630
|
+
select: Object.freeze(columns.map(function enrichColumn(column) {
|
|
631
|
+
return Object.freeze({
|
|
632
|
+
name: column.name,
|
|
633
|
+
entry: column.entry,
|
|
634
|
+
closed: selectClosedOf(state.varFields, column.entry)
|
|
635
|
+
});
|
|
636
|
+
})),
|
|
451
637
|
varFields: state.varFields,
|
|
452
638
|
paramUses: state.paramUses
|
|
453
639
|
});
|
|
@@ -496,10 +682,10 @@ function selectColumns(context, entries) {
|
|
|
496
682
|
function makeRawChain(context, state) {
|
|
497
683
|
const chain = {
|
|
498
684
|
match(relation, bindings) {
|
|
499
|
-
return makeRawChain(context, advanceMatch(state, relation, bindings));
|
|
685
|
+
return makeRawChain(context, advanceMatch(state, relation, bindings, context.classes));
|
|
500
686
|
},
|
|
501
687
|
where(cond) {
|
|
502
|
-
return makeRawChain(context, advanceWhere(state, cond));
|
|
688
|
+
return makeRawChain(context, advanceWhere(state, cond, context.classes));
|
|
503
689
|
},
|
|
504
690
|
idb(target, ...vars) {
|
|
505
691
|
return makeRawChain(context, idbAdvance(context, state, target, vars));
|
|
@@ -516,7 +702,7 @@ function makeRawScope(context) {
|
|
|
516
702
|
const scope = {
|
|
517
703
|
...termOps,
|
|
518
704
|
match(relation, bindings) {
|
|
519
|
-
return makeRawChain(context, advanceMatch(EMPTY_RULE, relation, bindings));
|
|
705
|
+
return makeRawChain(context, advanceMatch(EMPTY_RULE, relation, bindings, context.classes));
|
|
520
706
|
}
|
|
521
707
|
};
|
|
522
708
|
Object.freeze(scope);
|
|
@@ -535,8 +721,8 @@ function isTypedScope(scope) {
|
|
|
535
721
|
return typeof scope.match === "function";
|
|
536
722
|
}
|
|
537
723
|
/** Builds one query-rule builder (the typed face of the raw builder). */
|
|
538
|
-
function makeQueryRuleScope() {
|
|
539
|
-
const raw = makeRawScope({ kind: "query" });
|
|
724
|
+
function makeQueryRuleScope(classes) {
|
|
725
|
+
const raw = makeRawScope({ kind: "query", classes });
|
|
540
726
|
if (!isTypedScope(raw)) {
|
|
541
727
|
throw errors.new("query rule builder construction incomplete");
|
|
542
728
|
}
|
|
@@ -544,12 +730,16 @@ function makeQueryRuleScope() {
|
|
|
544
730
|
}
|
|
545
731
|
/** Builds one output-rule builder over a program's recs. */
|
|
546
732
|
function makeOutputRuleScope(program) {
|
|
547
|
-
const raw = makeRawScope({ kind: "output", program });
|
|
733
|
+
const raw = makeRawScope({ kind: "output", program, classes: program.classes });
|
|
548
734
|
if (!isTypedScope(raw)) {
|
|
549
735
|
throw errors.new("program output rule builder construction incomplete");
|
|
550
736
|
}
|
|
551
737
|
return raw;
|
|
552
738
|
}
|
|
739
|
+
/** Renders one head column's closed slice for the rule-alignment check's diagnostics. */
|
|
740
|
+
function renderClosedSlice(closed) {
|
|
741
|
+
return closed === undefined ? "a bare value" : `a ${closed.name} reference`;
|
|
742
|
+
}
|
|
553
743
|
/** Renders one head column's signature for the rule-alignment check. */
|
|
554
744
|
function headSignature(column) {
|
|
555
745
|
const entry = column.entry;
|
|
@@ -565,11 +755,36 @@ function headSignature(column) {
|
|
|
565
755
|
}
|
|
566
756
|
return `${column.name}:${agg.op}`;
|
|
567
757
|
}
|
|
758
|
+
/** The roster a param anchor carries: present exactly on a closed-reference field anchor. */
|
|
759
|
+
function anchorRosterOf(anchor) {
|
|
760
|
+
if (anchor === undefined || anchor === "measure") {
|
|
761
|
+
return undefined;
|
|
762
|
+
}
|
|
763
|
+
if ("closed" in anchor) {
|
|
764
|
+
return anchor.closed;
|
|
765
|
+
}
|
|
766
|
+
return undefined;
|
|
767
|
+
}
|
|
768
|
+
/** Renders one param anchor's closedness for the registry's coherence diagnostics. */
|
|
769
|
+
function renderParamAnchor(roster) {
|
|
770
|
+
return roster === undefined ? "a non-closed position" : `a ${roster.name} reference`;
|
|
771
|
+
}
|
|
568
772
|
/**
|
|
569
773
|
* Folds every rule's param uses (recs in declaration order first, output
|
|
570
774
|
* rules last — exactly the lowering walk) into the query's registry:
|
|
571
775
|
* first use mints the dense `ParamId`, the first FIELD-ANCHORED use types
|
|
572
|
-
* the wire, and one name must keep one shape
|
|
776
|
+
* the wire, and one name must keep one shape AND one closedness — every
|
|
777
|
+
* anchored use of one name must agree on the roster (value identity), so a
|
|
778
|
+
* param anchored at a closed reference is GUARANTEED to ride the one
|
|
779
|
+
* roster-verification point (`taggedHandleId`) at execute; a name anchored
|
|
780
|
+
* both at a closed reference and at a non-closed position (or at two
|
|
781
|
+
* vocabularies) is refused here, because the wire would translate only the
|
|
782
|
+
* first anchor's reading (the type tier intersects the uses to `never`;
|
|
783
|
+
* this is its runtime twin for untyped callers). A param whose anchor is a
|
|
784
|
+
* CLOSED reference must never sit in an order-comparison position — the
|
|
785
|
+
* anchor types its value a handle name and the engine would order the
|
|
786
|
+
* translated row ids, so the pairing is refused here too (the registry is
|
|
787
|
+
* the one place a name's every use and its anchoring field meet).
|
|
573
788
|
*/
|
|
574
789
|
function paramRegistryOf(recs, rules) {
|
|
575
790
|
const order = [];
|
|
@@ -579,16 +794,35 @@ function paramRegistryOf(recs, rules) {
|
|
|
579
794
|
const existing = byName.get(use.name);
|
|
580
795
|
if (existing === undefined) {
|
|
581
796
|
order.push(use.name);
|
|
582
|
-
byName.set(use.name, {
|
|
797
|
+
byName.set(use.name, {
|
|
798
|
+
shape: use.shape,
|
|
799
|
+
anchor: use.anchor,
|
|
800
|
+
op: use.op,
|
|
801
|
+
members: use.members,
|
|
802
|
+
orderOp: isOrderOp(use.op) ? use.op : undefined
|
|
803
|
+
});
|
|
583
804
|
continue;
|
|
584
805
|
}
|
|
806
|
+
if ((existing.members === undefined) !== (use.members === undefined)) {
|
|
807
|
+
throw errors.new(`query param ${use.name} collides with a membership array's registry entry — name the param differently`);
|
|
808
|
+
}
|
|
585
809
|
if (existing.shape !== use.shape) {
|
|
586
810
|
throw errors.new(`query param ${use.name} is used both as a ${existing.shape} param and a ${use.shape} param — one name, one shape`);
|
|
587
811
|
}
|
|
812
|
+
if (existing.anchor !== undefined && use.anchor !== undefined) {
|
|
813
|
+
const registered = anchorRosterOf(existing.anchor);
|
|
814
|
+
const anchored = anchorRosterOf(use.anchor);
|
|
815
|
+
if (registered !== anchored) {
|
|
816
|
+
throw errors.new(`query param ${use.name} is anchored at ${renderParamAnchor(registered)} and at ${renderParamAnchor(anchored)} — a closed-anchored param translates handle names through ONE roster (one name, one domain); name the params differently`);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
588
819
|
if (existing.anchor === undefined && use.anchor !== undefined) {
|
|
589
820
|
existing.anchor = use.anchor;
|
|
590
821
|
existing.op = use.op;
|
|
591
822
|
}
|
|
823
|
+
if (existing.orderOp === undefined && isOrderOp(use.op)) {
|
|
824
|
+
existing.orderOp = use.op;
|
|
825
|
+
}
|
|
592
826
|
}
|
|
593
827
|
}
|
|
594
828
|
for (const rec of recs) {
|
|
@@ -604,7 +838,13 @@ function paramRegistryOf(recs, rules) {
|
|
|
604
838
|
if (entry === undefined) {
|
|
605
839
|
throw errors.new(`query param ${name} lost its registry entry`);
|
|
606
840
|
}
|
|
607
|
-
|
|
841
|
+
if (entry.orderOp !== undefined &&
|
|
842
|
+
entry.anchor !== undefined &&
|
|
843
|
+
entry.anchor !== "measure" &&
|
|
844
|
+
"closed" in entry.anchor) {
|
|
845
|
+
throw closedOrderError(`query param ${name}`, `its ${entry.orderOp} use's anchor`, entry.anchor.closed.name);
|
|
846
|
+
}
|
|
847
|
+
return Object.freeze({ name, shape: entry.shape, anchor: entry.anchor, op: entry.op, members: entry.members });
|
|
608
848
|
}));
|
|
609
849
|
}
|
|
610
850
|
/**
|
|
@@ -624,6 +864,17 @@ function makeRawQuery(theory, recs, rules) {
|
|
|
624
864
|
if (candidate !== signature) {
|
|
625
865
|
throw errors.new(`every rule of a query derives the same head — rule 0 selects (${signature}), rule ${index} selects (${candidate})`);
|
|
626
866
|
}
|
|
867
|
+
// The closed slice is part of the head too: one answer column decodes
|
|
868
|
+
// through one roster, so a union whose rules bind a column at
|
|
869
|
+
// different vocabularies (or one closed, one bare — the ids would
|
|
870
|
+
// mistranslate silently) is refused pointed. Vocabulary identity is
|
|
871
|
+
// value identity, the SDK's membership rule everywhere.
|
|
872
|
+
rule.select.forEach(function verifyClosedSlice(column, position) {
|
|
873
|
+
const lead = first.select[position];
|
|
874
|
+
if (lead !== undefined && column.closed !== lead.closed) {
|
|
875
|
+
throw errors.new(`every rule of a query derives the same head — the answer column ${lead.name} is ${renderClosedSlice(lead.closed)} in rule 0 but ${renderClosedSlice(column.closed)} in rule ${index} (one column decodes through one roster)`);
|
|
876
|
+
}
|
|
877
|
+
});
|
|
627
878
|
});
|
|
628
879
|
const data = Object.freeze({
|
|
629
880
|
recs: Object.freeze([...recs]),
|
|
@@ -635,7 +886,7 @@ function makeRawQuery(theory, recs, rules) {
|
|
|
635
886
|
schema: theory,
|
|
636
887
|
data,
|
|
637
888
|
rule(build) {
|
|
638
|
-
const built = build(makeRawScope({ kind: "query" }));
|
|
889
|
+
const built = build(makeRawScope({ kind: "query", classes: theory.classes }));
|
|
639
890
|
return makeRawQuery(theory, recs, [...rules, built.rule]);
|
|
640
891
|
}
|
|
641
892
|
};
|
|
@@ -661,12 +912,14 @@ function makeQuery(theory, recs, rules) {
|
|
|
661
912
|
/**
|
|
662
913
|
* Opens a query over a schema: `query(S).rule(r => ...)`. Each `.rule`
|
|
663
914
|
* adds one conjunctive rule; multiple rules are the set union (answers are
|
|
664
|
-
* SETS — no order or limit exists anywhere; hosts sort).
|
|
915
|
+
* SETS — no order or limit exists anywhere; hosts sort). The schema's
|
|
916
|
+
* law-computed class map rides into every rule builder — the join walls
|
|
917
|
+
* compare class names off it, at the type level and at construction alike.
|
|
665
918
|
*/
|
|
666
919
|
function query(theory) {
|
|
667
920
|
const start = {
|
|
668
921
|
rule(build) {
|
|
669
|
-
const built = build(makeQueryRuleScope());
|
|
922
|
+
const built = build(makeQueryRuleScope(theory.classes));
|
|
670
923
|
return makeQuery(theory, [], [built.rule]);
|
|
671
924
|
}
|
|
672
925
|
};
|
|
@@ -687,19 +940,24 @@ function isIntervalShaped(value) {
|
|
|
687
940
|
typeof value.end === "bigint");
|
|
688
941
|
}
|
|
689
942
|
/**
|
|
690
|
-
* Tags one closed-reference literal: the
|
|
691
|
-
*
|
|
692
|
-
* make any
|
|
693
|
-
* never handle
|
|
943
|
+
* Tags one closed-reference literal: the handle NAME, verified against the
|
|
944
|
+
* roster (the belt the wide fallback type cannot provide — structural
|
|
945
|
+
* values make any string spellable here) and translated to its
|
|
946
|
+
* declaration-order row id, tagged u64 — queries cross ids, never handle
|
|
947
|
+
* names; the wire is untouched. THE single roster-verification point of
|
|
948
|
+
* the query surface: atom-binding literals, comparison literals,
|
|
949
|
+
* execute-time params, and membership-array members all reach it (never
|
|
950
|
+
* duplicate the check per call site).
|
|
694
951
|
*/
|
|
695
952
|
function taggedHandleId(context, closed, value) {
|
|
696
|
-
if (typeof value !== "
|
|
697
|
-
throw literalShapeError(context, `a ${closed.name} handle
|
|
953
|
+
if (typeof value !== "string") {
|
|
954
|
+
throw literalShapeError(context, `a ${closed.name} handle name (string)`, value);
|
|
698
955
|
}
|
|
699
|
-
|
|
700
|
-
|
|
956
|
+
const id = closed.handles.indexOf(value);
|
|
957
|
+
if (id < 0) {
|
|
958
|
+
throw errors.new(`${context}: "${value}" is not a handle of ${closed.name} — the roster is ${closed.handles.join(", ")}`);
|
|
701
959
|
}
|
|
702
|
-
return { kind: "u64", value };
|
|
960
|
+
return { kind: "u64", value: BigInt(id) };
|
|
703
961
|
}
|
|
704
962
|
/**
|
|
705
963
|
* Tags one literal in an interval element domain: a bigint tags as the
|
|
@@ -822,7 +1080,13 @@ function paramIdOf(ctx, name) {
|
|
|
822
1080
|
}
|
|
823
1081
|
return id;
|
|
824
1082
|
}
|
|
825
|
-
/**
|
|
1083
|
+
/**
|
|
1084
|
+
* Lowers one EDB atom (either polarity). A CLOSED owner lowers through the
|
|
1085
|
+
* same edb source — its ordinal is its record-declaration slot exactly like
|
|
1086
|
+
* an ordinary relation's — with field ordinals over the SEALED shape: `id`
|
|
1087
|
+
* at 0, each payload column at its declared index + 1 (`matchFieldsOf`
|
|
1088
|
+
* carries the shift; the lowering golden pins it).
|
|
1089
|
+
*/
|
|
826
1090
|
function lowerAtom(ctx, atom, ids) {
|
|
827
1091
|
const member = ctx.theory.relations[atom.relation.name];
|
|
828
1092
|
if (member !== atom.relation) {
|
|
@@ -832,8 +1096,9 @@ function lowerAtom(ctx, atom, ids) {
|
|
|
832
1096
|
if (relationId === undefined) {
|
|
833
1097
|
throw errors.new(`query lowering: relation ${atom.relation.name} has no ordinal`);
|
|
834
1098
|
}
|
|
1099
|
+
const ordered = matchFieldsOf(atom.relation);
|
|
835
1100
|
const bindings = atom.bindings.map(function lowerBinding(binding) {
|
|
836
|
-
const ordinal =
|
|
1101
|
+
const ordinal = ordered.findIndex(function byName(candidate) {
|
|
837
1102
|
return candidate.name === binding.field;
|
|
838
1103
|
});
|
|
839
1104
|
if (ordinal < 0) {
|
|
@@ -843,7 +1108,12 @@ function lowerAtom(ctx, atom, ids) {
|
|
|
843
1108
|
});
|
|
844
1109
|
return { source: { kind: "edb", relation: relationId }, bindings };
|
|
845
1110
|
}
|
|
846
|
-
/**
|
|
1111
|
+
/**
|
|
1112
|
+
* Lowers one binding term. A membership ARRAY (`literalSet`) lowers to the
|
|
1113
|
+
* existing param-set term over its content-addressed registry entry — the
|
|
1114
|
+
* program IR is byte-identical to the same set spelled `r.inSet`; the SDK
|
|
1115
|
+
* supplies the translated member set itself at execute (`wireParams`).
|
|
1116
|
+
*/
|
|
847
1117
|
function lowerBindingTerm(ctx, context, binding, ids) {
|
|
848
1118
|
const bound = binding.term;
|
|
849
1119
|
switch (bound.kind) {
|
|
@@ -853,6 +1123,8 @@ function lowerBindingTerm(ctx, context, binding, ids) {
|
|
|
853
1123
|
return { kind: "param", param: paramIdOf(ctx, bound.name) };
|
|
854
1124
|
case "setParam":
|
|
855
1125
|
return { kind: "paramSet", param: paramIdOf(ctx, bound.name) };
|
|
1126
|
+
case "literalSet":
|
|
1127
|
+
return { kind: "paramSet", param: paramIdOf(ctx, bound.name) };
|
|
856
1128
|
case "literal":
|
|
857
1129
|
return { kind: "literal", value: taggedLiteral(context, binding.data, bound.value) };
|
|
858
1130
|
}
|
|
@@ -895,7 +1167,7 @@ function lowerCmpTerm(ctx, rule, side, sibling, ids, op) {
|
|
|
895
1167
|
/** Resolves the anchor a comparison literal tags by: the sibling's field, the measure, or an anchored param. */
|
|
896
1168
|
function cmpAnchorOf(ctx, rule, sibling) {
|
|
897
1169
|
if (sibling.kind === "var") {
|
|
898
|
-
return rule.varFields[sibling.name];
|
|
1170
|
+
return rule.varFields[sibling.name]?.field;
|
|
899
1171
|
}
|
|
900
1172
|
if (sibling.kind === "measure") {
|
|
901
1173
|
return "measure";
|