@suss/datalog 0.33.1 → 0.34.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -17
- package/dist/index.cjs +515 -101
- package/dist/index.d.cts +121 -61
- package/dist/index.d.ts +121 -61
- package/dist/index.js +514 -101
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -40,6 +40,7 @@ __export(index_exports, {
|
|
|
40
40
|
rowBudget: () => rowBudget,
|
|
41
41
|
rule: () => rule,
|
|
42
42
|
ruleLabel: () => ruleLabel,
|
|
43
|
+
rulesDeriving: () => rulesDeriving,
|
|
43
44
|
stratify: () => stratify,
|
|
44
45
|
tupleKey: () => tupleKey,
|
|
45
46
|
tupleKeyParts: () => tupleKeyParts,
|
|
@@ -307,7 +308,7 @@ function boundFirst(r, headAdornment, labels) {
|
|
|
307
308
|
}
|
|
308
309
|
});
|
|
309
310
|
const boundColumns = (literal) => literal.terms.filter(
|
|
310
|
-
(term, column) => !labels.get(literal.relation)?.has(column) &&
|
|
311
|
+
(term, column) => !labels.get(literal.relation)?.has(column) && term.type === "variable" && bound.has(term.name)
|
|
311
312
|
).length;
|
|
312
313
|
const remaining = [...r.body];
|
|
313
314
|
const body = [];
|
|
@@ -404,6 +405,11 @@ function chargeEvaluationRows(examined) {
|
|
|
404
405
|
}
|
|
405
406
|
}
|
|
406
407
|
}
|
|
408
|
+
function chargeEngine(part, ms) {
|
|
409
|
+
for (const collector of open) {
|
|
410
|
+
collector.engineMs[part] += ms;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
407
413
|
function chargeRound(ruleSet) {
|
|
408
414
|
for (const collector of open) {
|
|
409
415
|
collector.rounds += 1;
|
|
@@ -460,6 +466,7 @@ function openScope() {
|
|
|
460
466
|
startedAt: performance.now(),
|
|
461
467
|
rounds: 0,
|
|
462
468
|
evaluations: 0,
|
|
469
|
+
engineMs: { evaluate: 0, clear: 0 },
|
|
463
470
|
rules: /* @__PURE__ */ new Map(),
|
|
464
471
|
relations: /* @__PURE__ */ new Map(),
|
|
465
472
|
derivedRelations: /* @__PURE__ */ new Set(),
|
|
@@ -496,6 +503,7 @@ function summarise(mine) {
|
|
|
496
503
|
return {
|
|
497
504
|
wallMs: performance.now() - mine.startedAt,
|
|
498
505
|
datalogMs: totalMs(mine.rules.values()),
|
|
506
|
+
engineMs: { ...mine.engineMs },
|
|
499
507
|
rounds: mine.rounds,
|
|
500
508
|
evaluations: mine.evaluations,
|
|
501
509
|
examined: totalExamined(mine.rules.values()),
|
|
@@ -531,6 +539,9 @@ function formatProfile(profile) {
|
|
|
531
539
|
lines.push(
|
|
532
540
|
`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)`
|
|
533
541
|
);
|
|
542
|
+
lines.push(
|
|
543
|
+
` engine: ${profile.engineMs.evaluate.toFixed(0)}ms inside evaluate (${profile.datalogMs.toFixed(0)}ms of it in rules), ${profile.engineMs.clear.toFixed(0)}ms clearing relations between questions`
|
|
544
|
+
);
|
|
534
545
|
const { asked, abandoned, skipped } = profile.questions;
|
|
535
546
|
if (asked > 0) {
|
|
536
547
|
lines.push(
|
|
@@ -563,6 +574,102 @@ function formatProfile(profile) {
|
|
|
563
574
|
}
|
|
564
575
|
|
|
565
576
|
// src/relation.ts
|
|
577
|
+
var sameTuple = (a, b) => a.length === b.length && a.every((atom, at) => atom === b[at]);
|
|
578
|
+
function storedTuplesOf(relation, facts) {
|
|
579
|
+
const column = relation.columns.findIndex((buckets2) => buckets2 !== void 0);
|
|
580
|
+
const buckets = relation.columns[column];
|
|
581
|
+
if (buckets === void 0) {
|
|
582
|
+
return null;
|
|
583
|
+
}
|
|
584
|
+
const stored = facts.map(
|
|
585
|
+
(fact) => buckets.get(fact[column])?.find((tuple) => sameTuple(tuple, fact))
|
|
586
|
+
);
|
|
587
|
+
return stored.every((tuple) => tuple !== void 0) ? stored : null;
|
|
588
|
+
}
|
|
589
|
+
function removeFrom(buckets, key, tuple) {
|
|
590
|
+
const bucket = buckets.get(key);
|
|
591
|
+
const at = bucket?.indexOf(tuple) ?? -1;
|
|
592
|
+
if (bucket === void 0 || at === -1) {
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
bucket.splice(at, 1);
|
|
596
|
+
if (bucket.length === 0) {
|
|
597
|
+
buckets.delete(key);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
function removeTuples(relation, stored) {
|
|
601
|
+
const leaving = new Set(stored);
|
|
602
|
+
relation.tuples = relation.tuples.filter((tuple) => !leaving.has(tuple));
|
|
603
|
+
for (const tuple of stored) {
|
|
604
|
+
relation.columns.forEach((buckets, column) => {
|
|
605
|
+
if (buckets !== void 0) {
|
|
606
|
+
removeFrom(buckets, tuple[column], tuple);
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
for (const index of relation.combined.values()) {
|
|
610
|
+
let level = index.root;
|
|
611
|
+
const last = index.columns.length - 1;
|
|
612
|
+
for (let at = 0; at < last && level !== void 0; at++) {
|
|
613
|
+
level = level.get(tuple[index.columns[at]]);
|
|
614
|
+
}
|
|
615
|
+
if (level !== void 0) {
|
|
616
|
+
removeFrom(
|
|
617
|
+
level,
|
|
618
|
+
tuple[index.columns[last]],
|
|
619
|
+
tuple
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
function columnsIn(mask) {
|
|
626
|
+
const columns = [];
|
|
627
|
+
for (let column = 0; mask >> column !== 0; column++) {
|
|
628
|
+
if (mask & 1 << column) {
|
|
629
|
+
columns.push(column);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return columns;
|
|
633
|
+
}
|
|
634
|
+
function addToCombined(index, tuple) {
|
|
635
|
+
const last = index.columns.length - 1;
|
|
636
|
+
if (index.columns[last] >= tuple.length) {
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
let level = index.root;
|
|
640
|
+
for (let at = 0; at < last; at++) {
|
|
641
|
+
const value = tuple[index.columns[at]];
|
|
642
|
+
let next = level.get(value);
|
|
643
|
+
if (next === void 0) {
|
|
644
|
+
next = /* @__PURE__ */ new Map();
|
|
645
|
+
level.set(value, next);
|
|
646
|
+
}
|
|
647
|
+
level = next;
|
|
648
|
+
}
|
|
649
|
+
addToBucket(
|
|
650
|
+
level,
|
|
651
|
+
tuple[index.columns[last]],
|
|
652
|
+
tuple
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
function combinedBucketIn(relation, mask, values) {
|
|
656
|
+
let index = relation.combined.get(mask);
|
|
657
|
+
if (index === void 0) {
|
|
658
|
+
index = { columns: columnsIn(mask), root: /* @__PURE__ */ new Map() };
|
|
659
|
+
for (const tuple of relation.tuples) {
|
|
660
|
+
addToCombined(index, tuple);
|
|
661
|
+
}
|
|
662
|
+
relation.combined.set(mask, index);
|
|
663
|
+
}
|
|
664
|
+
let level = index.root;
|
|
665
|
+
for (const value of values) {
|
|
666
|
+
level = level.get(value);
|
|
667
|
+
if (level === void 0) {
|
|
668
|
+
return [];
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return level;
|
|
672
|
+
}
|
|
566
673
|
function addToBucket(buckets, key, tuple) {
|
|
567
674
|
const bucket = buckets.get(key);
|
|
568
675
|
if (bucket === void 0) {
|
|
@@ -586,6 +693,72 @@ function bucketIn(relation, column, value) {
|
|
|
586
693
|
return buckets.get(value) ?? [];
|
|
587
694
|
}
|
|
588
695
|
|
|
696
|
+
// src/stratum.ts
|
|
697
|
+
function planStratum(rules) {
|
|
698
|
+
const derived = /* @__PURE__ */ new Set();
|
|
699
|
+
const readers = /* @__PURE__ */ new Map();
|
|
700
|
+
for (let at = 0; at < rules.length; at++) {
|
|
701
|
+
const r = rules[at];
|
|
702
|
+
derived.add(r.head.relation);
|
|
703
|
+
for (const literal of r.body) {
|
|
704
|
+
if (literal.negated) {
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
const positions = readers.get(literal.relation);
|
|
708
|
+
if (positions === void 0) {
|
|
709
|
+
readers.set(literal.relation, [at]);
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
if (positions[positions.length - 1] !== at) {
|
|
713
|
+
positions.push(at);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return { rules, derived, readers, marked: new Uint8Array(rules.length) };
|
|
718
|
+
}
|
|
719
|
+
var NONE = [];
|
|
720
|
+
function rulesReading(stratum, seed, derivedOnly) {
|
|
721
|
+
const lists = [];
|
|
722
|
+
for (const [relation, tuples] of seed) {
|
|
723
|
+
if (tuples.length === 0) {
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
if (derivedOnly && !stratum.derived.has(relation)) {
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
const positions = stratum.readers.get(relation);
|
|
730
|
+
if (positions !== void 0) {
|
|
731
|
+
lists.push(positions);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
if (lists.length === 0) {
|
|
735
|
+
return NONE;
|
|
736
|
+
}
|
|
737
|
+
if (lists.length === 1) {
|
|
738
|
+
return lists[0];
|
|
739
|
+
}
|
|
740
|
+
return unionInOrder(lists, stratum.marked);
|
|
741
|
+
}
|
|
742
|
+
function unionInOrder(lists, marked) {
|
|
743
|
+
let first = marked.length;
|
|
744
|
+
let last = -1;
|
|
745
|
+
for (const positions of lists) {
|
|
746
|
+
for (const at of positions) {
|
|
747
|
+
marked[at] = 1;
|
|
748
|
+
}
|
|
749
|
+
first = Math.min(first, positions[0]);
|
|
750
|
+
last = Math.max(last, positions[positions.length - 1]);
|
|
751
|
+
}
|
|
752
|
+
const union = [];
|
|
753
|
+
for (let at = first; at <= last; at++) {
|
|
754
|
+
if (marked[at] === 1) {
|
|
755
|
+
marked[at] = 0;
|
|
756
|
+
union.push(at);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
return union;
|
|
760
|
+
}
|
|
761
|
+
|
|
589
762
|
// src/confidence.ts
|
|
590
763
|
var RANK = {
|
|
591
764
|
low: 0,
|
|
@@ -664,9 +837,7 @@ var Witness = class {
|
|
|
664
837
|
var witnesses = {
|
|
665
838
|
asserted: "asserted",
|
|
666
839
|
absent: "absent",
|
|
667
|
-
// A witness is built from the derivation alone
|
|
668
|
-
// the evaluator skip the body-tag lookups, which were most of the
|
|
669
|
-
// tagged path's cost on a large sweep.
|
|
840
|
+
// A witness is built from the derivation alone.
|
|
670
841
|
ignoresBodyTags: true,
|
|
671
842
|
combine: (_bodyTags, derivation) => new Witness(derivation.rule, derivation.body),
|
|
672
843
|
merge: (stored) => stored
|
|
@@ -728,6 +899,7 @@ var notLit = (relation, ...terms) => ({
|
|
|
728
899
|
var rule = (relation, terms, body, name) => name === void 0 ? { head: { relation, terms }, body } : { head: { relation, terms }, body, name };
|
|
729
900
|
var literalName = (literal) => literal.negated ? `!${literal.relation}` : literal.relation;
|
|
730
901
|
var ruleLabel = (r) => r.name ?? `${r.head.relation} :- ${r.body.map(literalName).join(", ")}`;
|
|
902
|
+
var FEW_REMOVED = 16;
|
|
731
903
|
var Database = class {
|
|
732
904
|
store = /* @__PURE__ */ new Map();
|
|
733
905
|
relation(name) {
|
|
@@ -738,15 +910,16 @@ var Database = class {
|
|
|
738
910
|
const created = {
|
|
739
911
|
index: new FactIndex(),
|
|
740
912
|
tuples: [],
|
|
741
|
-
columns: []
|
|
913
|
+
columns: [],
|
|
914
|
+
combined: /* @__PURE__ */ new Map()
|
|
742
915
|
};
|
|
743
916
|
this.store.set(name, created);
|
|
744
917
|
return created;
|
|
745
918
|
}
|
|
746
919
|
/**
|
|
747
920
|
* The key this relation gives a tuple, whether or not the fact is
|
|
748
|
-
* present. Evaluation keys its ledger of derived facts by it
|
|
749
|
-
*
|
|
921
|
+
* present. Evaluation keys its ledger of derived facts by it. Nothing
|
|
922
|
+
* outside this module accepts a key.
|
|
750
923
|
*/
|
|
751
924
|
keyFor(relationName, tuple) {
|
|
752
925
|
return this.relation(relationName).index.key(tuple);
|
|
@@ -786,6 +959,9 @@ var Database = class {
|
|
|
786
959
|
addToBucket(buckets, value, tuple);
|
|
787
960
|
}
|
|
788
961
|
}
|
|
962
|
+
for (const combined of relation.combined.values()) {
|
|
963
|
+
addToCombined(combined, tuple);
|
|
964
|
+
}
|
|
789
965
|
return "added";
|
|
790
966
|
}
|
|
791
967
|
/** The tag stored for this fact, or undefined when there is none. */
|
|
@@ -851,18 +1027,45 @@ var Database = class {
|
|
|
851
1027
|
if (index.size === 0) {
|
|
852
1028
|
relation.tuples = [];
|
|
853
1029
|
index.clear();
|
|
1030
|
+
relation.columns.length = 0;
|
|
1031
|
+
relation.combined.clear();
|
|
1032
|
+
forgetFacts(this, relationName, going);
|
|
1033
|
+
return going.size;
|
|
1034
|
+
}
|
|
1035
|
+
for (const tuple of gone) {
|
|
1036
|
+
index.prune(tuple);
|
|
1037
|
+
}
|
|
1038
|
+
const stored = gone.length * FEW_REMOVED < relation.tuples.length ? storedTuplesOf(relation, gone) : null;
|
|
1039
|
+
if (stored !== null) {
|
|
1040
|
+
removeTuples(relation, stored);
|
|
854
1041
|
} else {
|
|
855
1042
|
relation.tuples = relation.tuples.filter(
|
|
856
1043
|
(tuple) => index.find(tuple) !== void 0
|
|
857
1044
|
);
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
}
|
|
1045
|
+
relation.columns.length = 0;
|
|
1046
|
+
relation.combined.clear();
|
|
861
1047
|
}
|
|
862
|
-
relation.columns.length = 0;
|
|
863
1048
|
forgetFacts(this, relationName, going);
|
|
864
1049
|
return going.size;
|
|
865
1050
|
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Remove every fact in a relation, and return how many there were. The
|
|
1053
|
+
* database ends up as `retract` leaves it when handed every tuple, but
|
|
1054
|
+
* nothing looks a tuple up to remove it.
|
|
1055
|
+
*/
|
|
1056
|
+
retractAll(relationName) {
|
|
1057
|
+
const relation = this.store.get(relationName);
|
|
1058
|
+
if (relation === void 0 || relation.tuples.length === 0) {
|
|
1059
|
+
return 0;
|
|
1060
|
+
}
|
|
1061
|
+
const removed = relation.tuples.length;
|
|
1062
|
+
relation.tuples = [];
|
|
1063
|
+
relation.index.clear();
|
|
1064
|
+
relation.columns.length = 0;
|
|
1065
|
+
relation.combined.clear();
|
|
1066
|
+
forgetRelation(this, relationName);
|
|
1067
|
+
return removed;
|
|
1068
|
+
}
|
|
866
1069
|
size(relationName) {
|
|
867
1070
|
return this.store.get(relationName)?.tuples.length ?? 0;
|
|
868
1071
|
}
|
|
@@ -870,6 +1073,25 @@ var Database = class {
|
|
|
870
1073
|
return [...this.store.keys()];
|
|
871
1074
|
}
|
|
872
1075
|
};
|
|
1076
|
+
function rulesDeriving(rules, relations) {
|
|
1077
|
+
const wanted = new Set(relations);
|
|
1078
|
+
const pending = [...relations];
|
|
1079
|
+
while (pending.length > 0) {
|
|
1080
|
+
const relation = pending.pop();
|
|
1081
|
+
for (const r of rules) {
|
|
1082
|
+
if (r.head.relation !== relation) {
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
1085
|
+
for (const literal of r.body) {
|
|
1086
|
+
if (!wanted.has(literal.relation)) {
|
|
1087
|
+
wanted.add(literal.relation);
|
|
1088
|
+
pending.push(literal.relation);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
return rules.filter((r) => wanted.has(r.head.relation));
|
|
1094
|
+
}
|
|
873
1095
|
function stratify(rules) {
|
|
874
1096
|
const derived = new Set(rules.map((r) => r.head.relation));
|
|
875
1097
|
const stratum = /* @__PURE__ */ new Map();
|
|
@@ -969,13 +1191,16 @@ function headTuple(head, bindings) {
|
|
|
969
1191
|
});
|
|
970
1192
|
}
|
|
971
1193
|
var positiveLiterals = /* @__PURE__ */ new WeakMap();
|
|
972
|
-
function
|
|
1194
|
+
function positivesOf(r) {
|
|
973
1195
|
let positives = positiveLiterals.get(r);
|
|
974
1196
|
if (positives === void 0) {
|
|
975
1197
|
positives = r.body.map((literal, index) => literal.negated ? -1 : index).filter((index) => index !== -1);
|
|
976
1198
|
positiveLiterals.set(r, positives);
|
|
977
1199
|
}
|
|
978
|
-
return positives
|
|
1200
|
+
return positives;
|
|
1201
|
+
}
|
|
1202
|
+
function deltaLiteral(r, deltaIndex) {
|
|
1203
|
+
return positivesOf(r)[deltaIndex] ?? -1;
|
|
979
1204
|
}
|
|
980
1205
|
var allBound = (literal, bindings) => literal.terms.every(
|
|
981
1206
|
(term) => term.type === "constant" || boundValue(bindings, term.name) !== void 0
|
|
@@ -1018,58 +1243,139 @@ function evaluateRule(db, deltas, r, deltaIndex, budget) {
|
|
|
1018
1243
|
results.push(headTuple(r.head, bindings));
|
|
1019
1244
|
return;
|
|
1020
1245
|
}
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
for (let index = 0; index < body.length; index++) {
|
|
1024
|
-
if (taken & 1 << index) {
|
|
1025
|
-
continue;
|
|
1026
|
-
}
|
|
1027
|
-
const literal = body[index];
|
|
1028
|
-
if (literal.negated) {
|
|
1029
|
-
if (allBound(literal, bindings)) {
|
|
1030
|
-
if (!db.has(literal.relation, groundNegated(literal, bindings))) {
|
|
1031
|
-
step(taken | 1 << index, bindings);
|
|
1032
|
-
}
|
|
1033
|
-
return;
|
|
1034
|
-
}
|
|
1035
|
-
continue;
|
|
1036
|
-
}
|
|
1037
|
-
const source = narrowedSource(db, literal, bindings);
|
|
1038
|
-
if (source === null) {
|
|
1039
|
-
continue;
|
|
1040
|
-
}
|
|
1041
|
-
if (source.length === 0) {
|
|
1042
|
-
return;
|
|
1043
|
-
}
|
|
1044
|
-
if (narrowest === null || source.length < narrowest.length) {
|
|
1045
|
-
pick = index;
|
|
1046
|
-
narrowest = source;
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
if (narrowest !== null) {
|
|
1050
|
-
walk2(pick, narrowest, taken, bindings);
|
|
1246
|
+
const index = nextLiteral(db, body, taken, bindings);
|
|
1247
|
+
if (index === -1) {
|
|
1051
1248
|
return;
|
|
1052
1249
|
}
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
for (let index = 0; index < body.length; index++) {
|
|
1060
|
-
if (!(taken & 1 << index)) {
|
|
1061
|
-
groundNegated(body[index], bindings);
|
|
1250
|
+
const literal = body[index];
|
|
1251
|
+
if (literal.negated) {
|
|
1252
|
+
if (!db.has(literal.relation, groundNegated(literal, bindings))) {
|
|
1253
|
+
step(taken | 1 << index, bindings);
|
|
1062
1254
|
}
|
|
1255
|
+
return;
|
|
1063
1256
|
}
|
|
1257
|
+
walk2(index, nextSource, taken, bindings);
|
|
1064
1258
|
};
|
|
1065
1259
|
if (deltaAt === -1) {
|
|
1066
1260
|
step(0, null);
|
|
1067
|
-
|
|
1068
|
-
|
|
1261
|
+
return results;
|
|
1262
|
+
}
|
|
1263
|
+
const rows = deltaRows(deltas, body[deltaAt], budget);
|
|
1264
|
+
const start = smallerStart(db, body, deltaAt, rows);
|
|
1265
|
+
if (start === null) {
|
|
1266
|
+
walk2(deltaAt, rows, 0, null);
|
|
1267
|
+
return results;
|
|
1268
|
+
}
|
|
1269
|
+
const byValue = newFactsByColumn(rows, start.column, budget);
|
|
1270
|
+
const first = body[start.index];
|
|
1271
|
+
for (const tuple of start.source) {
|
|
1272
|
+
budget.examined++;
|
|
1273
|
+
if (budget.examined > budget.limit) {
|
|
1274
|
+
throw OUT_OF_BUDGET;
|
|
1275
|
+
}
|
|
1276
|
+
const bindings = unify(first, tuple, null);
|
|
1277
|
+
if (bindings === NO_MATCH) {
|
|
1278
|
+
continue;
|
|
1279
|
+
}
|
|
1280
|
+
const value = boundValue(bindings, start.variable);
|
|
1281
|
+
const matching = value === void 0 ? void 0 : byValue.get(value);
|
|
1282
|
+
if (matching !== void 0) {
|
|
1283
|
+
walk2(deltaAt, matching, 1 << start.index, bindings);
|
|
1284
|
+
}
|
|
1069
1285
|
}
|
|
1070
1286
|
return results;
|
|
1071
1287
|
}
|
|
1072
|
-
var
|
|
1288
|
+
var newFactIndexes = /* @__PURE__ */ new WeakMap();
|
|
1289
|
+
function smallerStart(db, body, deltaAt, rows) {
|
|
1290
|
+
const newFacts = body[deltaAt];
|
|
1291
|
+
const indexed = newFactIndexes.get(rows);
|
|
1292
|
+
let best = null;
|
|
1293
|
+
for (let index = 0; index < body.length; index++) {
|
|
1294
|
+
const literal = body[index];
|
|
1295
|
+
if (index === deltaAt || literal.negated) {
|
|
1296
|
+
continue;
|
|
1297
|
+
}
|
|
1298
|
+
const column = sharedColumn(newFacts, literal);
|
|
1299
|
+
if (column === -1) {
|
|
1300
|
+
continue;
|
|
1301
|
+
}
|
|
1302
|
+
const source = narrowedSource(db, literal, null) ?? db.facts(literal.relation);
|
|
1303
|
+
const worthIt = indexed?.has(column) ? source.length < rows.length : source.length * 2 < rows.length;
|
|
1304
|
+
if (worthIt && (best === null || source.length < best.source.length)) {
|
|
1305
|
+
best = {
|
|
1306
|
+
index,
|
|
1307
|
+
source,
|
|
1308
|
+
variable: newFacts.terms[column].name,
|
|
1309
|
+
column
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
return best;
|
|
1314
|
+
}
|
|
1315
|
+
function sharedColumn(from, other) {
|
|
1316
|
+
return from.terms.findIndex(
|
|
1317
|
+
(term) => term.type === "variable" && other.terms.some((t) => t.type === "variable" && t.name === term.name)
|
|
1318
|
+
);
|
|
1319
|
+
}
|
|
1320
|
+
function newFactsByColumn(rows, column, budget) {
|
|
1321
|
+
let byColumn = newFactIndexes.get(rows);
|
|
1322
|
+
if (byColumn === void 0) {
|
|
1323
|
+
byColumn = /* @__PURE__ */ new Map();
|
|
1324
|
+
newFactIndexes.set(rows, byColumn);
|
|
1325
|
+
}
|
|
1326
|
+
let byValue = byColumn.get(column);
|
|
1327
|
+
if (byValue === void 0) {
|
|
1328
|
+
byValue = /* @__PURE__ */ new Map();
|
|
1329
|
+
for (const tuple of rows) {
|
|
1330
|
+
const value = tuple[column];
|
|
1331
|
+
if (value !== void 0) {
|
|
1332
|
+
addToBucket(byValue, value, tuple);
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
budget.examined += rows.length;
|
|
1336
|
+
byColumn.set(column, byValue);
|
|
1337
|
+
}
|
|
1338
|
+
return byValue;
|
|
1339
|
+
}
|
|
1340
|
+
var nextSource = [];
|
|
1341
|
+
function nextLiteral(db, body, taken, bindings) {
|
|
1342
|
+
let pick = -1;
|
|
1343
|
+
let narrowest = null;
|
|
1344
|
+
for (let index = 0; index < body.length; index++) {
|
|
1345
|
+
if (taken & 1 << index) {
|
|
1346
|
+
continue;
|
|
1347
|
+
}
|
|
1348
|
+
const literal = body[index];
|
|
1349
|
+
if (literal.negated) {
|
|
1350
|
+
if (allBound(literal, bindings)) {
|
|
1351
|
+
return index;
|
|
1352
|
+
}
|
|
1353
|
+
continue;
|
|
1354
|
+
}
|
|
1355
|
+
const source = narrowedSource(db, literal, bindings);
|
|
1356
|
+
if (source === null) {
|
|
1357
|
+
continue;
|
|
1358
|
+
}
|
|
1359
|
+
if (source.length === 0) {
|
|
1360
|
+
return -1;
|
|
1361
|
+
}
|
|
1362
|
+
if (narrowest === null || source.length < narrowest.length) {
|
|
1363
|
+
pick = index;
|
|
1364
|
+
narrowest = source;
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
if (narrowest !== null) {
|
|
1368
|
+
nextSource = narrowedFurther(db, body[pick], bindings, narrowest);
|
|
1369
|
+
return nextSource.length === 0 ? -1 : pick;
|
|
1370
|
+
}
|
|
1371
|
+
for (let index = 0; index < body.length; index++) {
|
|
1372
|
+
if (!(taken & 1 << index) && !body[index].negated) {
|
|
1373
|
+
nextSource = db.facts(body[index].relation);
|
|
1374
|
+
return index;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
return body.findIndex((_, index) => !(taken & 1 << index));
|
|
1378
|
+
}
|
|
1073
1379
|
function narrowedSource(db, literal, bindings) {
|
|
1074
1380
|
const relation = db.relationOf(literal.relation);
|
|
1075
1381
|
let narrowest = null;
|
|
@@ -1089,14 +1395,119 @@ function narrowedSource(db, literal, bindings) {
|
|
|
1089
1395
|
}
|
|
1090
1396
|
return narrowest;
|
|
1091
1397
|
}
|
|
1398
|
+
function narrowedFurther(db, literal, bindings, source) {
|
|
1399
|
+
if (source.length <= 1) {
|
|
1400
|
+
return source;
|
|
1401
|
+
}
|
|
1402
|
+
let mask = 0;
|
|
1403
|
+
const values = [];
|
|
1404
|
+
for (let column = 0; column < literal.terms.length && column < 31; column++) {
|
|
1405
|
+
const term = literal.terms[column];
|
|
1406
|
+
const value = term.type === "constant" ? term.value : boundValue(bindings, term.name);
|
|
1407
|
+
if (value !== void 0) {
|
|
1408
|
+
mask |= 1 << column;
|
|
1409
|
+
values.push(value);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
const relation = db.relationOf(literal.relation);
|
|
1413
|
+
if (values.length < 2 || relation === void 0) {
|
|
1414
|
+
return source;
|
|
1415
|
+
}
|
|
1416
|
+
return combinedBucketIn(relation, mask, values);
|
|
1417
|
+
}
|
|
1418
|
+
var constantColumns = /* @__PURE__ */ new WeakMap();
|
|
1419
|
+
function constantColumnsOf(literal) {
|
|
1420
|
+
let known = constantColumns.get(literal);
|
|
1421
|
+
if (known === void 0) {
|
|
1422
|
+
const columns = [];
|
|
1423
|
+
const values = [];
|
|
1424
|
+
literal.terms.forEach((term, column) => {
|
|
1425
|
+
if (term.type === "constant") {
|
|
1426
|
+
columns.push(column);
|
|
1427
|
+
values.push(term.value);
|
|
1428
|
+
}
|
|
1429
|
+
});
|
|
1430
|
+
known = columns.length === 0 ? null : { columns, values };
|
|
1431
|
+
constantColumns.set(literal, known);
|
|
1432
|
+
}
|
|
1433
|
+
return known;
|
|
1434
|
+
}
|
|
1435
|
+
var deltaBuckets = /* @__PURE__ */ new WeakMap();
|
|
1436
|
+
var deltaBucketKey = (relation, column) => `${relation}\0${column}`;
|
|
1437
|
+
function hasNoNewRows(deltas, literal) {
|
|
1438
|
+
const rows = deltas.get(literal.relation) ?? [];
|
|
1439
|
+
const constants = constantColumnsOf(literal);
|
|
1440
|
+
if (rows.length === 0 || constants === null) {
|
|
1441
|
+
return rows.length === 0;
|
|
1442
|
+
}
|
|
1443
|
+
const buckets = deltaBuckets.get(deltas)?.get(deltaBucketKey(literal.relation, constants.columns[0]));
|
|
1444
|
+
return buckets !== void 0 && !buckets.has(constants.values[0]);
|
|
1445
|
+
}
|
|
1446
|
+
function deltaRows(deltas, literal, budget) {
|
|
1447
|
+
const rows = deltas.get(literal.relation) ?? [];
|
|
1448
|
+
const constants = constantColumnsOf(literal);
|
|
1449
|
+
if (constants === null || rows.length <= 1) {
|
|
1450
|
+
return rows;
|
|
1451
|
+
}
|
|
1452
|
+
let byColumn = deltaBuckets.get(deltas);
|
|
1453
|
+
if (byColumn === void 0) {
|
|
1454
|
+
byColumn = /* @__PURE__ */ new Map();
|
|
1455
|
+
deltaBuckets.set(deltas, byColumn);
|
|
1456
|
+
}
|
|
1457
|
+
const column = constants.columns[0];
|
|
1458
|
+
const key = deltaBucketKey(literal.relation, column);
|
|
1459
|
+
let buckets = byColumn.get(key);
|
|
1460
|
+
if (buckets === void 0) {
|
|
1461
|
+
buckets = /* @__PURE__ */ new Map();
|
|
1462
|
+
for (const tuple of rows) {
|
|
1463
|
+
const value = tuple[column];
|
|
1464
|
+
if (value !== void 0) {
|
|
1465
|
+
addToBucket(buckets, value, tuple);
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
budget.examined += rows.length;
|
|
1469
|
+
byColumn.set(key, buckets);
|
|
1470
|
+
}
|
|
1471
|
+
const bucket = buckets.get(constants.values[0]) ?? [];
|
|
1472
|
+
if (constants.columns.length === 1) {
|
|
1473
|
+
return bucket;
|
|
1474
|
+
}
|
|
1475
|
+
return bucket.filter(
|
|
1476
|
+
(tuple) => constants.columns.every(
|
|
1477
|
+
(at, i) => at === column || tuple[at] === constants.values[i]
|
|
1478
|
+
)
|
|
1479
|
+
);
|
|
1480
|
+
}
|
|
1092
1481
|
var EMPTY_TAGS = [];
|
|
1093
1482
|
function evaluateRuleTagged(db, deltas, r, deltaIndex, algebra, budget) {
|
|
1094
1483
|
const results = [];
|
|
1095
|
-
const
|
|
1096
|
-
const
|
|
1484
|
+
const body = r.body;
|
|
1485
|
+
const deltaAt = deltaLiteral(r, deltaIndex);
|
|
1486
|
+
const whole = 2 ** body.length - 1;
|
|
1487
|
+
const bodyTags = new Array(body.length);
|
|
1488
|
+
const bodyMatches = new Array(body.length);
|
|
1097
1489
|
const readsTags = algebra.ignoresBodyTags !== true;
|
|
1098
|
-
const
|
|
1099
|
-
|
|
1490
|
+
const walk2 = (index, source, taken, bindings) => {
|
|
1491
|
+
const literal = body[index];
|
|
1492
|
+
for (const tuple of source) {
|
|
1493
|
+
budget.examined++;
|
|
1494
|
+
const next = unify(literal, tuple, bindings);
|
|
1495
|
+
if (next !== NO_MATCH) {
|
|
1496
|
+
if (readsTags) {
|
|
1497
|
+
const stored = db.tagOf(literal.relation, tuple);
|
|
1498
|
+
bodyTags[index] = stored === void 0 ? algebra.asserted : stored;
|
|
1499
|
+
}
|
|
1500
|
+
bodyMatches[index] = {
|
|
1501
|
+
kind: "fact",
|
|
1502
|
+
relation: literal.relation,
|
|
1503
|
+
tuple
|
|
1504
|
+
};
|
|
1505
|
+
step(taken | 1 << index, next);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
};
|
|
1509
|
+
const step = (taken, bindings) => {
|
|
1510
|
+
if (taken === whole) {
|
|
1100
1511
|
results.push({
|
|
1101
1512
|
tuple: headTuple(r.head, bindings),
|
|
1102
1513
|
tag: algebra.combine(readsTags ? bodyTags.slice() : EMPTY_TAGS, {
|
|
@@ -1106,51 +1517,33 @@ function evaluateRuleTagged(db, deltas, r, deltaIndex, algebra, budget) {
|
|
|
1106
1517
|
});
|
|
1107
1518
|
return;
|
|
1108
1519
|
}
|
|
1109
|
-
const
|
|
1520
|
+
const index = nextLiteral(db, body, taken, bindings);
|
|
1521
|
+
if (index === -1) {
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
1524
|
+
const literal = body[index];
|
|
1110
1525
|
if (literal.negated) {
|
|
1111
1526
|
const grounded = groundNegated(literal, bindings);
|
|
1112
1527
|
if (!db.has(literal.relation, grounded)) {
|
|
1113
1528
|
if (readsTags) {
|
|
1114
|
-
bodyTags
|
|
1529
|
+
bodyTags[index] = algebra.absent;
|
|
1115
1530
|
}
|
|
1116
|
-
bodyMatches
|
|
1531
|
+
bodyMatches[index] = {
|
|
1117
1532
|
kind: "absence",
|
|
1118
1533
|
relation: literal.relation,
|
|
1119
1534
|
tuple: grounded
|
|
1120
|
-
}
|
|
1121
|
-
step(
|
|
1122
|
-
if (readsTags) {
|
|
1123
|
-
bodyTags.pop();
|
|
1124
|
-
}
|
|
1125
|
-
bodyMatches.pop();
|
|
1535
|
+
};
|
|
1536
|
+
step(taken | 1 << index, bindings);
|
|
1126
1537
|
}
|
|
1127
1538
|
return;
|
|
1128
1539
|
}
|
|
1129
|
-
|
|
1130
|
-
for (const tuple of source) {
|
|
1131
|
-
budget.examined++;
|
|
1132
|
-
const next = unify(literal, tuple, bindings);
|
|
1133
|
-
if (next !== NO_MATCH) {
|
|
1134
|
-
if (readsTags) {
|
|
1135
|
-
const stored = db.tagOf(literal.relation, tuple);
|
|
1136
|
-
bodyTags.push(
|
|
1137
|
-
stored === void 0 ? algebra.asserted : stored
|
|
1138
|
-
);
|
|
1139
|
-
}
|
|
1140
|
-
bodyMatches.push({
|
|
1141
|
-
kind: "fact",
|
|
1142
|
-
relation: literal.relation,
|
|
1143
|
-
tuple
|
|
1144
|
-
});
|
|
1145
|
-
step(literalIndex + 1, positiveIndex + 1, next);
|
|
1146
|
-
if (readsTags) {
|
|
1147
|
-
bodyTags.pop();
|
|
1148
|
-
}
|
|
1149
|
-
bodyMatches.pop();
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1540
|
+
walk2(index, nextSource, taken, bindings);
|
|
1152
1541
|
};
|
|
1153
|
-
|
|
1542
|
+
if (deltaAt === -1) {
|
|
1543
|
+
step(0, null);
|
|
1544
|
+
} else {
|
|
1545
|
+
walk2(deltaAt, deltaRows(deltas, body[deltaAt], budget), 0, null);
|
|
1546
|
+
}
|
|
1154
1547
|
return results;
|
|
1155
1548
|
}
|
|
1156
1549
|
var evaluated = /* @__PURE__ */ new WeakMap();
|
|
@@ -1190,6 +1583,16 @@ function forgetFacts(db, relation, keys) {
|
|
|
1190
1583
|
}
|
|
1191
1584
|
}
|
|
1192
1585
|
}
|
|
1586
|
+
function forgetRelation(db, relation) {
|
|
1587
|
+
const states = evaluated.get(db);
|
|
1588
|
+
if (states === void 0) {
|
|
1589
|
+
return;
|
|
1590
|
+
}
|
|
1591
|
+
for (const state of states.values()) {
|
|
1592
|
+
state.marks = null;
|
|
1593
|
+
state.derived.get(relation)?.clear();
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1193
1596
|
var usesNegation = (rules) => rules.some((r) => r.body.some((l) => l.negated));
|
|
1194
1597
|
function canResume(rules, state) {
|
|
1195
1598
|
return !usesNegation(rules) && state.marks !== null;
|
|
@@ -1222,6 +1625,8 @@ function evaluate(db, rules, algebra, budget) {
|
|
|
1222
1625
|
throw new Error("cannot evaluate with both a tag algebra and a row budget");
|
|
1223
1626
|
}
|
|
1224
1627
|
deriving.set(db, (deriving.get(db) ?? 0) + 1);
|
|
1628
|
+
const profiling = isProfiling();
|
|
1629
|
+
const startedAt = profiling ? performance.now() : 0;
|
|
1225
1630
|
try {
|
|
1226
1631
|
return runRules(db, rules, algebra, budget);
|
|
1227
1632
|
} finally {
|
|
@@ -1231,16 +1636,24 @@ function evaluate(db, rules, algebra, budget) {
|
|
|
1231
1636
|
} else {
|
|
1232
1637
|
deriving.set(db, depth);
|
|
1233
1638
|
}
|
|
1639
|
+
if (profiling) {
|
|
1640
|
+
chargeEngine("evaluate", performance.now() - startedAt);
|
|
1641
|
+
}
|
|
1234
1642
|
}
|
|
1235
1643
|
}
|
|
1236
1644
|
function clearRelations(db, rules, relations) {
|
|
1645
|
+
const profiling = isProfiling();
|
|
1646
|
+
const startedAt = profiling ? performance.now() : 0;
|
|
1237
1647
|
for (const relation of relations) {
|
|
1238
|
-
db.
|
|
1648
|
+
db.retractAll(relation);
|
|
1239
1649
|
}
|
|
1240
1650
|
const state = statesFor(db).get(signatureOf(rules));
|
|
1241
1651
|
if (state !== void 0) {
|
|
1242
1652
|
state.marks = currentMarks(db);
|
|
1243
1653
|
}
|
|
1654
|
+
if (profiling) {
|
|
1655
|
+
chargeEngine("clear", performance.now() - startedAt);
|
|
1656
|
+
}
|
|
1244
1657
|
}
|
|
1245
1658
|
var signatureOf = (rules) => shapeOf(rules).signature;
|
|
1246
1659
|
var shapes = /* @__PURE__ */ new WeakMap();
|
|
@@ -1254,7 +1667,7 @@ function shapeOf(rules) {
|
|
|
1254
1667
|
signature: JSON.stringify(rules),
|
|
1255
1668
|
name: [...derivedRelations].sort().join(", "),
|
|
1256
1669
|
derivedRelations,
|
|
1257
|
-
strata: stratify(rules)
|
|
1670
|
+
strata: stratify(rules).map(planStratum)
|
|
1258
1671
|
};
|
|
1259
1672
|
shapes.set(rules, shape);
|
|
1260
1673
|
return shape;
|
|
@@ -1313,7 +1726,6 @@ function runRules(db, rules, algebra, given) {
|
|
|
1313
1726
|
const marks = canResume(rules, state) ? state.marks : void 0;
|
|
1314
1727
|
const runStratum = (stratum) => {
|
|
1315
1728
|
let delta = /* @__PURE__ */ new Map();
|
|
1316
|
-
const derivedHere = new Set(stratum.map((r) => r.head.relation));
|
|
1317
1729
|
const record = (relation, tuple) => {
|
|
1318
1730
|
if (db.add(relation, tuple) === "added") {
|
|
1319
1731
|
addTo(delta, relation, tuple);
|
|
@@ -1367,17 +1779,18 @@ function runRules(db, rules, algebra, given) {
|
|
|
1367
1779
|
);
|
|
1368
1780
|
};
|
|
1369
1781
|
const applyDelta = (seed, derivedOnly) => {
|
|
1370
|
-
for (const
|
|
1782
|
+
for (const at of rulesReading(stratum, seed, derivedOnly)) {
|
|
1783
|
+
const r = stratum.rules[at];
|
|
1371
1784
|
if (!couldProduce(db, r)) {
|
|
1372
1785
|
continue;
|
|
1373
1786
|
}
|
|
1374
|
-
const positives = r
|
|
1787
|
+
const positives = positivesOf(r);
|
|
1375
1788
|
for (let i = 0; i < positives.length; i++) {
|
|
1376
|
-
const literal = positives[i];
|
|
1377
|
-
if (derivedOnly && !
|
|
1789
|
+
const literal = r.body[positives[i]];
|
|
1790
|
+
if (derivedOnly && !stratum.derived.has(literal.relation)) {
|
|
1378
1791
|
continue;
|
|
1379
1792
|
}
|
|
1380
|
-
if ((seed
|
|
1793
|
+
if (hasNoNewRows(seed, literal)) {
|
|
1381
1794
|
continue;
|
|
1382
1795
|
}
|
|
1383
1796
|
runOneRule(r, seed, i);
|
|
@@ -1385,7 +1798,7 @@ function runRules(db, rules, algebra, given) {
|
|
|
1385
1798
|
}
|
|
1386
1799
|
};
|
|
1387
1800
|
if (marks === void 0 || marks === null) {
|
|
1388
|
-
for (const r of stratum) {
|
|
1801
|
+
for (const r of stratum.rules) {
|
|
1389
1802
|
if (!couldProduce(db, r)) {
|
|
1390
1803
|
continue;
|
|
1391
1804
|
}
|
|
@@ -1472,6 +1885,7 @@ function addTo(buckets, relation, tuple) {
|
|
|
1472
1885
|
rowBudget,
|
|
1473
1886
|
rule,
|
|
1474
1887
|
ruleLabel,
|
|
1888
|
+
rulesDeriving,
|
|
1475
1889
|
stratify,
|
|
1476
1890
|
tupleKey,
|
|
1477
1891
|
tupleKeyParts,
|