@suss/datalog 0.33.1 → 0.34.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/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.js
CHANGED
|
@@ -257,7 +257,7 @@ function boundFirst(r, headAdornment, labels) {
|
|
|
257
257
|
}
|
|
258
258
|
});
|
|
259
259
|
const boundColumns = (literal) => literal.terms.filter(
|
|
260
|
-
(term, column) => !labels.get(literal.relation)?.has(column) &&
|
|
260
|
+
(term, column) => !labels.get(literal.relation)?.has(column) && term.type === "variable" && bound.has(term.name)
|
|
261
261
|
).length;
|
|
262
262
|
const remaining = [...r.body];
|
|
263
263
|
const body = [];
|
|
@@ -354,6 +354,11 @@ function chargeEvaluationRows(examined) {
|
|
|
354
354
|
}
|
|
355
355
|
}
|
|
356
356
|
}
|
|
357
|
+
function chargeEngine(part, ms) {
|
|
358
|
+
for (const collector of open) {
|
|
359
|
+
collector.engineMs[part] += ms;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
357
362
|
function chargeRound(ruleSet) {
|
|
358
363
|
for (const collector of open) {
|
|
359
364
|
collector.rounds += 1;
|
|
@@ -410,6 +415,7 @@ function openScope() {
|
|
|
410
415
|
startedAt: performance.now(),
|
|
411
416
|
rounds: 0,
|
|
412
417
|
evaluations: 0,
|
|
418
|
+
engineMs: { evaluate: 0, clear: 0 },
|
|
413
419
|
rules: /* @__PURE__ */ new Map(),
|
|
414
420
|
relations: /* @__PURE__ */ new Map(),
|
|
415
421
|
derivedRelations: /* @__PURE__ */ new Set(),
|
|
@@ -446,6 +452,7 @@ function summarise(mine) {
|
|
|
446
452
|
return {
|
|
447
453
|
wallMs: performance.now() - mine.startedAt,
|
|
448
454
|
datalogMs: totalMs(mine.rules.values()),
|
|
455
|
+
engineMs: { ...mine.engineMs },
|
|
449
456
|
rounds: mine.rounds,
|
|
450
457
|
evaluations: mine.evaluations,
|
|
451
458
|
examined: totalExamined(mine.rules.values()),
|
|
@@ -481,6 +488,9 @@ function formatProfile(profile) {
|
|
|
481
488
|
lines.push(
|
|
482
489
|
`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)`
|
|
483
490
|
);
|
|
491
|
+
lines.push(
|
|
492
|
+
` 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`
|
|
493
|
+
);
|
|
484
494
|
const { asked, abandoned, skipped } = profile.questions;
|
|
485
495
|
if (asked > 0) {
|
|
486
496
|
lines.push(
|
|
@@ -513,6 +523,102 @@ function formatProfile(profile) {
|
|
|
513
523
|
}
|
|
514
524
|
|
|
515
525
|
// src/relation.ts
|
|
526
|
+
var sameTuple = (a, b) => a.length === b.length && a.every((atom, at) => atom === b[at]);
|
|
527
|
+
function storedTuplesOf(relation, facts) {
|
|
528
|
+
const column = relation.columns.findIndex((buckets2) => buckets2 !== void 0);
|
|
529
|
+
const buckets = relation.columns[column];
|
|
530
|
+
if (buckets === void 0) {
|
|
531
|
+
return null;
|
|
532
|
+
}
|
|
533
|
+
const stored = facts.map(
|
|
534
|
+
(fact) => buckets.get(fact[column])?.find((tuple) => sameTuple(tuple, fact))
|
|
535
|
+
);
|
|
536
|
+
return stored.every((tuple) => tuple !== void 0) ? stored : null;
|
|
537
|
+
}
|
|
538
|
+
function removeFrom(buckets, key, tuple) {
|
|
539
|
+
const bucket = buckets.get(key);
|
|
540
|
+
const at = bucket?.indexOf(tuple) ?? -1;
|
|
541
|
+
if (bucket === void 0 || at === -1) {
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
bucket.splice(at, 1);
|
|
545
|
+
if (bucket.length === 0) {
|
|
546
|
+
buckets.delete(key);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
function removeTuples(relation, stored) {
|
|
550
|
+
const leaving = new Set(stored);
|
|
551
|
+
relation.tuples = relation.tuples.filter((tuple) => !leaving.has(tuple));
|
|
552
|
+
for (const tuple of stored) {
|
|
553
|
+
relation.columns.forEach((buckets, column) => {
|
|
554
|
+
if (buckets !== void 0) {
|
|
555
|
+
removeFrom(buckets, tuple[column], tuple);
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
for (const index of relation.combined.values()) {
|
|
559
|
+
let level = index.root;
|
|
560
|
+
const last = index.columns.length - 1;
|
|
561
|
+
for (let at = 0; at < last && level !== void 0; at++) {
|
|
562
|
+
level = level.get(tuple[index.columns[at]]);
|
|
563
|
+
}
|
|
564
|
+
if (level !== void 0) {
|
|
565
|
+
removeFrom(
|
|
566
|
+
level,
|
|
567
|
+
tuple[index.columns[last]],
|
|
568
|
+
tuple
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
function columnsIn(mask) {
|
|
575
|
+
const columns = [];
|
|
576
|
+
for (let column = 0; mask >> column !== 0; column++) {
|
|
577
|
+
if (mask & 1 << column) {
|
|
578
|
+
columns.push(column);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return columns;
|
|
582
|
+
}
|
|
583
|
+
function addToCombined(index, tuple) {
|
|
584
|
+
const last = index.columns.length - 1;
|
|
585
|
+
if (index.columns[last] >= tuple.length) {
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
let level = index.root;
|
|
589
|
+
for (let at = 0; at < last; at++) {
|
|
590
|
+
const value = tuple[index.columns[at]];
|
|
591
|
+
let next = level.get(value);
|
|
592
|
+
if (next === void 0) {
|
|
593
|
+
next = /* @__PURE__ */ new Map();
|
|
594
|
+
level.set(value, next);
|
|
595
|
+
}
|
|
596
|
+
level = next;
|
|
597
|
+
}
|
|
598
|
+
addToBucket(
|
|
599
|
+
level,
|
|
600
|
+
tuple[index.columns[last]],
|
|
601
|
+
tuple
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
function combinedBucketIn(relation, mask, values) {
|
|
605
|
+
let index = relation.combined.get(mask);
|
|
606
|
+
if (index === void 0) {
|
|
607
|
+
index = { columns: columnsIn(mask), root: /* @__PURE__ */ new Map() };
|
|
608
|
+
for (const tuple of relation.tuples) {
|
|
609
|
+
addToCombined(index, tuple);
|
|
610
|
+
}
|
|
611
|
+
relation.combined.set(mask, index);
|
|
612
|
+
}
|
|
613
|
+
let level = index.root;
|
|
614
|
+
for (const value of values) {
|
|
615
|
+
level = level.get(value);
|
|
616
|
+
if (level === void 0) {
|
|
617
|
+
return [];
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
return level;
|
|
621
|
+
}
|
|
516
622
|
function addToBucket(buckets, key, tuple) {
|
|
517
623
|
const bucket = buckets.get(key);
|
|
518
624
|
if (bucket === void 0) {
|
|
@@ -536,6 +642,72 @@ function bucketIn(relation, column, value) {
|
|
|
536
642
|
return buckets.get(value) ?? [];
|
|
537
643
|
}
|
|
538
644
|
|
|
645
|
+
// src/stratum.ts
|
|
646
|
+
function planStratum(rules) {
|
|
647
|
+
const derived = /* @__PURE__ */ new Set();
|
|
648
|
+
const readers = /* @__PURE__ */ new Map();
|
|
649
|
+
for (let at = 0; at < rules.length; at++) {
|
|
650
|
+
const r = rules[at];
|
|
651
|
+
derived.add(r.head.relation);
|
|
652
|
+
for (const literal of r.body) {
|
|
653
|
+
if (literal.negated) {
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
const positions = readers.get(literal.relation);
|
|
657
|
+
if (positions === void 0) {
|
|
658
|
+
readers.set(literal.relation, [at]);
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
if (positions[positions.length - 1] !== at) {
|
|
662
|
+
positions.push(at);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
return { rules, derived, readers, marked: new Uint8Array(rules.length) };
|
|
667
|
+
}
|
|
668
|
+
var NONE = [];
|
|
669
|
+
function rulesReading(stratum, seed, derivedOnly) {
|
|
670
|
+
const lists = [];
|
|
671
|
+
for (const [relation, tuples] of seed) {
|
|
672
|
+
if (tuples.length === 0) {
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
if (derivedOnly && !stratum.derived.has(relation)) {
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
const positions = stratum.readers.get(relation);
|
|
679
|
+
if (positions !== void 0) {
|
|
680
|
+
lists.push(positions);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
if (lists.length === 0) {
|
|
684
|
+
return NONE;
|
|
685
|
+
}
|
|
686
|
+
if (lists.length === 1) {
|
|
687
|
+
return lists[0];
|
|
688
|
+
}
|
|
689
|
+
return unionInOrder(lists, stratum.marked);
|
|
690
|
+
}
|
|
691
|
+
function unionInOrder(lists, marked) {
|
|
692
|
+
let first = marked.length;
|
|
693
|
+
let last = -1;
|
|
694
|
+
for (const positions of lists) {
|
|
695
|
+
for (const at of positions) {
|
|
696
|
+
marked[at] = 1;
|
|
697
|
+
}
|
|
698
|
+
first = Math.min(first, positions[0]);
|
|
699
|
+
last = Math.max(last, positions[positions.length - 1]);
|
|
700
|
+
}
|
|
701
|
+
const union = [];
|
|
702
|
+
for (let at = first; at <= last; at++) {
|
|
703
|
+
if (marked[at] === 1) {
|
|
704
|
+
marked[at] = 0;
|
|
705
|
+
union.push(at);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
return union;
|
|
709
|
+
}
|
|
710
|
+
|
|
539
711
|
// src/confidence.ts
|
|
540
712
|
var RANK = {
|
|
541
713
|
low: 0,
|
|
@@ -614,9 +786,7 @@ var Witness = class {
|
|
|
614
786
|
var witnesses = {
|
|
615
787
|
asserted: "asserted",
|
|
616
788
|
absent: "absent",
|
|
617
|
-
// A witness is built from the derivation alone
|
|
618
|
-
// the evaluator skip the body-tag lookups, which were most of the
|
|
619
|
-
// tagged path's cost on a large sweep.
|
|
789
|
+
// A witness is built from the derivation alone.
|
|
620
790
|
ignoresBodyTags: true,
|
|
621
791
|
combine: (_bodyTags, derivation) => new Witness(derivation.rule, derivation.body),
|
|
622
792
|
merge: (stored) => stored
|
|
@@ -678,6 +848,7 @@ var notLit = (relation, ...terms) => ({
|
|
|
678
848
|
var rule = (relation, terms, body, name) => name === void 0 ? { head: { relation, terms }, body } : { head: { relation, terms }, body, name };
|
|
679
849
|
var literalName = (literal) => literal.negated ? `!${literal.relation}` : literal.relation;
|
|
680
850
|
var ruleLabel = (r) => r.name ?? `${r.head.relation} :- ${r.body.map(literalName).join(", ")}`;
|
|
851
|
+
var FEW_REMOVED = 16;
|
|
681
852
|
var Database = class {
|
|
682
853
|
store = /* @__PURE__ */ new Map();
|
|
683
854
|
relation(name) {
|
|
@@ -688,15 +859,16 @@ var Database = class {
|
|
|
688
859
|
const created = {
|
|
689
860
|
index: new FactIndex(),
|
|
690
861
|
tuples: [],
|
|
691
|
-
columns: []
|
|
862
|
+
columns: [],
|
|
863
|
+
combined: /* @__PURE__ */ new Map()
|
|
692
864
|
};
|
|
693
865
|
this.store.set(name, created);
|
|
694
866
|
return created;
|
|
695
867
|
}
|
|
696
868
|
/**
|
|
697
869
|
* The key this relation gives a tuple, whether or not the fact is
|
|
698
|
-
* present. Evaluation keys its ledger of derived facts by it
|
|
699
|
-
*
|
|
870
|
+
* present. Evaluation keys its ledger of derived facts by it. Nothing
|
|
871
|
+
* outside this module accepts a key.
|
|
700
872
|
*/
|
|
701
873
|
keyFor(relationName, tuple) {
|
|
702
874
|
return this.relation(relationName).index.key(tuple);
|
|
@@ -736,6 +908,9 @@ var Database = class {
|
|
|
736
908
|
addToBucket(buckets, value, tuple);
|
|
737
909
|
}
|
|
738
910
|
}
|
|
911
|
+
for (const combined of relation.combined.values()) {
|
|
912
|
+
addToCombined(combined, tuple);
|
|
913
|
+
}
|
|
739
914
|
return "added";
|
|
740
915
|
}
|
|
741
916
|
/** The tag stored for this fact, or undefined when there is none. */
|
|
@@ -801,18 +976,45 @@ var Database = class {
|
|
|
801
976
|
if (index.size === 0) {
|
|
802
977
|
relation.tuples = [];
|
|
803
978
|
index.clear();
|
|
979
|
+
relation.columns.length = 0;
|
|
980
|
+
relation.combined.clear();
|
|
981
|
+
forgetFacts(this, relationName, going);
|
|
982
|
+
return going.size;
|
|
983
|
+
}
|
|
984
|
+
for (const tuple of gone) {
|
|
985
|
+
index.prune(tuple);
|
|
986
|
+
}
|
|
987
|
+
const stored = gone.length * FEW_REMOVED < relation.tuples.length ? storedTuplesOf(relation, gone) : null;
|
|
988
|
+
if (stored !== null) {
|
|
989
|
+
removeTuples(relation, stored);
|
|
804
990
|
} else {
|
|
805
991
|
relation.tuples = relation.tuples.filter(
|
|
806
992
|
(tuple) => index.find(tuple) !== void 0
|
|
807
993
|
);
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
}
|
|
994
|
+
relation.columns.length = 0;
|
|
995
|
+
relation.combined.clear();
|
|
811
996
|
}
|
|
812
|
-
relation.columns.length = 0;
|
|
813
997
|
forgetFacts(this, relationName, going);
|
|
814
998
|
return going.size;
|
|
815
999
|
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Remove every fact in a relation, and return how many there were. The
|
|
1002
|
+
* database ends up as `retract` leaves it when handed every tuple, but
|
|
1003
|
+
* nothing looks a tuple up to remove it.
|
|
1004
|
+
*/
|
|
1005
|
+
retractAll(relationName) {
|
|
1006
|
+
const relation = this.store.get(relationName);
|
|
1007
|
+
if (relation === void 0 || relation.tuples.length === 0) {
|
|
1008
|
+
return 0;
|
|
1009
|
+
}
|
|
1010
|
+
const removed = relation.tuples.length;
|
|
1011
|
+
relation.tuples = [];
|
|
1012
|
+
relation.index.clear();
|
|
1013
|
+
relation.columns.length = 0;
|
|
1014
|
+
relation.combined.clear();
|
|
1015
|
+
forgetRelation(this, relationName);
|
|
1016
|
+
return removed;
|
|
1017
|
+
}
|
|
816
1018
|
size(relationName) {
|
|
817
1019
|
return this.store.get(relationName)?.tuples.length ?? 0;
|
|
818
1020
|
}
|
|
@@ -820,6 +1022,25 @@ var Database = class {
|
|
|
820
1022
|
return [...this.store.keys()];
|
|
821
1023
|
}
|
|
822
1024
|
};
|
|
1025
|
+
function rulesDeriving(rules, relations) {
|
|
1026
|
+
const wanted = new Set(relations);
|
|
1027
|
+
const pending = [...relations];
|
|
1028
|
+
while (pending.length > 0) {
|
|
1029
|
+
const relation = pending.pop();
|
|
1030
|
+
for (const r of rules) {
|
|
1031
|
+
if (r.head.relation !== relation) {
|
|
1032
|
+
continue;
|
|
1033
|
+
}
|
|
1034
|
+
for (const literal of r.body) {
|
|
1035
|
+
if (!wanted.has(literal.relation)) {
|
|
1036
|
+
wanted.add(literal.relation);
|
|
1037
|
+
pending.push(literal.relation);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
return rules.filter((r) => wanted.has(r.head.relation));
|
|
1043
|
+
}
|
|
823
1044
|
function stratify(rules) {
|
|
824
1045
|
const derived = new Set(rules.map((r) => r.head.relation));
|
|
825
1046
|
const stratum = /* @__PURE__ */ new Map();
|
|
@@ -919,13 +1140,16 @@ function headTuple(head, bindings) {
|
|
|
919
1140
|
});
|
|
920
1141
|
}
|
|
921
1142
|
var positiveLiterals = /* @__PURE__ */ new WeakMap();
|
|
922
|
-
function
|
|
1143
|
+
function positivesOf(r) {
|
|
923
1144
|
let positives = positiveLiterals.get(r);
|
|
924
1145
|
if (positives === void 0) {
|
|
925
1146
|
positives = r.body.map((literal, index) => literal.negated ? -1 : index).filter((index) => index !== -1);
|
|
926
1147
|
positiveLiterals.set(r, positives);
|
|
927
1148
|
}
|
|
928
|
-
return positives
|
|
1149
|
+
return positives;
|
|
1150
|
+
}
|
|
1151
|
+
function deltaLiteral(r, deltaIndex) {
|
|
1152
|
+
return positivesOf(r)[deltaIndex] ?? -1;
|
|
929
1153
|
}
|
|
930
1154
|
var allBound = (literal, bindings) => literal.terms.every(
|
|
931
1155
|
(term) => term.type === "constant" || boundValue(bindings, term.name) !== void 0
|
|
@@ -968,58 +1192,139 @@ function evaluateRule(db, deltas, r, deltaIndex, budget) {
|
|
|
968
1192
|
results.push(headTuple(r.head, bindings));
|
|
969
1193
|
return;
|
|
970
1194
|
}
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
for (let index = 0; index < body.length; index++) {
|
|
974
|
-
if (taken & 1 << index) {
|
|
975
|
-
continue;
|
|
976
|
-
}
|
|
977
|
-
const literal = body[index];
|
|
978
|
-
if (literal.negated) {
|
|
979
|
-
if (allBound(literal, bindings)) {
|
|
980
|
-
if (!db.has(literal.relation, groundNegated(literal, bindings))) {
|
|
981
|
-
step(taken | 1 << index, bindings);
|
|
982
|
-
}
|
|
983
|
-
return;
|
|
984
|
-
}
|
|
985
|
-
continue;
|
|
986
|
-
}
|
|
987
|
-
const source = narrowedSource(db, literal, bindings);
|
|
988
|
-
if (source === null) {
|
|
989
|
-
continue;
|
|
990
|
-
}
|
|
991
|
-
if (source.length === 0) {
|
|
992
|
-
return;
|
|
993
|
-
}
|
|
994
|
-
if (narrowest === null || source.length < narrowest.length) {
|
|
995
|
-
pick = index;
|
|
996
|
-
narrowest = source;
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
if (narrowest !== null) {
|
|
1000
|
-
walk2(pick, narrowest, taken, bindings);
|
|
1195
|
+
const index = nextLiteral(db, body, taken, bindings);
|
|
1196
|
+
if (index === -1) {
|
|
1001
1197
|
return;
|
|
1002
1198
|
}
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
for (let index = 0; index < body.length; index++) {
|
|
1010
|
-
if (!(taken & 1 << index)) {
|
|
1011
|
-
groundNegated(body[index], bindings);
|
|
1199
|
+
const literal = body[index];
|
|
1200
|
+
if (literal.negated) {
|
|
1201
|
+
if (!db.has(literal.relation, groundNegated(literal, bindings))) {
|
|
1202
|
+
step(taken | 1 << index, bindings);
|
|
1012
1203
|
}
|
|
1204
|
+
return;
|
|
1013
1205
|
}
|
|
1206
|
+
walk2(index, nextSource, taken, bindings);
|
|
1014
1207
|
};
|
|
1015
1208
|
if (deltaAt === -1) {
|
|
1016
1209
|
step(0, null);
|
|
1017
|
-
|
|
1018
|
-
|
|
1210
|
+
return results;
|
|
1211
|
+
}
|
|
1212
|
+
const rows = deltaRows(deltas, body[deltaAt], budget);
|
|
1213
|
+
const start = smallerStart(db, body, deltaAt, rows);
|
|
1214
|
+
if (start === null) {
|
|
1215
|
+
walk2(deltaAt, rows, 0, null);
|
|
1216
|
+
return results;
|
|
1217
|
+
}
|
|
1218
|
+
const byValue = newFactsByColumn(rows, start.column, budget);
|
|
1219
|
+
const first = body[start.index];
|
|
1220
|
+
for (const tuple of start.source) {
|
|
1221
|
+
budget.examined++;
|
|
1222
|
+
if (budget.examined > budget.limit) {
|
|
1223
|
+
throw OUT_OF_BUDGET;
|
|
1224
|
+
}
|
|
1225
|
+
const bindings = unify(first, tuple, null);
|
|
1226
|
+
if (bindings === NO_MATCH) {
|
|
1227
|
+
continue;
|
|
1228
|
+
}
|
|
1229
|
+
const value = boundValue(bindings, start.variable);
|
|
1230
|
+
const matching = value === void 0 ? void 0 : byValue.get(value);
|
|
1231
|
+
if (matching !== void 0) {
|
|
1232
|
+
walk2(deltaAt, matching, 1 << start.index, bindings);
|
|
1233
|
+
}
|
|
1019
1234
|
}
|
|
1020
1235
|
return results;
|
|
1021
1236
|
}
|
|
1022
|
-
var
|
|
1237
|
+
var newFactIndexes = /* @__PURE__ */ new WeakMap();
|
|
1238
|
+
function smallerStart(db, body, deltaAt, rows) {
|
|
1239
|
+
const newFacts = body[deltaAt];
|
|
1240
|
+
const indexed = newFactIndexes.get(rows);
|
|
1241
|
+
let best = null;
|
|
1242
|
+
for (let index = 0; index < body.length; index++) {
|
|
1243
|
+
const literal = body[index];
|
|
1244
|
+
if (index === deltaAt || literal.negated) {
|
|
1245
|
+
continue;
|
|
1246
|
+
}
|
|
1247
|
+
const column = sharedColumn(newFacts, literal);
|
|
1248
|
+
if (column === -1) {
|
|
1249
|
+
continue;
|
|
1250
|
+
}
|
|
1251
|
+
const source = narrowedSource(db, literal, null) ?? db.facts(literal.relation);
|
|
1252
|
+
const worthIt = indexed?.has(column) ? source.length < rows.length : source.length * 2 < rows.length;
|
|
1253
|
+
if (worthIt && (best === null || source.length < best.source.length)) {
|
|
1254
|
+
best = {
|
|
1255
|
+
index,
|
|
1256
|
+
source,
|
|
1257
|
+
variable: newFacts.terms[column].name,
|
|
1258
|
+
column
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
return best;
|
|
1263
|
+
}
|
|
1264
|
+
function sharedColumn(from, other) {
|
|
1265
|
+
return from.terms.findIndex(
|
|
1266
|
+
(term) => term.type === "variable" && other.terms.some((t) => t.type === "variable" && t.name === term.name)
|
|
1267
|
+
);
|
|
1268
|
+
}
|
|
1269
|
+
function newFactsByColumn(rows, column, budget) {
|
|
1270
|
+
let byColumn = newFactIndexes.get(rows);
|
|
1271
|
+
if (byColumn === void 0) {
|
|
1272
|
+
byColumn = /* @__PURE__ */ new Map();
|
|
1273
|
+
newFactIndexes.set(rows, byColumn);
|
|
1274
|
+
}
|
|
1275
|
+
let byValue = byColumn.get(column);
|
|
1276
|
+
if (byValue === void 0) {
|
|
1277
|
+
byValue = /* @__PURE__ */ new Map();
|
|
1278
|
+
for (const tuple of rows) {
|
|
1279
|
+
const value = tuple[column];
|
|
1280
|
+
if (value !== void 0) {
|
|
1281
|
+
addToBucket(byValue, value, tuple);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
budget.examined += rows.length;
|
|
1285
|
+
byColumn.set(column, byValue);
|
|
1286
|
+
}
|
|
1287
|
+
return byValue;
|
|
1288
|
+
}
|
|
1289
|
+
var nextSource = [];
|
|
1290
|
+
function nextLiteral(db, body, taken, bindings) {
|
|
1291
|
+
let pick = -1;
|
|
1292
|
+
let narrowest = null;
|
|
1293
|
+
for (let index = 0; index < body.length; index++) {
|
|
1294
|
+
if (taken & 1 << index) {
|
|
1295
|
+
continue;
|
|
1296
|
+
}
|
|
1297
|
+
const literal = body[index];
|
|
1298
|
+
if (literal.negated) {
|
|
1299
|
+
if (allBound(literal, bindings)) {
|
|
1300
|
+
return index;
|
|
1301
|
+
}
|
|
1302
|
+
continue;
|
|
1303
|
+
}
|
|
1304
|
+
const source = narrowedSource(db, literal, bindings);
|
|
1305
|
+
if (source === null) {
|
|
1306
|
+
continue;
|
|
1307
|
+
}
|
|
1308
|
+
if (source.length === 0) {
|
|
1309
|
+
return -1;
|
|
1310
|
+
}
|
|
1311
|
+
if (narrowest === null || source.length < narrowest.length) {
|
|
1312
|
+
pick = index;
|
|
1313
|
+
narrowest = source;
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
if (narrowest !== null) {
|
|
1317
|
+
nextSource = narrowedFurther(db, body[pick], bindings, narrowest);
|
|
1318
|
+
return nextSource.length === 0 ? -1 : pick;
|
|
1319
|
+
}
|
|
1320
|
+
for (let index = 0; index < body.length; index++) {
|
|
1321
|
+
if (!(taken & 1 << index) && !body[index].negated) {
|
|
1322
|
+
nextSource = db.facts(body[index].relation);
|
|
1323
|
+
return index;
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
return body.findIndex((_, index) => !(taken & 1 << index));
|
|
1327
|
+
}
|
|
1023
1328
|
function narrowedSource(db, literal, bindings) {
|
|
1024
1329
|
const relation = db.relationOf(literal.relation);
|
|
1025
1330
|
let narrowest = null;
|
|
@@ -1039,14 +1344,119 @@ function narrowedSource(db, literal, bindings) {
|
|
|
1039
1344
|
}
|
|
1040
1345
|
return narrowest;
|
|
1041
1346
|
}
|
|
1347
|
+
function narrowedFurther(db, literal, bindings, source) {
|
|
1348
|
+
if (source.length <= 1) {
|
|
1349
|
+
return source;
|
|
1350
|
+
}
|
|
1351
|
+
let mask = 0;
|
|
1352
|
+
const values = [];
|
|
1353
|
+
for (let column = 0; column < literal.terms.length && column < 31; column++) {
|
|
1354
|
+
const term = literal.terms[column];
|
|
1355
|
+
const value = term.type === "constant" ? term.value : boundValue(bindings, term.name);
|
|
1356
|
+
if (value !== void 0) {
|
|
1357
|
+
mask |= 1 << column;
|
|
1358
|
+
values.push(value);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
const relation = db.relationOf(literal.relation);
|
|
1362
|
+
if (values.length < 2 || relation === void 0) {
|
|
1363
|
+
return source;
|
|
1364
|
+
}
|
|
1365
|
+
return combinedBucketIn(relation, mask, values);
|
|
1366
|
+
}
|
|
1367
|
+
var constantColumns = /* @__PURE__ */ new WeakMap();
|
|
1368
|
+
function constantColumnsOf(literal) {
|
|
1369
|
+
let known = constantColumns.get(literal);
|
|
1370
|
+
if (known === void 0) {
|
|
1371
|
+
const columns = [];
|
|
1372
|
+
const values = [];
|
|
1373
|
+
literal.terms.forEach((term, column) => {
|
|
1374
|
+
if (term.type === "constant") {
|
|
1375
|
+
columns.push(column);
|
|
1376
|
+
values.push(term.value);
|
|
1377
|
+
}
|
|
1378
|
+
});
|
|
1379
|
+
known = columns.length === 0 ? null : { columns, values };
|
|
1380
|
+
constantColumns.set(literal, known);
|
|
1381
|
+
}
|
|
1382
|
+
return known;
|
|
1383
|
+
}
|
|
1384
|
+
var deltaBuckets = /* @__PURE__ */ new WeakMap();
|
|
1385
|
+
var deltaBucketKey = (relation, column) => `${relation}\0${column}`;
|
|
1386
|
+
function hasNoNewRows(deltas, literal) {
|
|
1387
|
+
const rows = deltas.get(literal.relation) ?? [];
|
|
1388
|
+
const constants = constantColumnsOf(literal);
|
|
1389
|
+
if (rows.length === 0 || constants === null) {
|
|
1390
|
+
return rows.length === 0;
|
|
1391
|
+
}
|
|
1392
|
+
const buckets = deltaBuckets.get(deltas)?.get(deltaBucketKey(literal.relation, constants.columns[0]));
|
|
1393
|
+
return buckets !== void 0 && !buckets.has(constants.values[0]);
|
|
1394
|
+
}
|
|
1395
|
+
function deltaRows(deltas, literal, budget) {
|
|
1396
|
+
const rows = deltas.get(literal.relation) ?? [];
|
|
1397
|
+
const constants = constantColumnsOf(literal);
|
|
1398
|
+
if (constants === null || rows.length <= 1) {
|
|
1399
|
+
return rows;
|
|
1400
|
+
}
|
|
1401
|
+
let byColumn = deltaBuckets.get(deltas);
|
|
1402
|
+
if (byColumn === void 0) {
|
|
1403
|
+
byColumn = /* @__PURE__ */ new Map();
|
|
1404
|
+
deltaBuckets.set(deltas, byColumn);
|
|
1405
|
+
}
|
|
1406
|
+
const column = constants.columns[0];
|
|
1407
|
+
const key = deltaBucketKey(literal.relation, column);
|
|
1408
|
+
let buckets = byColumn.get(key);
|
|
1409
|
+
if (buckets === void 0) {
|
|
1410
|
+
buckets = /* @__PURE__ */ new Map();
|
|
1411
|
+
for (const tuple of rows) {
|
|
1412
|
+
const value = tuple[column];
|
|
1413
|
+
if (value !== void 0) {
|
|
1414
|
+
addToBucket(buckets, value, tuple);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
budget.examined += rows.length;
|
|
1418
|
+
byColumn.set(key, buckets);
|
|
1419
|
+
}
|
|
1420
|
+
const bucket = buckets.get(constants.values[0]) ?? [];
|
|
1421
|
+
if (constants.columns.length === 1) {
|
|
1422
|
+
return bucket;
|
|
1423
|
+
}
|
|
1424
|
+
return bucket.filter(
|
|
1425
|
+
(tuple) => constants.columns.every(
|
|
1426
|
+
(at, i) => at === column || tuple[at] === constants.values[i]
|
|
1427
|
+
)
|
|
1428
|
+
);
|
|
1429
|
+
}
|
|
1042
1430
|
var EMPTY_TAGS = [];
|
|
1043
1431
|
function evaluateRuleTagged(db, deltas, r, deltaIndex, algebra, budget) {
|
|
1044
1432
|
const results = [];
|
|
1045
|
-
const
|
|
1046
|
-
const
|
|
1433
|
+
const body = r.body;
|
|
1434
|
+
const deltaAt = deltaLiteral(r, deltaIndex);
|
|
1435
|
+
const whole = 2 ** body.length - 1;
|
|
1436
|
+
const bodyTags = new Array(body.length);
|
|
1437
|
+
const bodyMatches = new Array(body.length);
|
|
1047
1438
|
const readsTags = algebra.ignoresBodyTags !== true;
|
|
1048
|
-
const
|
|
1049
|
-
|
|
1439
|
+
const walk2 = (index, source, taken, bindings) => {
|
|
1440
|
+
const literal = body[index];
|
|
1441
|
+
for (const tuple of source) {
|
|
1442
|
+
budget.examined++;
|
|
1443
|
+
const next = unify(literal, tuple, bindings);
|
|
1444
|
+
if (next !== NO_MATCH) {
|
|
1445
|
+
if (readsTags) {
|
|
1446
|
+
const stored = db.tagOf(literal.relation, tuple);
|
|
1447
|
+
bodyTags[index] = stored === void 0 ? algebra.asserted : stored;
|
|
1448
|
+
}
|
|
1449
|
+
bodyMatches[index] = {
|
|
1450
|
+
kind: "fact",
|
|
1451
|
+
relation: literal.relation,
|
|
1452
|
+
tuple
|
|
1453
|
+
};
|
|
1454
|
+
step(taken | 1 << index, next);
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
};
|
|
1458
|
+
const step = (taken, bindings) => {
|
|
1459
|
+
if (taken === whole) {
|
|
1050
1460
|
results.push({
|
|
1051
1461
|
tuple: headTuple(r.head, bindings),
|
|
1052
1462
|
tag: algebra.combine(readsTags ? bodyTags.slice() : EMPTY_TAGS, {
|
|
@@ -1056,51 +1466,33 @@ function evaluateRuleTagged(db, deltas, r, deltaIndex, algebra, budget) {
|
|
|
1056
1466
|
});
|
|
1057
1467
|
return;
|
|
1058
1468
|
}
|
|
1059
|
-
const
|
|
1469
|
+
const index = nextLiteral(db, body, taken, bindings);
|
|
1470
|
+
if (index === -1) {
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
const literal = body[index];
|
|
1060
1474
|
if (literal.negated) {
|
|
1061
1475
|
const grounded = groundNegated(literal, bindings);
|
|
1062
1476
|
if (!db.has(literal.relation, grounded)) {
|
|
1063
1477
|
if (readsTags) {
|
|
1064
|
-
bodyTags
|
|
1478
|
+
bodyTags[index] = algebra.absent;
|
|
1065
1479
|
}
|
|
1066
|
-
bodyMatches
|
|
1480
|
+
bodyMatches[index] = {
|
|
1067
1481
|
kind: "absence",
|
|
1068
1482
|
relation: literal.relation,
|
|
1069
1483
|
tuple: grounded
|
|
1070
|
-
}
|
|
1071
|
-
step(
|
|
1072
|
-
if (readsTags) {
|
|
1073
|
-
bodyTags.pop();
|
|
1074
|
-
}
|
|
1075
|
-
bodyMatches.pop();
|
|
1484
|
+
};
|
|
1485
|
+
step(taken | 1 << index, bindings);
|
|
1076
1486
|
}
|
|
1077
1487
|
return;
|
|
1078
1488
|
}
|
|
1079
|
-
|
|
1080
|
-
for (const tuple of source) {
|
|
1081
|
-
budget.examined++;
|
|
1082
|
-
const next = unify(literal, tuple, bindings);
|
|
1083
|
-
if (next !== NO_MATCH) {
|
|
1084
|
-
if (readsTags) {
|
|
1085
|
-
const stored = db.tagOf(literal.relation, tuple);
|
|
1086
|
-
bodyTags.push(
|
|
1087
|
-
stored === void 0 ? algebra.asserted : stored
|
|
1088
|
-
);
|
|
1089
|
-
}
|
|
1090
|
-
bodyMatches.push({
|
|
1091
|
-
kind: "fact",
|
|
1092
|
-
relation: literal.relation,
|
|
1093
|
-
tuple
|
|
1094
|
-
});
|
|
1095
|
-
step(literalIndex + 1, positiveIndex + 1, next);
|
|
1096
|
-
if (readsTags) {
|
|
1097
|
-
bodyTags.pop();
|
|
1098
|
-
}
|
|
1099
|
-
bodyMatches.pop();
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1489
|
+
walk2(index, nextSource, taken, bindings);
|
|
1102
1490
|
};
|
|
1103
|
-
|
|
1491
|
+
if (deltaAt === -1) {
|
|
1492
|
+
step(0, null);
|
|
1493
|
+
} else {
|
|
1494
|
+
walk2(deltaAt, deltaRows(deltas, body[deltaAt], budget), 0, null);
|
|
1495
|
+
}
|
|
1104
1496
|
return results;
|
|
1105
1497
|
}
|
|
1106
1498
|
var evaluated = /* @__PURE__ */ new WeakMap();
|
|
@@ -1140,6 +1532,16 @@ function forgetFacts(db, relation, keys) {
|
|
|
1140
1532
|
}
|
|
1141
1533
|
}
|
|
1142
1534
|
}
|
|
1535
|
+
function forgetRelation(db, relation) {
|
|
1536
|
+
const states = evaluated.get(db);
|
|
1537
|
+
if (states === void 0) {
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
for (const state of states.values()) {
|
|
1541
|
+
state.marks = null;
|
|
1542
|
+
state.derived.get(relation)?.clear();
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1143
1545
|
var usesNegation = (rules) => rules.some((r) => r.body.some((l) => l.negated));
|
|
1144
1546
|
function canResume(rules, state) {
|
|
1145
1547
|
return !usesNegation(rules) && state.marks !== null;
|
|
@@ -1172,6 +1574,8 @@ function evaluate(db, rules, algebra, budget) {
|
|
|
1172
1574
|
throw new Error("cannot evaluate with both a tag algebra and a row budget");
|
|
1173
1575
|
}
|
|
1174
1576
|
deriving.set(db, (deriving.get(db) ?? 0) + 1);
|
|
1577
|
+
const profiling = isProfiling();
|
|
1578
|
+
const startedAt = profiling ? performance.now() : 0;
|
|
1175
1579
|
try {
|
|
1176
1580
|
return runRules(db, rules, algebra, budget);
|
|
1177
1581
|
} finally {
|
|
@@ -1181,16 +1585,24 @@ function evaluate(db, rules, algebra, budget) {
|
|
|
1181
1585
|
} else {
|
|
1182
1586
|
deriving.set(db, depth);
|
|
1183
1587
|
}
|
|
1588
|
+
if (profiling) {
|
|
1589
|
+
chargeEngine("evaluate", performance.now() - startedAt);
|
|
1590
|
+
}
|
|
1184
1591
|
}
|
|
1185
1592
|
}
|
|
1186
1593
|
function clearRelations(db, rules, relations) {
|
|
1594
|
+
const profiling = isProfiling();
|
|
1595
|
+
const startedAt = profiling ? performance.now() : 0;
|
|
1187
1596
|
for (const relation of relations) {
|
|
1188
|
-
db.
|
|
1597
|
+
db.retractAll(relation);
|
|
1189
1598
|
}
|
|
1190
1599
|
const state = statesFor(db).get(signatureOf(rules));
|
|
1191
1600
|
if (state !== void 0) {
|
|
1192
1601
|
state.marks = currentMarks(db);
|
|
1193
1602
|
}
|
|
1603
|
+
if (profiling) {
|
|
1604
|
+
chargeEngine("clear", performance.now() - startedAt);
|
|
1605
|
+
}
|
|
1194
1606
|
}
|
|
1195
1607
|
var signatureOf = (rules) => shapeOf(rules).signature;
|
|
1196
1608
|
var shapes = /* @__PURE__ */ new WeakMap();
|
|
@@ -1204,7 +1616,7 @@ function shapeOf(rules) {
|
|
|
1204
1616
|
signature: JSON.stringify(rules),
|
|
1205
1617
|
name: [...derivedRelations].sort().join(", "),
|
|
1206
1618
|
derivedRelations,
|
|
1207
|
-
strata: stratify(rules)
|
|
1619
|
+
strata: stratify(rules).map(planStratum)
|
|
1208
1620
|
};
|
|
1209
1621
|
shapes.set(rules, shape);
|
|
1210
1622
|
return shape;
|
|
@@ -1263,7 +1675,6 @@ function runRules(db, rules, algebra, given) {
|
|
|
1263
1675
|
const marks = canResume(rules, state) ? state.marks : void 0;
|
|
1264
1676
|
const runStratum = (stratum) => {
|
|
1265
1677
|
let delta = /* @__PURE__ */ new Map();
|
|
1266
|
-
const derivedHere = new Set(stratum.map((r) => r.head.relation));
|
|
1267
1678
|
const record = (relation, tuple) => {
|
|
1268
1679
|
if (db.add(relation, tuple) === "added") {
|
|
1269
1680
|
addTo(delta, relation, tuple);
|
|
@@ -1317,17 +1728,18 @@ function runRules(db, rules, algebra, given) {
|
|
|
1317
1728
|
);
|
|
1318
1729
|
};
|
|
1319
1730
|
const applyDelta = (seed, derivedOnly) => {
|
|
1320
|
-
for (const
|
|
1731
|
+
for (const at of rulesReading(stratum, seed, derivedOnly)) {
|
|
1732
|
+
const r = stratum.rules[at];
|
|
1321
1733
|
if (!couldProduce(db, r)) {
|
|
1322
1734
|
continue;
|
|
1323
1735
|
}
|
|
1324
|
-
const positives = r
|
|
1736
|
+
const positives = positivesOf(r);
|
|
1325
1737
|
for (let i = 0; i < positives.length; i++) {
|
|
1326
|
-
const literal = positives[i];
|
|
1327
|
-
if (derivedOnly && !
|
|
1738
|
+
const literal = r.body[positives[i]];
|
|
1739
|
+
if (derivedOnly && !stratum.derived.has(literal.relation)) {
|
|
1328
1740
|
continue;
|
|
1329
1741
|
}
|
|
1330
|
-
if ((seed
|
|
1742
|
+
if (hasNoNewRows(seed, literal)) {
|
|
1331
1743
|
continue;
|
|
1332
1744
|
}
|
|
1333
1745
|
runOneRule(r, seed, i);
|
|
@@ -1335,7 +1747,7 @@ function runRules(db, rules, algebra, given) {
|
|
|
1335
1747
|
}
|
|
1336
1748
|
};
|
|
1337
1749
|
if (marks === void 0 || marks === null) {
|
|
1338
|
-
for (const r of stratum) {
|
|
1750
|
+
for (const r of stratum.rules) {
|
|
1339
1751
|
if (!couldProduce(db, r)) {
|
|
1340
1752
|
continue;
|
|
1341
1753
|
}
|
|
@@ -1421,6 +1833,7 @@ export {
|
|
|
1421
1833
|
rowBudget,
|
|
1422
1834
|
rule,
|
|
1423
1835
|
ruleLabel,
|
|
1836
|
+
rulesDeriving,
|
|
1424
1837
|
stratify,
|
|
1425
1838
|
tupleKey,
|
|
1426
1839
|
tupleKeyParts,
|