@suss/datalog 0.31.0 → 0.31.2
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/dist/index.cjs +237 -96
- package/dist/index.d.cts +89 -7
- package/dist/index.d.ts +89 -7
- package/dist/index.js +233 -96
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -20,8 +20,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
BudgetExhausted: () => BudgetExhausted,
|
|
23
24
|
Database: () => Database,
|
|
24
25
|
Witness: () => Witness,
|
|
26
|
+
chargeAbandoned: () => chargeAbandoned,
|
|
27
|
+
chargeQuestion: () => chargeQuestion,
|
|
25
28
|
clearRelations: () => clearRelations,
|
|
26
29
|
confidence: () => confidence,
|
|
27
30
|
confidenceWith: () => confidenceWith,
|
|
@@ -34,6 +37,7 @@ __export(index_exports, {
|
|
|
34
37
|
profileEvaluation: () => profileEvaluation,
|
|
35
38
|
profileEvaluationAsync: () => profileEvaluationAsync,
|
|
36
39
|
proofOf: () => proofOf,
|
|
40
|
+
rowBudget: () => rowBudget,
|
|
37
41
|
rule: () => rule,
|
|
38
42
|
ruleLabel: () => ruleLabel,
|
|
39
43
|
stratify: () => stratify,
|
|
@@ -73,6 +77,7 @@ function deriveOnDemand(rules, complete) {
|
|
|
73
77
|
}
|
|
74
78
|
}
|
|
75
79
|
const derives = (relation) => rulesByHead.has(relation);
|
|
80
|
+
const labels = labelColumns(rules, derives);
|
|
76
81
|
const asked = /* @__PURE__ */ new Map();
|
|
77
82
|
const pending = [];
|
|
78
83
|
const want = (adorned) => {
|
|
@@ -94,7 +99,7 @@ function deriveOnDemand(rules, complete) {
|
|
|
94
99
|
while (pending.length > 0) {
|
|
95
100
|
const adorned = pending.pop();
|
|
96
101
|
for (const written of rulesByHead.get(adorned.relation) ?? []) {
|
|
97
|
-
const r = boundFirst(written, adorned.adornment);
|
|
102
|
+
const r = boundFirst(written, adorned.adornment, labels);
|
|
98
103
|
const adornments = bodyAdornments(r, adorned.adornment, derives);
|
|
99
104
|
r.body.forEach((literal, at) => {
|
|
100
105
|
const adornment = adornments[at];
|
|
@@ -137,7 +142,7 @@ function deriveOnDemand(rules, complete) {
|
|
|
137
142
|
});
|
|
138
143
|
}
|
|
139
144
|
for (const written of rulesByHead.get(adorned.relation) ?? []) {
|
|
140
|
-
const r = boundFirst(written, adorned.adornment);
|
|
145
|
+
const r = boundFirst(written, adorned.adornment, labels);
|
|
141
146
|
const guard = anyBound(adorned.adornment) ? [
|
|
142
147
|
positiveLiteral(
|
|
143
148
|
demandOn(adorned.relation, adorned.adornment),
|
|
@@ -178,7 +183,30 @@ function deriveOnDemand(rules, complete) {
|
|
|
178
183
|
return { rules: rewritten, demandDriven: [...demandDriven], demands };
|
|
179
184
|
}
|
|
180
185
|
var sameLiteral = (premises, conclusion) => premises.length === 1 && JSON.stringify(premises[0]) === JSON.stringify(conclusion);
|
|
181
|
-
function
|
|
186
|
+
function labelColumns(rules, derives) {
|
|
187
|
+
const labels = /* @__PURE__ */ new Map();
|
|
188
|
+
const note = ({
|
|
189
|
+
relation,
|
|
190
|
+
terms
|
|
191
|
+
}) => {
|
|
192
|
+
if (!derives(relation)) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
terms.forEach((term, column) => {
|
|
196
|
+
if (term.type === "constant") {
|
|
197
|
+
const columns = labels.get(relation) ?? /* @__PURE__ */ new Set();
|
|
198
|
+
columns.add(column);
|
|
199
|
+
labels.set(relation, columns);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
};
|
|
203
|
+
for (const r of rules) {
|
|
204
|
+
note(r.head);
|
|
205
|
+
r.body.forEach(note);
|
|
206
|
+
}
|
|
207
|
+
return labels;
|
|
208
|
+
}
|
|
209
|
+
function boundFirst(r, headAdornment, labels) {
|
|
182
210
|
const bound = /* @__PURE__ */ new Set();
|
|
183
211
|
r.head.terms.forEach((term, column) => {
|
|
184
212
|
if (headAdornment[column] && term.type === "variable") {
|
|
@@ -186,7 +214,7 @@ function boundFirst(r, headAdornment) {
|
|
|
186
214
|
}
|
|
187
215
|
});
|
|
188
216
|
const boundColumns = (literal) => literal.terms.filter(
|
|
189
|
-
(term) => term.type === "constant" || bound.has(term.name)
|
|
217
|
+
(term, column) => !labels.get(literal.relation)?.has(column) && (term.type === "constant" || bound.has(term.name))
|
|
190
218
|
).length;
|
|
191
219
|
const remaining = [...r.body];
|
|
192
220
|
const body = [];
|
|
@@ -231,21 +259,43 @@ function bodyAdornments(r, headAdornment, derives) {
|
|
|
231
259
|
var open = [];
|
|
232
260
|
var isProfiling = () => open.length > 0;
|
|
233
261
|
var ruleKey = (head, body) => `${head} <- ${body.join(", ")}`;
|
|
234
|
-
function addCost(into, key, head, body, ms, derived) {
|
|
262
|
+
function addCost(into, key, head, body, ms, derived, examined) {
|
|
235
263
|
const existing = into.get(key);
|
|
236
264
|
if (existing === void 0) {
|
|
237
|
-
into.set(key, { head, body, ms, derived, attempts: 1 });
|
|
265
|
+
into.set(key, { head, body, ms, derived, examined, attempts: 1 });
|
|
238
266
|
return;
|
|
239
267
|
}
|
|
240
268
|
existing.ms += ms;
|
|
241
269
|
existing.derived += derived;
|
|
270
|
+
existing.examined += examined;
|
|
242
271
|
existing.attempts += 1;
|
|
243
272
|
}
|
|
244
|
-
function chargeRule(ruleSet, head, body, ms, derived) {
|
|
273
|
+
function chargeRule(ruleSet, head, body, ms, derived, examined) {
|
|
245
274
|
const key = ruleKey(head, body);
|
|
246
275
|
for (const collector of open) {
|
|
247
|
-
addCost(collector.rules, key, head, body, ms, derived);
|
|
248
|
-
addCost(
|
|
276
|
+
addCost(collector.rules, key, head, body, ms, derived, examined);
|
|
277
|
+
addCost(
|
|
278
|
+
setIn(collector, ruleSet).rules,
|
|
279
|
+
key,
|
|
280
|
+
head,
|
|
281
|
+
body,
|
|
282
|
+
ms,
|
|
283
|
+
derived,
|
|
284
|
+
examined
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function chargeAbandoned(question, examined) {
|
|
289
|
+
for (const collector of open) {
|
|
290
|
+
collector.abandoned.push({ question, examined });
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
function chargeQuestion(outcome) {
|
|
294
|
+
for (const collector of open) {
|
|
295
|
+
collector.questions.asked += 1;
|
|
296
|
+
if (outcome !== void 0) {
|
|
297
|
+
collector.questions[outcome] += 1;
|
|
298
|
+
}
|
|
249
299
|
}
|
|
250
300
|
}
|
|
251
301
|
function chargeEvaluation(ruleSet) {
|
|
@@ -254,6 +304,13 @@ function chargeEvaluation(ruleSet) {
|
|
|
254
304
|
setIn(collector, ruleSet).evaluations += 1;
|
|
255
305
|
}
|
|
256
306
|
}
|
|
307
|
+
function chargeEvaluationRows(examined) {
|
|
308
|
+
for (const collector of open) {
|
|
309
|
+
if (examined > collector.largestEvaluation) {
|
|
310
|
+
collector.largestEvaluation = examined;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
257
314
|
function chargeRound(ruleSet) {
|
|
258
315
|
for (const collector of open) {
|
|
259
316
|
collector.rounds += 1;
|
|
@@ -313,7 +370,10 @@ function openScope() {
|
|
|
313
370
|
rules: /* @__PURE__ */ new Map(),
|
|
314
371
|
relations: /* @__PURE__ */ new Map(),
|
|
315
372
|
derivedRelations: /* @__PURE__ */ new Set(),
|
|
316
|
-
ruleSets: /* @__PURE__ */ new Map()
|
|
373
|
+
ruleSets: /* @__PURE__ */ new Map(),
|
|
374
|
+
abandoned: [],
|
|
375
|
+
largestEvaluation: 0,
|
|
376
|
+
questions: { asked: 0, abandoned: 0, skipped: 0 }
|
|
317
377
|
};
|
|
318
378
|
open.push(mine);
|
|
319
379
|
return mine;
|
|
@@ -331,6 +391,13 @@ var totalMs = (rules) => {
|
|
|
331
391
|
}
|
|
332
392
|
return ms;
|
|
333
393
|
};
|
|
394
|
+
var totalExamined = (rules) => {
|
|
395
|
+
let rows = 0;
|
|
396
|
+
for (const cost of rules) {
|
|
397
|
+
rows += cost.examined;
|
|
398
|
+
}
|
|
399
|
+
return rows;
|
|
400
|
+
};
|
|
334
401
|
var byCost = (a, b) => b.ms - a.ms;
|
|
335
402
|
function summarise(mine) {
|
|
336
403
|
return {
|
|
@@ -338,6 +405,10 @@ function summarise(mine) {
|
|
|
338
405
|
datalogMs: totalMs(mine.rules.values()),
|
|
339
406
|
rounds: mine.rounds,
|
|
340
407
|
evaluations: mine.evaluations,
|
|
408
|
+
examined: totalExamined(mine.rules.values()),
|
|
409
|
+
largestEvaluation: mine.largestEvaluation,
|
|
410
|
+
abandoned: [...mine.abandoned],
|
|
411
|
+
questions: { ...mine.questions },
|
|
341
412
|
relations: [...mine.relations].map(([relation, tuples]) => ({
|
|
342
413
|
relation,
|
|
343
414
|
tuples,
|
|
@@ -358,14 +429,26 @@ function ruleLines(rules, datalogMs) {
|
|
|
358
429
|
return rules.slice(0, 20).map((r) => {
|
|
359
430
|
const ms = `${r.ms.toFixed(0)}ms`.padStart(8);
|
|
360
431
|
const derived = String(r.derived).padStart(7);
|
|
361
|
-
|
|
432
|
+
const examined = String(r.examined).padStart(9);
|
|
433
|
+
return ` ${ms} ${share(r.ms, datalogMs)} ${derived} tuples ${examined} rows read ${r.attempts} attempts ${ruleKey(r.head, r.body)}`;
|
|
362
434
|
});
|
|
363
435
|
}
|
|
364
436
|
function formatProfile(profile) {
|
|
365
437
|
const lines = [];
|
|
366
438
|
lines.push(
|
|
367
|
-
`datalog: ${profile.datalogMs.toFixed(0)}ms (${share(profile.datalogMs, profile.wallMs).trim()} of ${profile.wallMs.toFixed(0)}ms wall), ${profile.evaluations} evaluations, ${profile.rounds} rounds`
|
|
439
|
+
`datalog: ${profile.datalogMs.toFixed(0)}ms (${share(profile.datalogMs, profile.wallMs).trim()} of ${profile.wallMs.toFixed(0)}ms wall), ${profile.evaluations} evaluations, ${profile.rounds} rounds, ${profile.examined} rows read (${profile.largestEvaluation} in the biggest evaluation)`
|
|
368
440
|
);
|
|
441
|
+
const { asked, abandoned, skipped } = profile.questions;
|
|
442
|
+
if (asked > 0) {
|
|
443
|
+
lines.push(
|
|
444
|
+
` ${asked} questions under a site: ${abandoned} given up part way, ${skipped} never put once the run's rows were spent`
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
for (const given of profile.abandoned) {
|
|
448
|
+
lines.push(
|
|
449
|
+
` gave up on ${given.question} after reading ${given.examined} rows`
|
|
450
|
+
);
|
|
451
|
+
}
|
|
369
452
|
const derivedTuples = profile.relations.filter((r) => r.derived).reduce((sum, r) => sum + r.tuples, 0);
|
|
370
453
|
lines.push(" tuples by relation (d = derived by a rule):");
|
|
371
454
|
for (const { relation, tuples, derived } of profile.relations.slice(0, 20)) {
|
|
@@ -779,93 +862,109 @@ function headTuple(head, bindings) {
|
|
|
779
862
|
return bound;
|
|
780
863
|
});
|
|
781
864
|
}
|
|
782
|
-
var
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
const known = byDelta.get(deltaIndex);
|
|
791
|
-
if (known !== void 0) {
|
|
792
|
-
return known;
|
|
793
|
-
}
|
|
794
|
-
const positives = r.body.map((literal, index) => literal.negated ? -1 : index).filter((index) => index !== -1);
|
|
795
|
-
const deltaAt = positives[deltaIndex] ?? -1;
|
|
796
|
-
const computed = { deltaAt, order: planBodyOrder(r, deltaAt) };
|
|
797
|
-
byDelta.set(deltaIndex, computed);
|
|
798
|
-
return computed;
|
|
865
|
+
var positiveLiterals = /* @__PURE__ */ new WeakMap();
|
|
866
|
+
function deltaLiteral(r, deltaIndex) {
|
|
867
|
+
let positives = positiveLiterals.get(r);
|
|
868
|
+
if (positives === void 0) {
|
|
869
|
+
positives = r.body.map((literal, index) => literal.negated ? -1 : index).filter((index) => index !== -1);
|
|
870
|
+
positiveLiterals.set(r, positives);
|
|
871
|
+
}
|
|
872
|
+
return positives[deltaIndex] ?? -1;
|
|
799
873
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
)
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
874
|
+
var allBound = (literal, bindings) => literal.terms.every(
|
|
875
|
+
(term) => term.type === "constant" || boundValue(bindings, term.name) !== void 0
|
|
876
|
+
);
|
|
877
|
+
var rowBudget = (limit) => ({
|
|
878
|
+
examined: 0,
|
|
879
|
+
limit
|
|
880
|
+
});
|
|
881
|
+
var OUT_OF_BUDGET = /* @__PURE__ */ Symbol("datalog row budget exhausted");
|
|
882
|
+
var BudgetExhausted = class extends Error {
|
|
883
|
+
constructor(ruleSet, examined) {
|
|
884
|
+
super(`gave up after reading ${examined} rows without settling`);
|
|
885
|
+
this.ruleSet = ruleSet;
|
|
886
|
+
this.examined = examined;
|
|
887
|
+
this.name = "BudgetExhausted";
|
|
888
|
+
}
|
|
889
|
+
ruleSet;
|
|
890
|
+
examined;
|
|
891
|
+
};
|
|
892
|
+
function evaluateRule(db, deltas, r, deltaIndex, budget) {
|
|
893
|
+
const results = [];
|
|
894
|
+
const body = r.body;
|
|
895
|
+
const deltaAt = deltaLiteral(r, deltaIndex);
|
|
896
|
+
const whole = 2 ** body.length - 1;
|
|
897
|
+
const walk2 = (index, source, taken, bindings) => {
|
|
898
|
+
const literal = body[index];
|
|
899
|
+
for (const tuple of source) {
|
|
900
|
+
budget.examined++;
|
|
901
|
+
if (budget.examined > budget.limit) {
|
|
902
|
+
throw OUT_OF_BUDGET;
|
|
903
|
+
}
|
|
904
|
+
const next = unify(literal, tuple, bindings);
|
|
905
|
+
if (next !== NO_MATCH) {
|
|
906
|
+
step(taken | 1 << index, next);
|
|
907
|
+
}
|
|
832
908
|
}
|
|
833
|
-
return firstWhere(() => true);
|
|
834
909
|
};
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
take(nextLiteral());
|
|
838
|
-
}
|
|
839
|
-
return order;
|
|
840
|
-
}
|
|
841
|
-
function evaluateRule(db, deltas, r, deltaIndex) {
|
|
842
|
-
const results = [];
|
|
843
|
-
const { deltaAt, order } = bodyPlan(r, deltaIndex);
|
|
844
|
-
const step = (orderIndex, bindings) => {
|
|
845
|
-
if (orderIndex === order.length) {
|
|
910
|
+
const step = (taken, bindings) => {
|
|
911
|
+
if (taken === whole) {
|
|
846
912
|
results.push(headTuple(r.head, bindings));
|
|
847
913
|
return;
|
|
848
914
|
}
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
if (
|
|
853
|
-
|
|
915
|
+
let pick = -1;
|
|
916
|
+
let narrowest = null;
|
|
917
|
+
for (let index = 0; index < body.length; index++) {
|
|
918
|
+
if (taken & 1 << index) {
|
|
919
|
+
continue;
|
|
920
|
+
}
|
|
921
|
+
const literal = body[index];
|
|
922
|
+
if (literal.negated) {
|
|
923
|
+
if (allBound(literal, bindings)) {
|
|
924
|
+
if (!db.has(literal.relation, groundNegated(literal, bindings))) {
|
|
925
|
+
step(taken | 1 << index, bindings);
|
|
926
|
+
}
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
continue;
|
|
930
|
+
}
|
|
931
|
+
const source = narrowedSource(db, literal, bindings);
|
|
932
|
+
if (source === null) {
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
if (source.length === 0) {
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
if (narrowest === null || source.length < narrowest.length) {
|
|
939
|
+
pick = index;
|
|
940
|
+
narrowest = source;
|
|
854
941
|
}
|
|
942
|
+
}
|
|
943
|
+
if (narrowest !== null) {
|
|
944
|
+
walk2(pick, narrowest, taken, bindings);
|
|
855
945
|
return;
|
|
856
946
|
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
947
|
+
for (let index = 0; index < body.length; index++) {
|
|
948
|
+
if (!(taken & 1 << index) && !body[index].negated) {
|
|
949
|
+
walk2(index, db.facts(body[index].relation), taken, bindings);
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
for (let index = 0; index < body.length; index++) {
|
|
954
|
+
if (!(taken & 1 << index)) {
|
|
955
|
+
groundNegated(body[index], bindings);
|
|
862
956
|
}
|
|
863
957
|
}
|
|
864
958
|
};
|
|
865
|
-
|
|
959
|
+
if (deltaAt === -1) {
|
|
960
|
+
step(0, null);
|
|
961
|
+
} else {
|
|
962
|
+
walk2(deltaAt, deltas.get(body[deltaAt].relation) ?? [], 0, null);
|
|
963
|
+
}
|
|
866
964
|
return results;
|
|
867
965
|
}
|
|
868
|
-
|
|
966
|
+
var boundSource = (db, literal, bindings) => narrowedSource(db, literal, bindings) ?? db.facts(literal.relation);
|
|
967
|
+
function narrowedSource(db, literal, bindings) {
|
|
869
968
|
let narrowest = null;
|
|
870
969
|
for (let column = 0; column < literal.terms.length; column++) {
|
|
871
970
|
const term = literal.terms[column];
|
|
@@ -881,10 +980,10 @@ function boundSource(db, literal, bindings) {
|
|
|
881
980
|
narrowest = bucket;
|
|
882
981
|
}
|
|
883
982
|
}
|
|
884
|
-
return narrowest
|
|
983
|
+
return narrowest;
|
|
885
984
|
}
|
|
886
985
|
var EMPTY_TAGS = [];
|
|
887
|
-
function evaluateRuleTagged(db, deltas, r, deltaIndex, algebra) {
|
|
986
|
+
function evaluateRuleTagged(db, deltas, r, deltaIndex, algebra, budget) {
|
|
888
987
|
const results = [];
|
|
889
988
|
const bodyTags = [];
|
|
890
989
|
const bodyMatches = [];
|
|
@@ -922,6 +1021,7 @@ function evaluateRuleTagged(db, deltas, r, deltaIndex, algebra) {
|
|
|
922
1021
|
}
|
|
923
1022
|
const source = positiveIndex === deltaIndex ? deltas.get(literal.relation) ?? [] : boundSource(db, literal, bindings);
|
|
924
1023
|
for (const tuple of source) {
|
|
1024
|
+
budget.examined++;
|
|
925
1025
|
const next = unify(literal, tuple, bindings);
|
|
926
1026
|
if (next !== NO_MATCH) {
|
|
927
1027
|
if (readsTags) {
|
|
@@ -1005,15 +1105,18 @@ function currentMarks(db) {
|
|
|
1005
1105
|
}
|
|
1006
1106
|
return marks;
|
|
1007
1107
|
}
|
|
1008
|
-
function evaluate(db, rules, algebra) {
|
|
1108
|
+
function evaluate(db, rules, algebra, budget) {
|
|
1009
1109
|
if (algebra !== void 0 && isDemandRewritten(rules)) {
|
|
1010
1110
|
throw new Error(
|
|
1011
1111
|
"cannot evaluate demand-rewritten rules with a tag algebra: tags would follow the demand-transformed rules rather than the ones the caller wrote"
|
|
1012
1112
|
);
|
|
1013
1113
|
}
|
|
1114
|
+
if (algebra !== void 0 && budget !== void 0) {
|
|
1115
|
+
throw new Error("cannot evaluate with both a tag algebra and a row budget");
|
|
1116
|
+
}
|
|
1014
1117
|
deriving.set(db, (deriving.get(db) ?? 0) + 1);
|
|
1015
1118
|
try {
|
|
1016
|
-
return runRules(db, rules, algebra);
|
|
1119
|
+
return runRules(db, rules, algebra, budget);
|
|
1017
1120
|
} finally {
|
|
1018
1121
|
const depth = (deriving.get(db) ?? 1) - 1;
|
|
1019
1122
|
if (depth === 0) {
|
|
@@ -1063,13 +1166,19 @@ function storedMergeFor(algebra) {
|
|
|
1063
1166
|
return stored === void 0 && merged === algebra.asserted ? void 0 : merged;
|
|
1064
1167
|
};
|
|
1065
1168
|
}
|
|
1066
|
-
function runRules(db, rules, algebra) {
|
|
1169
|
+
function runRules(db, rules, algebra, given) {
|
|
1067
1170
|
const {
|
|
1068
1171
|
signature,
|
|
1069
1172
|
name: ruleSetName,
|
|
1070
1173
|
derivedRelations,
|
|
1071
1174
|
strata
|
|
1072
1175
|
} = shapeOf(rules);
|
|
1176
|
+
const budget = given ?? {
|
|
1177
|
+
examined: 0,
|
|
1178
|
+
limit: Number.POSITIVE_INFINITY
|
|
1179
|
+
};
|
|
1180
|
+
const startedAt = budget.examined;
|
|
1181
|
+
const addedHere = given === void 0 ? null : [];
|
|
1073
1182
|
chargeEvaluation(ruleSetName);
|
|
1074
1183
|
const states = statesFor(db);
|
|
1075
1184
|
const state = states.get(signature) ?? {
|
|
@@ -1091,10 +1200,11 @@ function runRules(db, rules, algebra) {
|
|
|
1091
1200
|
derived.set(relation, ledger);
|
|
1092
1201
|
}
|
|
1093
1202
|
ledger.set(keyOf2(tuple), tuple);
|
|
1203
|
+
addedHere?.push([relation, tuple]);
|
|
1094
1204
|
};
|
|
1095
1205
|
const mergeStored = algebra === void 0 ? void 0 : storedMergeFor(algebra);
|
|
1096
1206
|
const marks = canResume(rules, state) ? state.marks : void 0;
|
|
1097
|
-
|
|
1207
|
+
const runStratum = (stratum) => {
|
|
1098
1208
|
let delta = /* @__PURE__ */ new Map();
|
|
1099
1209
|
const derivedHere = new Set(stratum.map((r) => r.head.relation));
|
|
1100
1210
|
const record = (relation, tuple) => {
|
|
@@ -1120,13 +1230,14 @@ function runRules(db, rules, algebra) {
|
|
|
1120
1230
|
seed,
|
|
1121
1231
|
r,
|
|
1122
1232
|
deltaIndex,
|
|
1123
|
-
algebra
|
|
1233
|
+
algebra,
|
|
1234
|
+
budget
|
|
1124
1235
|
)) {
|
|
1125
1236
|
recordTagged(r.head.relation, found.tuple, found.tag);
|
|
1126
1237
|
}
|
|
1127
1238
|
return;
|
|
1128
1239
|
}
|
|
1129
|
-
for (const tuple of evaluateRule(db, seed, r, deltaIndex)) {
|
|
1240
|
+
for (const tuple of evaluateRule(db, seed, r, deltaIndex, budget)) {
|
|
1130
1241
|
record(r.head.relation, tuple);
|
|
1131
1242
|
}
|
|
1132
1243
|
};
|
|
@@ -1135,15 +1246,17 @@ function runRules(db, rules, algebra) {
|
|
|
1135
1246
|
deriveInto(r, seed, deltaIndex);
|
|
1136
1247
|
return;
|
|
1137
1248
|
}
|
|
1138
|
-
const
|
|
1249
|
+
const startedAt2 = performance.now();
|
|
1139
1250
|
const before = db.size(r.head.relation);
|
|
1251
|
+
const readBefore = budget.examined;
|
|
1140
1252
|
deriveInto(r, seed, deltaIndex);
|
|
1141
1253
|
chargeRule(
|
|
1142
1254
|
ruleSetName,
|
|
1143
1255
|
r.head.relation,
|
|
1144
1256
|
r.body.map(literalName),
|
|
1145
|
-
performance.now() -
|
|
1146
|
-
db.size(r.head.relation) - before
|
|
1257
|
+
performance.now() - startedAt2,
|
|
1258
|
+
db.size(r.head.relation) - before,
|
|
1259
|
+
budget.examined - readBefore
|
|
1147
1260
|
);
|
|
1148
1261
|
};
|
|
1149
1262
|
const applyDelta = (seed, derivedOnly) => {
|
|
@@ -1188,7 +1301,24 @@ function runRules(db, rules, algebra) {
|
|
|
1188
1301
|
applyDelta(lastDelta, true);
|
|
1189
1302
|
chargeRound(ruleSetName);
|
|
1190
1303
|
}
|
|
1304
|
+
};
|
|
1305
|
+
const readHere = () => budget.examined - startedAt;
|
|
1306
|
+
try {
|
|
1307
|
+
for (const stratum of strata) {
|
|
1308
|
+
runStratum(stratum);
|
|
1309
|
+
}
|
|
1310
|
+
} catch (error) {
|
|
1311
|
+
if (error !== OUT_OF_BUDGET) {
|
|
1312
|
+
throw error;
|
|
1313
|
+
}
|
|
1314
|
+
for (const [relation, tuples] of byRelation(addedHere ?? [])) {
|
|
1315
|
+
db.retract(relation, tuples);
|
|
1316
|
+
}
|
|
1317
|
+
state.marks = null;
|
|
1318
|
+
chargeEvaluationRows(readHere());
|
|
1319
|
+
throw new BudgetExhausted(ruleSetName, readHere());
|
|
1191
1320
|
}
|
|
1321
|
+
chargeEvaluationRows(readHere());
|
|
1192
1322
|
state.marks = currentMarks(db);
|
|
1193
1323
|
if (isProfiling()) {
|
|
1194
1324
|
chargeRelationSizes(
|
|
@@ -1198,6 +1328,13 @@ function runRules(db, rules, algebra) {
|
|
|
1198
1328
|
}
|
|
1199
1329
|
return db;
|
|
1200
1330
|
}
|
|
1331
|
+
function byRelation(added) {
|
|
1332
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
1333
|
+
for (const [relation, tuple] of added) {
|
|
1334
|
+
addTo(buckets, relation, tuple);
|
|
1335
|
+
}
|
|
1336
|
+
return buckets;
|
|
1337
|
+
}
|
|
1201
1338
|
function addTo(buckets, relation, tuple) {
|
|
1202
1339
|
const bucket = buckets.get(relation);
|
|
1203
1340
|
if (bucket === void 0) {
|
|
@@ -1208,8 +1345,11 @@ function addTo(buckets, relation, tuple) {
|
|
|
1208
1345
|
}
|
|
1209
1346
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1210
1347
|
0 && (module.exports = {
|
|
1348
|
+
BudgetExhausted,
|
|
1211
1349
|
Database,
|
|
1212
1350
|
Witness,
|
|
1351
|
+
chargeAbandoned,
|
|
1352
|
+
chargeQuestion,
|
|
1213
1353
|
clearRelations,
|
|
1214
1354
|
confidence,
|
|
1215
1355
|
confidenceWith,
|
|
@@ -1222,6 +1362,7 @@ function addTo(buckets, relation, tuple) {
|
|
|
1222
1362
|
profileEvaluation,
|
|
1223
1363
|
profileEvaluationAsync,
|
|
1224
1364
|
proofOf,
|
|
1365
|
+
rowBudget,
|
|
1225
1366
|
rule,
|
|
1226
1367
|
ruleLabel,
|
|
1227
1368
|
stratify,
|