@suss/datalog 0.31.2 → 0.33.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 +1 -1
- package/dist/index.cjs +200 -93
- package/dist/index.d.cts +83 -0
- package/dist/index.d.ts +83 -0
- package/dist/index.js +200 -93
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ second language adapter only has to emit the same facts.
|
|
|
48
48
|
## More
|
|
49
49
|
|
|
50
50
|
- [How the engine evaluates](./DESIGN.md)
|
|
51
|
-
- [Documentation](https://
|
|
51
|
+
- [Documentation](https://suss.sh/)
|
|
52
52
|
- [Source and issues](https://github.com/nimbuscloud-ai/suss)
|
|
53
53
|
|
|
54
54
|

|
package/dist/index.cjs
CHANGED
|
@@ -48,6 +48,99 @@ __export(index_exports, {
|
|
|
48
48
|
});
|
|
49
49
|
module.exports = __toCommonJS(index_exports);
|
|
50
50
|
|
|
51
|
+
// src/factIndex.ts
|
|
52
|
+
var emptyNode = () => ({
|
|
53
|
+
present: false,
|
|
54
|
+
tag: void 0,
|
|
55
|
+
next: void 0
|
|
56
|
+
});
|
|
57
|
+
var FactIndex = class {
|
|
58
|
+
/** How many facts are present. */
|
|
59
|
+
size = 0;
|
|
60
|
+
root = emptyNode();
|
|
61
|
+
/** This tuple's key here, whether or not the fact is present. */
|
|
62
|
+
key(tuple) {
|
|
63
|
+
let node = this.root;
|
|
64
|
+
for (let i = 0; i < tuple.length; i++) {
|
|
65
|
+
let next = node.next;
|
|
66
|
+
if (next === void 0) {
|
|
67
|
+
next = /* @__PURE__ */ new Map();
|
|
68
|
+
node.next = next;
|
|
69
|
+
}
|
|
70
|
+
const atom = tuple[i];
|
|
71
|
+
let child = next.get(atom);
|
|
72
|
+
if (child === void 0) {
|
|
73
|
+
child = emptyNode();
|
|
74
|
+
next.set(atom, child);
|
|
75
|
+
}
|
|
76
|
+
node = child;
|
|
77
|
+
}
|
|
78
|
+
return node;
|
|
79
|
+
}
|
|
80
|
+
/** The key of a fact this relation has, or undefined when it has none. */
|
|
81
|
+
find(tuple) {
|
|
82
|
+
let node = this.root;
|
|
83
|
+
for (let i = 0; i < tuple.length; i++) {
|
|
84
|
+
const child = node.next?.get(tuple[i]);
|
|
85
|
+
if (child === void 0) {
|
|
86
|
+
return void 0;
|
|
87
|
+
}
|
|
88
|
+
node = child;
|
|
89
|
+
}
|
|
90
|
+
return node.present ? node : void 0;
|
|
91
|
+
}
|
|
92
|
+
has(key) {
|
|
93
|
+
return key.present;
|
|
94
|
+
}
|
|
95
|
+
/** Record the fact, under the tag the caller gave it. */
|
|
96
|
+
put(key, tag) {
|
|
97
|
+
key.present = true;
|
|
98
|
+
key.tag = tag;
|
|
99
|
+
this.size += 1;
|
|
100
|
+
}
|
|
101
|
+
tagOf(key) {
|
|
102
|
+
return key.tag;
|
|
103
|
+
}
|
|
104
|
+
setTag(key, tag) {
|
|
105
|
+
key.tag = tag;
|
|
106
|
+
}
|
|
107
|
+
/** Take the fact away, and the tag with it. */
|
|
108
|
+
remove(key) {
|
|
109
|
+
key.present = false;
|
|
110
|
+
key.tag = void 0;
|
|
111
|
+
this.size -= 1;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Drop what is left of a removed tuple's path, up to the first node
|
|
115
|
+
* another fact still needs. Without this a relation that is retracted
|
|
116
|
+
* from over and over keeps a node per tuple it ever had.
|
|
117
|
+
*/
|
|
118
|
+
prune(tuple) {
|
|
119
|
+
const path = [this.root];
|
|
120
|
+
let node = this.root;
|
|
121
|
+
for (let i = 0; i < tuple.length; i++) {
|
|
122
|
+
const child = node.next?.get(tuple[i]);
|
|
123
|
+
if (child === void 0) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
node = child;
|
|
127
|
+
path.push(child);
|
|
128
|
+
}
|
|
129
|
+
for (let i = path.length - 1; i > 0; i--) {
|
|
130
|
+
const child = path[i];
|
|
131
|
+
if (child.present || (child.next?.size ?? 0) > 0) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
path[i - 1].next?.delete(tuple[i - 1]);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/** Drop every fact, and whatever was kept in order to find them. */
|
|
138
|
+
clear() {
|
|
139
|
+
this.root = emptyNode();
|
|
140
|
+
this.size = 0;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
51
144
|
// src/onDemand.ts
|
|
52
145
|
var adornmentKey = (adornment) => adornment.map((bound) => bound ? "b" : "f").join("");
|
|
53
146
|
var anyBound = (adornment) => adornment.some(Boolean);
|
|
@@ -469,6 +562,58 @@ function formatProfile(profile) {
|
|
|
469
562
|
return lines.join("\n");
|
|
470
563
|
}
|
|
471
564
|
|
|
565
|
+
// src/relation.ts
|
|
566
|
+
function addToBucket(buckets, key, tuple) {
|
|
567
|
+
const bucket = buckets.get(key);
|
|
568
|
+
if (bucket === void 0) {
|
|
569
|
+
buckets.set(key, [tuple]);
|
|
570
|
+
} else {
|
|
571
|
+
bucket.push(tuple);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
function bucketIn(relation, column, value) {
|
|
575
|
+
let buckets = relation.columns[column];
|
|
576
|
+
if (buckets === void 0) {
|
|
577
|
+
buckets = /* @__PURE__ */ new Map();
|
|
578
|
+
for (const tuple of relation.tuples) {
|
|
579
|
+
const at = tuple[column];
|
|
580
|
+
if (at !== void 0) {
|
|
581
|
+
addToBucket(buckets, at, tuple);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
relation.columns[column] = buckets;
|
|
585
|
+
}
|
|
586
|
+
return buckets.get(value) ?? [];
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// src/confidence.ts
|
|
590
|
+
var RANK = {
|
|
591
|
+
low: 0,
|
|
592
|
+
medium: 1,
|
|
593
|
+
high: 2
|
|
594
|
+
};
|
|
595
|
+
function confidenceWith(levelOf) {
|
|
596
|
+
return {
|
|
597
|
+
asserted: "high",
|
|
598
|
+
absent: "high",
|
|
599
|
+
combine(bodyTags, derivation) {
|
|
600
|
+
let lowest = levelOf(derivation.rule) ?? "high";
|
|
601
|
+
for (const tag of bodyTags) {
|
|
602
|
+
if (RANK[tag] < RANK[lowest]) {
|
|
603
|
+
lowest = tag;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
return lowest;
|
|
607
|
+
},
|
|
608
|
+
merge(stored, incoming) {
|
|
609
|
+
return RANK[incoming] > RANK[stored] ? incoming : stored;
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
var confidence = confidenceWith(
|
|
614
|
+
() => void 0
|
|
615
|
+
);
|
|
616
|
+
|
|
472
617
|
// src/tupleKey.ts
|
|
473
618
|
var atomKey = (a) => typeof a === "number" ? `n${a}:` : `s${a.length}:${a}`;
|
|
474
619
|
var tupleKey = (parts) => {
|
|
@@ -507,34 +652,6 @@ function tupleKeyParts(key) {
|
|
|
507
652
|
return parts;
|
|
508
653
|
}
|
|
509
654
|
|
|
510
|
-
// src/confidence.ts
|
|
511
|
-
var RANK = {
|
|
512
|
-
low: 0,
|
|
513
|
-
medium: 1,
|
|
514
|
-
high: 2
|
|
515
|
-
};
|
|
516
|
-
function confidenceWith(levelOf) {
|
|
517
|
-
return {
|
|
518
|
-
asserted: "high",
|
|
519
|
-
absent: "high",
|
|
520
|
-
combine(bodyTags, derivation) {
|
|
521
|
-
let lowest = levelOf(derivation.rule) ?? "high";
|
|
522
|
-
for (const tag of bodyTags) {
|
|
523
|
-
if (RANK[tag] < RANK[lowest]) {
|
|
524
|
-
lowest = tag;
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
return lowest;
|
|
528
|
-
},
|
|
529
|
-
merge(stored, incoming) {
|
|
530
|
-
return RANK[incoming] > RANK[stored] ? incoming : stored;
|
|
531
|
-
}
|
|
532
|
-
};
|
|
533
|
-
}
|
|
534
|
-
var confidence = confidenceWith(
|
|
535
|
-
() => void 0
|
|
536
|
-
);
|
|
537
|
-
|
|
538
655
|
// src/witness.ts
|
|
539
656
|
var Witness = class {
|
|
540
657
|
constructor(rule2, body) {
|
|
@@ -611,30 +728,6 @@ var notLit = (relation, ...terms) => ({
|
|
|
611
728
|
var rule = (relation, terms, body, name) => name === void 0 ? { head: { relation, terms }, body } : { head: { relation, terms }, body, name };
|
|
612
729
|
var literalName = (literal) => literal.negated ? `!${literal.relation}` : literal.relation;
|
|
613
730
|
var ruleLabel = (r) => r.name ?? `${r.head.relation} :- ${r.body.map(literalName).join(", ")}`;
|
|
614
|
-
var keys = /* @__PURE__ */ new WeakMap();
|
|
615
|
-
var keyOf2 = (tuple) => {
|
|
616
|
-
const known = keys.get(tuple);
|
|
617
|
-
if (known !== void 0) {
|
|
618
|
-
return known;
|
|
619
|
-
}
|
|
620
|
-
const key = tupleKey(tuple);
|
|
621
|
-
keys.set(tuple, key);
|
|
622
|
-
return key;
|
|
623
|
-
};
|
|
624
|
-
function tagSlots(relation) {
|
|
625
|
-
if (relation.tags === void 0) {
|
|
626
|
-
relation.tags = /* @__PURE__ */ new Map();
|
|
627
|
-
}
|
|
628
|
-
return relation.tags;
|
|
629
|
-
}
|
|
630
|
-
function addToBucket(index, key, tuple) {
|
|
631
|
-
const bucket = index.get(key);
|
|
632
|
-
if (bucket === void 0) {
|
|
633
|
-
index.set(key, [tuple]);
|
|
634
|
-
} else {
|
|
635
|
-
bucket.push(tuple);
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
731
|
var Database = class {
|
|
639
732
|
store = /* @__PURE__ */ new Map();
|
|
640
733
|
relation(name) {
|
|
@@ -643,14 +736,21 @@ var Database = class {
|
|
|
643
736
|
return existing;
|
|
644
737
|
}
|
|
645
738
|
const created = {
|
|
646
|
-
|
|
739
|
+
index: new FactIndex(),
|
|
647
740
|
tuples: [],
|
|
648
|
-
|
|
649
|
-
indexes: /* @__PURE__ */ new Map()
|
|
741
|
+
columns: []
|
|
650
742
|
};
|
|
651
743
|
this.store.set(name, created);
|
|
652
744
|
return created;
|
|
653
745
|
}
|
|
746
|
+
/**
|
|
747
|
+
* The key this relation gives a tuple, whether or not the fact is
|
|
748
|
+
* present. Evaluation keys its ledger of derived facts by it; a
|
|
749
|
+
* caller outside this module has nowhere to spend one.
|
|
750
|
+
*/
|
|
751
|
+
keyFor(relationName, tuple) {
|
|
752
|
+
return this.relation(relationName).index.key(tuple);
|
|
753
|
+
}
|
|
654
754
|
/**
|
|
655
755
|
* Add a fact, or merge a tag into one already present. A new fact
|
|
656
756
|
* stores `tag` when one is given. For a fact already present,
|
|
@@ -660,14 +760,15 @@ var Database = class {
|
|
|
660
760
|
*/
|
|
661
761
|
add(relationName, tuple, tag, merge) {
|
|
662
762
|
const relation = this.relation(relationName);
|
|
663
|
-
const
|
|
664
|
-
|
|
763
|
+
const index = relation.index;
|
|
764
|
+
const key = index.key(tuple);
|
|
765
|
+
if (index.has(key)) {
|
|
665
766
|
let outcome = "unchanged";
|
|
666
767
|
if (merge !== void 0) {
|
|
667
|
-
const stored =
|
|
768
|
+
const stored = index.tagOf(key);
|
|
668
769
|
const merged = merge(stored, tag);
|
|
669
770
|
if (merged !== stored) {
|
|
670
|
-
|
|
771
|
+
index.setTag(key, merged);
|
|
671
772
|
outcome = "improved";
|
|
672
773
|
}
|
|
673
774
|
}
|
|
@@ -676,25 +777,28 @@ var Database = class {
|
|
|
676
777
|
}
|
|
677
778
|
return outcome;
|
|
678
779
|
}
|
|
679
|
-
|
|
780
|
+
index.put(key, tag);
|
|
680
781
|
relation.tuples.push(tuple);
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
}
|
|
684
|
-
for (const [column, index] of relation.indexes) {
|
|
782
|
+
for (let column = 0; column < relation.columns.length; column++) {
|
|
783
|
+
const buckets = relation.columns[column];
|
|
685
784
|
const value = tuple[column];
|
|
686
|
-
if (value !== void 0) {
|
|
687
|
-
addToBucket(
|
|
785
|
+
if (buckets !== void 0 && value !== void 0) {
|
|
786
|
+
addToBucket(buckets, value, tuple);
|
|
688
787
|
}
|
|
689
788
|
}
|
|
690
789
|
return "added";
|
|
691
790
|
}
|
|
692
791
|
/** The tag stored for this fact, or undefined when there is none. */
|
|
693
792
|
tagOf(relationName, tuple) {
|
|
694
|
-
|
|
793
|
+
const relation = this.store.get(relationName);
|
|
794
|
+
if (relation === void 0) {
|
|
795
|
+
return void 0;
|
|
796
|
+
}
|
|
797
|
+
const key = relation.index.find(tuple);
|
|
798
|
+
return key === void 0 ? void 0 : relation.index.tagOf(key);
|
|
695
799
|
}
|
|
696
800
|
has(relationName, tuple) {
|
|
697
|
-
return this.store.get(relationName)?.
|
|
801
|
+
return this.store.get(relationName)?.index.find(tuple) !== void 0;
|
|
698
802
|
}
|
|
699
803
|
facts(relationName) {
|
|
700
804
|
return this.store.get(relationName)?.tuples ?? [];
|
|
@@ -710,21 +814,11 @@ var Database = class {
|
|
|
710
814
|
*/
|
|
711
815
|
lookup(relationName, column, value) {
|
|
712
816
|
const relation = this.store.get(relationName);
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
index = /* @__PURE__ */ new Map();
|
|
719
|
-
for (const tuple of relation.tuples) {
|
|
720
|
-
const at = tuple[column];
|
|
721
|
-
if (at !== void 0) {
|
|
722
|
-
addToBucket(index, at, tuple);
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
relation.indexes.set(column, index);
|
|
726
|
-
}
|
|
727
|
-
return index.get(value) ?? [];
|
|
817
|
+
return relation === void 0 ? [] : bucketIn(relation, column, value);
|
|
818
|
+
}
|
|
819
|
+
/** @internal The join resolves a literal's relation once, then narrows on each fixed column. */
|
|
820
|
+
relationOf(relationName) {
|
|
821
|
+
return this.store.get(relationName);
|
|
728
822
|
}
|
|
729
823
|
/**
|
|
730
824
|
* Remove facts from a relation. Returns how many were actually there.
|
|
@@ -738,22 +832,34 @@ var Database = class {
|
|
|
738
832
|
if (relation === void 0) {
|
|
739
833
|
return 0;
|
|
740
834
|
}
|
|
835
|
+
const index = relation.index;
|
|
741
836
|
const going = /* @__PURE__ */ new Set();
|
|
837
|
+
const gone = [];
|
|
742
838
|
for (const tuple of tuples) {
|
|
743
|
-
const key =
|
|
744
|
-
if (
|
|
839
|
+
const key = index.find(tuple);
|
|
840
|
+
if (key !== void 0 && !going.has(key)) {
|
|
745
841
|
going.add(key);
|
|
842
|
+
gone.push(tuple);
|
|
746
843
|
}
|
|
747
844
|
}
|
|
748
845
|
if (going.size === 0) {
|
|
749
846
|
return 0;
|
|
750
847
|
}
|
|
751
848
|
for (const key of going) {
|
|
752
|
-
|
|
753
|
-
|
|
849
|
+
index.remove(key);
|
|
850
|
+
}
|
|
851
|
+
if (index.size === 0) {
|
|
852
|
+
relation.tuples = [];
|
|
853
|
+
index.clear();
|
|
854
|
+
} else {
|
|
855
|
+
relation.tuples = relation.tuples.filter(
|
|
856
|
+
(tuple) => index.find(tuple) !== void 0
|
|
857
|
+
);
|
|
858
|
+
for (const tuple of gone) {
|
|
859
|
+
index.prune(tuple);
|
|
860
|
+
}
|
|
754
861
|
}
|
|
755
|
-
relation.
|
|
756
|
-
relation.indexes.clear();
|
|
862
|
+
relation.columns.length = 0;
|
|
757
863
|
forgetFacts(this, relationName, going);
|
|
758
864
|
return going.size;
|
|
759
865
|
}
|
|
@@ -965,6 +1071,7 @@ function evaluateRule(db, deltas, r, deltaIndex, budget) {
|
|
|
965
1071
|
}
|
|
966
1072
|
var boundSource = (db, literal, bindings) => narrowedSource(db, literal, bindings) ?? db.facts(literal.relation);
|
|
967
1073
|
function narrowedSource(db, literal, bindings) {
|
|
1074
|
+
const relation = db.relationOf(literal.relation);
|
|
968
1075
|
let narrowest = null;
|
|
969
1076
|
for (let column = 0; column < literal.terms.length; column++) {
|
|
970
1077
|
const term = literal.terms[column];
|
|
@@ -972,7 +1079,7 @@ function narrowedSource(db, literal, bindings) {
|
|
|
972
1079
|
if (value === void 0) {
|
|
973
1080
|
continue;
|
|
974
1081
|
}
|
|
975
|
-
const bucket =
|
|
1082
|
+
const bucket = relation === void 0 ? [] : bucketIn(relation, column, value);
|
|
976
1083
|
if (bucket.length === 0) {
|
|
977
1084
|
return bucket;
|
|
978
1085
|
}
|
|
@@ -1068,7 +1175,7 @@ function claimFact(db, relation, key) {
|
|
|
1068
1175
|
state.derived.get(relation)?.delete(key);
|
|
1069
1176
|
}
|
|
1070
1177
|
}
|
|
1071
|
-
function forgetFacts(db, relation,
|
|
1178
|
+
function forgetFacts(db, relation, keys) {
|
|
1072
1179
|
const states = evaluated.get(db);
|
|
1073
1180
|
if (states === void 0) {
|
|
1074
1181
|
return;
|
|
@@ -1077,7 +1184,7 @@ function forgetFacts(db, relation, keys2) {
|
|
|
1077
1184
|
state.marks = null;
|
|
1078
1185
|
const ledger = state.derived.get(relation);
|
|
1079
1186
|
if (ledger !== void 0) {
|
|
1080
|
-
for (const key of
|
|
1187
|
+
for (const key of keys) {
|
|
1081
1188
|
ledger.delete(key);
|
|
1082
1189
|
}
|
|
1083
1190
|
}
|
|
@@ -1199,7 +1306,7 @@ function runRules(db, rules, algebra, given) {
|
|
|
1199
1306
|
ledger = /* @__PURE__ */ new Map();
|
|
1200
1307
|
derived.set(relation, ledger);
|
|
1201
1308
|
}
|
|
1202
|
-
ledger.set(
|
|
1309
|
+
ledger.set(db.keyFor(relation, tuple), tuple);
|
|
1203
1310
|
addedHere?.push([relation, tuple]);
|
|
1204
1311
|
};
|
|
1205
1312
|
const mergeStored = algebra === void 0 ? void 0 : storedMergeFor(algebra);
|
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which tuples a relation has, and what identifies one of them to
|
|
3
|
+
* everything else. The tag stored for a fact, each rule set's ledger of
|
|
4
|
+
* what it derived, and a caller's claim on a fact it asserted all point
|
|
5
|
+
* at a fact through the key this hands out.
|
|
6
|
+
*
|
|
7
|
+
* The join asks for that key again on every candidate the rules
|
|
8
|
+
* consider, so what the key costs to work out sets the cost of
|
|
9
|
+
* evaluation. A relation finds a fact by walking a trie of its tuple's
|
|
10
|
+
* atoms, one map hop per column, and the node the walk lands on is the
|
|
11
|
+
* fact itself. Nothing is built to identify a tuple, and a membership
|
|
12
|
+
* test that fails stops at the first column that misses. DESIGN.md says
|
|
13
|
+
* why the walk is over the atoms rather than over interned integers.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* What identifies one fact inside one relation. Opaque: a key means
|
|
18
|
+
* nothing to the relation that did not hand it out, and nothing outside
|
|
19
|
+
* this module reads its fields.
|
|
20
|
+
*/
|
|
21
|
+
interface FactKey {
|
|
22
|
+
present: boolean;
|
|
23
|
+
tag: unknown;
|
|
24
|
+
next: Map<Atom, FactKey> | undefined;
|
|
25
|
+
}
|
|
26
|
+
/** The facts one relation has, and the tag stored against each. */
|
|
27
|
+
declare class FactIndex {
|
|
28
|
+
/** How many facts are present. */
|
|
29
|
+
size: number;
|
|
30
|
+
private root;
|
|
31
|
+
/** This tuple's key here, whether or not the fact is present. */
|
|
32
|
+
key(tuple: Tuple): FactKey;
|
|
33
|
+
/** The key of a fact this relation has, or undefined when it has none. */
|
|
34
|
+
find(tuple: Tuple): FactKey | undefined;
|
|
35
|
+
has(key: FactKey): boolean;
|
|
36
|
+
/** Record the fact, under the tag the caller gave it. */
|
|
37
|
+
put(key: FactKey, tag: unknown): void;
|
|
38
|
+
tagOf(key: FactKey): unknown;
|
|
39
|
+
setTag(key: FactKey, tag: unknown): void;
|
|
40
|
+
/** Take the fact away, and the tag with it. */
|
|
41
|
+
remove(key: FactKey): void;
|
|
42
|
+
/**
|
|
43
|
+
* Drop what is left of a removed tuple's path, up to the first node
|
|
44
|
+
* another fact still needs. Without this a relation that is retracted
|
|
45
|
+
* from over and over keeps a node per tuple it ever had.
|
|
46
|
+
*/
|
|
47
|
+
prune(tuple: Tuple): void;
|
|
48
|
+
/** Drop every fact, and whatever was kept in order to find them. */
|
|
49
|
+
clear(): void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* One relation's facts: which tuples are there, and an index per column
|
|
54
|
+
* so a join that has a value for a column does not scan them all.
|
|
55
|
+
*
|
|
56
|
+
* A column is indexed the first time something asks for it and stays up
|
|
57
|
+
* to date after that, so a relation nobody joins on that way never gets
|
|
58
|
+
* an index. The join resolves the relation once per literal and then
|
|
59
|
+
* narrows on each of that literal's fixed columns, which is why finding
|
|
60
|
+
* a bucket is a function here rather than a method on the database.
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
interface Relation {
|
|
64
|
+
/** Which tuples are here, and what identifies each of them. */
|
|
65
|
+
index: FactIndex;
|
|
66
|
+
tuples: Tuple[];
|
|
67
|
+
/**
|
|
68
|
+
* Per column position, the tuples under each value at that position.
|
|
69
|
+
* The value is the atom itself: a Map already tells 1 from "1", so
|
|
70
|
+
* encoding it first would build a string out of every node id on every
|
|
71
|
+
* lookup and buy nothing.
|
|
72
|
+
*/
|
|
73
|
+
columns: (Map<Atom, Tuple[]> | undefined)[];
|
|
74
|
+
}
|
|
75
|
+
|
|
1
76
|
/**
|
|
2
77
|
* Confidence levels as a tag algebra: how sure the run is of each
|
|
3
78
|
* derived fact, given how sure it is of the facts and rules behind it.
|
|
@@ -354,6 +429,12 @@ type AddOutcome = "added" | "improved" | "unchanged";
|
|
|
354
429
|
declare class Database {
|
|
355
430
|
private readonly store;
|
|
356
431
|
private relation;
|
|
432
|
+
/**
|
|
433
|
+
* The key this relation gives a tuple, whether or not the fact is
|
|
434
|
+
* present. Evaluation keys its ledger of derived facts by it; a
|
|
435
|
+
* caller outside this module has nowhere to spend one.
|
|
436
|
+
*/
|
|
437
|
+
keyFor(relationName: string, tuple: Tuple): FactKey;
|
|
357
438
|
/**
|
|
358
439
|
* Add a fact, or merge a tag into one already present. A new fact
|
|
359
440
|
* stores `tag` when one is given. For a fact already present,
|
|
@@ -376,6 +457,8 @@ declare class Database {
|
|
|
376
457
|
* gets an index.
|
|
377
458
|
*/
|
|
378
459
|
lookup(relationName: string, column: number, value: Atom): readonly Tuple[];
|
|
460
|
+
/** @internal The join resolves a literal's relation once, then narrows on each fixed column. */
|
|
461
|
+
relationOf(relationName: string): Relation | undefined;
|
|
379
462
|
/**
|
|
380
463
|
* Remove facts from a relation. Returns how many were actually there.
|
|
381
464
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which tuples a relation has, and what identifies one of them to
|
|
3
|
+
* everything else. The tag stored for a fact, each rule set's ledger of
|
|
4
|
+
* what it derived, and a caller's claim on a fact it asserted all point
|
|
5
|
+
* at a fact through the key this hands out.
|
|
6
|
+
*
|
|
7
|
+
* The join asks for that key again on every candidate the rules
|
|
8
|
+
* consider, so what the key costs to work out sets the cost of
|
|
9
|
+
* evaluation. A relation finds a fact by walking a trie of its tuple's
|
|
10
|
+
* atoms, one map hop per column, and the node the walk lands on is the
|
|
11
|
+
* fact itself. Nothing is built to identify a tuple, and a membership
|
|
12
|
+
* test that fails stops at the first column that misses. DESIGN.md says
|
|
13
|
+
* why the walk is over the atoms rather than over interned integers.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* What identifies one fact inside one relation. Opaque: a key means
|
|
18
|
+
* nothing to the relation that did not hand it out, and nothing outside
|
|
19
|
+
* this module reads its fields.
|
|
20
|
+
*/
|
|
21
|
+
interface FactKey {
|
|
22
|
+
present: boolean;
|
|
23
|
+
tag: unknown;
|
|
24
|
+
next: Map<Atom, FactKey> | undefined;
|
|
25
|
+
}
|
|
26
|
+
/** The facts one relation has, and the tag stored against each. */
|
|
27
|
+
declare class FactIndex {
|
|
28
|
+
/** How many facts are present. */
|
|
29
|
+
size: number;
|
|
30
|
+
private root;
|
|
31
|
+
/** This tuple's key here, whether or not the fact is present. */
|
|
32
|
+
key(tuple: Tuple): FactKey;
|
|
33
|
+
/** The key of a fact this relation has, or undefined when it has none. */
|
|
34
|
+
find(tuple: Tuple): FactKey | undefined;
|
|
35
|
+
has(key: FactKey): boolean;
|
|
36
|
+
/** Record the fact, under the tag the caller gave it. */
|
|
37
|
+
put(key: FactKey, tag: unknown): void;
|
|
38
|
+
tagOf(key: FactKey): unknown;
|
|
39
|
+
setTag(key: FactKey, tag: unknown): void;
|
|
40
|
+
/** Take the fact away, and the tag with it. */
|
|
41
|
+
remove(key: FactKey): void;
|
|
42
|
+
/**
|
|
43
|
+
* Drop what is left of a removed tuple's path, up to the first node
|
|
44
|
+
* another fact still needs. Without this a relation that is retracted
|
|
45
|
+
* from over and over keeps a node per tuple it ever had.
|
|
46
|
+
*/
|
|
47
|
+
prune(tuple: Tuple): void;
|
|
48
|
+
/** Drop every fact, and whatever was kept in order to find them. */
|
|
49
|
+
clear(): void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* One relation's facts: which tuples are there, and an index per column
|
|
54
|
+
* so a join that has a value for a column does not scan them all.
|
|
55
|
+
*
|
|
56
|
+
* A column is indexed the first time something asks for it and stays up
|
|
57
|
+
* to date after that, so a relation nobody joins on that way never gets
|
|
58
|
+
* an index. The join resolves the relation once per literal and then
|
|
59
|
+
* narrows on each of that literal's fixed columns, which is why finding
|
|
60
|
+
* a bucket is a function here rather than a method on the database.
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
interface Relation {
|
|
64
|
+
/** Which tuples are here, and what identifies each of them. */
|
|
65
|
+
index: FactIndex;
|
|
66
|
+
tuples: Tuple[];
|
|
67
|
+
/**
|
|
68
|
+
* Per column position, the tuples under each value at that position.
|
|
69
|
+
* The value is the atom itself: a Map already tells 1 from "1", so
|
|
70
|
+
* encoding it first would build a string out of every node id on every
|
|
71
|
+
* lookup and buy nothing.
|
|
72
|
+
*/
|
|
73
|
+
columns: (Map<Atom, Tuple[]> | undefined)[];
|
|
74
|
+
}
|
|
75
|
+
|
|
1
76
|
/**
|
|
2
77
|
* Confidence levels as a tag algebra: how sure the run is of each
|
|
3
78
|
* derived fact, given how sure it is of the facts and rules behind it.
|
|
@@ -354,6 +429,12 @@ type AddOutcome = "added" | "improved" | "unchanged";
|
|
|
354
429
|
declare class Database {
|
|
355
430
|
private readonly store;
|
|
356
431
|
private relation;
|
|
432
|
+
/**
|
|
433
|
+
* The key this relation gives a tuple, whether or not the fact is
|
|
434
|
+
* present. Evaluation keys its ledger of derived facts by it; a
|
|
435
|
+
* caller outside this module has nowhere to spend one.
|
|
436
|
+
*/
|
|
437
|
+
keyFor(relationName: string, tuple: Tuple): FactKey;
|
|
357
438
|
/**
|
|
358
439
|
* Add a fact, or merge a tag into one already present. A new fact
|
|
359
440
|
* stores `tag` when one is given. For a fact already present,
|
|
@@ -376,6 +457,8 @@ declare class Database {
|
|
|
376
457
|
* gets an index.
|
|
377
458
|
*/
|
|
378
459
|
lookup(relationName: string, column: number, value: Atom): readonly Tuple[];
|
|
460
|
+
/** @internal The join resolves a literal's relation once, then narrows on each fixed column. */
|
|
461
|
+
relationOf(relationName: string): Relation | undefined;
|
|
379
462
|
/**
|
|
380
463
|
* Remove facts from a relation. Returns how many were actually there.
|
|
381
464
|
*
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,96 @@
|
|
|
1
|
+
// src/factIndex.ts
|
|
2
|
+
var emptyNode = () => ({
|
|
3
|
+
present: false,
|
|
4
|
+
tag: void 0,
|
|
5
|
+
next: void 0
|
|
6
|
+
});
|
|
7
|
+
var FactIndex = class {
|
|
8
|
+
/** How many facts are present. */
|
|
9
|
+
size = 0;
|
|
10
|
+
root = emptyNode();
|
|
11
|
+
/** This tuple's key here, whether or not the fact is present. */
|
|
12
|
+
key(tuple) {
|
|
13
|
+
let node = this.root;
|
|
14
|
+
for (let i = 0; i < tuple.length; i++) {
|
|
15
|
+
let next = node.next;
|
|
16
|
+
if (next === void 0) {
|
|
17
|
+
next = /* @__PURE__ */ new Map();
|
|
18
|
+
node.next = next;
|
|
19
|
+
}
|
|
20
|
+
const atom = tuple[i];
|
|
21
|
+
let child = next.get(atom);
|
|
22
|
+
if (child === void 0) {
|
|
23
|
+
child = emptyNode();
|
|
24
|
+
next.set(atom, child);
|
|
25
|
+
}
|
|
26
|
+
node = child;
|
|
27
|
+
}
|
|
28
|
+
return node;
|
|
29
|
+
}
|
|
30
|
+
/** The key of a fact this relation has, or undefined when it has none. */
|
|
31
|
+
find(tuple) {
|
|
32
|
+
let node = this.root;
|
|
33
|
+
for (let i = 0; i < tuple.length; i++) {
|
|
34
|
+
const child = node.next?.get(tuple[i]);
|
|
35
|
+
if (child === void 0) {
|
|
36
|
+
return void 0;
|
|
37
|
+
}
|
|
38
|
+
node = child;
|
|
39
|
+
}
|
|
40
|
+
return node.present ? node : void 0;
|
|
41
|
+
}
|
|
42
|
+
has(key) {
|
|
43
|
+
return key.present;
|
|
44
|
+
}
|
|
45
|
+
/** Record the fact, under the tag the caller gave it. */
|
|
46
|
+
put(key, tag) {
|
|
47
|
+
key.present = true;
|
|
48
|
+
key.tag = tag;
|
|
49
|
+
this.size += 1;
|
|
50
|
+
}
|
|
51
|
+
tagOf(key) {
|
|
52
|
+
return key.tag;
|
|
53
|
+
}
|
|
54
|
+
setTag(key, tag) {
|
|
55
|
+
key.tag = tag;
|
|
56
|
+
}
|
|
57
|
+
/** Take the fact away, and the tag with it. */
|
|
58
|
+
remove(key) {
|
|
59
|
+
key.present = false;
|
|
60
|
+
key.tag = void 0;
|
|
61
|
+
this.size -= 1;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Drop what is left of a removed tuple's path, up to the first node
|
|
65
|
+
* another fact still needs. Without this a relation that is retracted
|
|
66
|
+
* from over and over keeps a node per tuple it ever had.
|
|
67
|
+
*/
|
|
68
|
+
prune(tuple) {
|
|
69
|
+
const path = [this.root];
|
|
70
|
+
let node = this.root;
|
|
71
|
+
for (let i = 0; i < tuple.length; i++) {
|
|
72
|
+
const child = node.next?.get(tuple[i]);
|
|
73
|
+
if (child === void 0) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
node = child;
|
|
77
|
+
path.push(child);
|
|
78
|
+
}
|
|
79
|
+
for (let i = path.length - 1; i > 0; i--) {
|
|
80
|
+
const child = path[i];
|
|
81
|
+
if (child.present || (child.next?.size ?? 0) > 0) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
path[i - 1].next?.delete(tuple[i - 1]);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** Drop every fact, and whatever was kept in order to find them. */
|
|
88
|
+
clear() {
|
|
89
|
+
this.root = emptyNode();
|
|
90
|
+
this.size = 0;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
1
94
|
// src/onDemand.ts
|
|
2
95
|
var adornmentKey = (adornment) => adornment.map((bound) => bound ? "b" : "f").join("");
|
|
3
96
|
var anyBound = (adornment) => adornment.some(Boolean);
|
|
@@ -419,6 +512,58 @@ function formatProfile(profile) {
|
|
|
419
512
|
return lines.join("\n");
|
|
420
513
|
}
|
|
421
514
|
|
|
515
|
+
// src/relation.ts
|
|
516
|
+
function addToBucket(buckets, key, tuple) {
|
|
517
|
+
const bucket = buckets.get(key);
|
|
518
|
+
if (bucket === void 0) {
|
|
519
|
+
buckets.set(key, [tuple]);
|
|
520
|
+
} else {
|
|
521
|
+
bucket.push(tuple);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
function bucketIn(relation, column, value) {
|
|
525
|
+
let buckets = relation.columns[column];
|
|
526
|
+
if (buckets === void 0) {
|
|
527
|
+
buckets = /* @__PURE__ */ new Map();
|
|
528
|
+
for (const tuple of relation.tuples) {
|
|
529
|
+
const at = tuple[column];
|
|
530
|
+
if (at !== void 0) {
|
|
531
|
+
addToBucket(buckets, at, tuple);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
relation.columns[column] = buckets;
|
|
535
|
+
}
|
|
536
|
+
return buckets.get(value) ?? [];
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// src/confidence.ts
|
|
540
|
+
var RANK = {
|
|
541
|
+
low: 0,
|
|
542
|
+
medium: 1,
|
|
543
|
+
high: 2
|
|
544
|
+
};
|
|
545
|
+
function confidenceWith(levelOf) {
|
|
546
|
+
return {
|
|
547
|
+
asserted: "high",
|
|
548
|
+
absent: "high",
|
|
549
|
+
combine(bodyTags, derivation) {
|
|
550
|
+
let lowest = levelOf(derivation.rule) ?? "high";
|
|
551
|
+
for (const tag of bodyTags) {
|
|
552
|
+
if (RANK[tag] < RANK[lowest]) {
|
|
553
|
+
lowest = tag;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return lowest;
|
|
557
|
+
},
|
|
558
|
+
merge(stored, incoming) {
|
|
559
|
+
return RANK[incoming] > RANK[stored] ? incoming : stored;
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
var confidence = confidenceWith(
|
|
564
|
+
() => void 0
|
|
565
|
+
);
|
|
566
|
+
|
|
422
567
|
// src/tupleKey.ts
|
|
423
568
|
var atomKey = (a) => typeof a === "number" ? `n${a}:` : `s${a.length}:${a}`;
|
|
424
569
|
var tupleKey = (parts) => {
|
|
@@ -457,34 +602,6 @@ function tupleKeyParts(key) {
|
|
|
457
602
|
return parts;
|
|
458
603
|
}
|
|
459
604
|
|
|
460
|
-
// src/confidence.ts
|
|
461
|
-
var RANK = {
|
|
462
|
-
low: 0,
|
|
463
|
-
medium: 1,
|
|
464
|
-
high: 2
|
|
465
|
-
};
|
|
466
|
-
function confidenceWith(levelOf) {
|
|
467
|
-
return {
|
|
468
|
-
asserted: "high",
|
|
469
|
-
absent: "high",
|
|
470
|
-
combine(bodyTags, derivation) {
|
|
471
|
-
let lowest = levelOf(derivation.rule) ?? "high";
|
|
472
|
-
for (const tag of bodyTags) {
|
|
473
|
-
if (RANK[tag] < RANK[lowest]) {
|
|
474
|
-
lowest = tag;
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
return lowest;
|
|
478
|
-
},
|
|
479
|
-
merge(stored, incoming) {
|
|
480
|
-
return RANK[incoming] > RANK[stored] ? incoming : stored;
|
|
481
|
-
}
|
|
482
|
-
};
|
|
483
|
-
}
|
|
484
|
-
var confidence = confidenceWith(
|
|
485
|
-
() => void 0
|
|
486
|
-
);
|
|
487
|
-
|
|
488
605
|
// src/witness.ts
|
|
489
606
|
var Witness = class {
|
|
490
607
|
constructor(rule2, body) {
|
|
@@ -561,30 +678,6 @@ var notLit = (relation, ...terms) => ({
|
|
|
561
678
|
var rule = (relation, terms, body, name) => name === void 0 ? { head: { relation, terms }, body } : { head: { relation, terms }, body, name };
|
|
562
679
|
var literalName = (literal) => literal.negated ? `!${literal.relation}` : literal.relation;
|
|
563
680
|
var ruleLabel = (r) => r.name ?? `${r.head.relation} :- ${r.body.map(literalName).join(", ")}`;
|
|
564
|
-
var keys = /* @__PURE__ */ new WeakMap();
|
|
565
|
-
var keyOf2 = (tuple) => {
|
|
566
|
-
const known = keys.get(tuple);
|
|
567
|
-
if (known !== void 0) {
|
|
568
|
-
return known;
|
|
569
|
-
}
|
|
570
|
-
const key = tupleKey(tuple);
|
|
571
|
-
keys.set(tuple, key);
|
|
572
|
-
return key;
|
|
573
|
-
};
|
|
574
|
-
function tagSlots(relation) {
|
|
575
|
-
if (relation.tags === void 0) {
|
|
576
|
-
relation.tags = /* @__PURE__ */ new Map();
|
|
577
|
-
}
|
|
578
|
-
return relation.tags;
|
|
579
|
-
}
|
|
580
|
-
function addToBucket(index, key, tuple) {
|
|
581
|
-
const bucket = index.get(key);
|
|
582
|
-
if (bucket === void 0) {
|
|
583
|
-
index.set(key, [tuple]);
|
|
584
|
-
} else {
|
|
585
|
-
bucket.push(tuple);
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
681
|
var Database = class {
|
|
589
682
|
store = /* @__PURE__ */ new Map();
|
|
590
683
|
relation(name) {
|
|
@@ -593,14 +686,21 @@ var Database = class {
|
|
|
593
686
|
return existing;
|
|
594
687
|
}
|
|
595
688
|
const created = {
|
|
596
|
-
|
|
689
|
+
index: new FactIndex(),
|
|
597
690
|
tuples: [],
|
|
598
|
-
|
|
599
|
-
indexes: /* @__PURE__ */ new Map()
|
|
691
|
+
columns: []
|
|
600
692
|
};
|
|
601
693
|
this.store.set(name, created);
|
|
602
694
|
return created;
|
|
603
695
|
}
|
|
696
|
+
/**
|
|
697
|
+
* The key this relation gives a tuple, whether or not the fact is
|
|
698
|
+
* present. Evaluation keys its ledger of derived facts by it; a
|
|
699
|
+
* caller outside this module has nowhere to spend one.
|
|
700
|
+
*/
|
|
701
|
+
keyFor(relationName, tuple) {
|
|
702
|
+
return this.relation(relationName).index.key(tuple);
|
|
703
|
+
}
|
|
604
704
|
/**
|
|
605
705
|
* Add a fact, or merge a tag into one already present. A new fact
|
|
606
706
|
* stores `tag` when one is given. For a fact already present,
|
|
@@ -610,14 +710,15 @@ var Database = class {
|
|
|
610
710
|
*/
|
|
611
711
|
add(relationName, tuple, tag, merge) {
|
|
612
712
|
const relation = this.relation(relationName);
|
|
613
|
-
const
|
|
614
|
-
|
|
713
|
+
const index = relation.index;
|
|
714
|
+
const key = index.key(tuple);
|
|
715
|
+
if (index.has(key)) {
|
|
615
716
|
let outcome = "unchanged";
|
|
616
717
|
if (merge !== void 0) {
|
|
617
|
-
const stored =
|
|
718
|
+
const stored = index.tagOf(key);
|
|
618
719
|
const merged = merge(stored, tag);
|
|
619
720
|
if (merged !== stored) {
|
|
620
|
-
|
|
721
|
+
index.setTag(key, merged);
|
|
621
722
|
outcome = "improved";
|
|
622
723
|
}
|
|
623
724
|
}
|
|
@@ -626,25 +727,28 @@ var Database = class {
|
|
|
626
727
|
}
|
|
627
728
|
return outcome;
|
|
628
729
|
}
|
|
629
|
-
|
|
730
|
+
index.put(key, tag);
|
|
630
731
|
relation.tuples.push(tuple);
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
}
|
|
634
|
-
for (const [column, index] of relation.indexes) {
|
|
732
|
+
for (let column = 0; column < relation.columns.length; column++) {
|
|
733
|
+
const buckets = relation.columns[column];
|
|
635
734
|
const value = tuple[column];
|
|
636
|
-
if (value !== void 0) {
|
|
637
|
-
addToBucket(
|
|
735
|
+
if (buckets !== void 0 && value !== void 0) {
|
|
736
|
+
addToBucket(buckets, value, tuple);
|
|
638
737
|
}
|
|
639
738
|
}
|
|
640
739
|
return "added";
|
|
641
740
|
}
|
|
642
741
|
/** The tag stored for this fact, or undefined when there is none. */
|
|
643
742
|
tagOf(relationName, tuple) {
|
|
644
|
-
|
|
743
|
+
const relation = this.store.get(relationName);
|
|
744
|
+
if (relation === void 0) {
|
|
745
|
+
return void 0;
|
|
746
|
+
}
|
|
747
|
+
const key = relation.index.find(tuple);
|
|
748
|
+
return key === void 0 ? void 0 : relation.index.tagOf(key);
|
|
645
749
|
}
|
|
646
750
|
has(relationName, tuple) {
|
|
647
|
-
return this.store.get(relationName)?.
|
|
751
|
+
return this.store.get(relationName)?.index.find(tuple) !== void 0;
|
|
648
752
|
}
|
|
649
753
|
facts(relationName) {
|
|
650
754
|
return this.store.get(relationName)?.tuples ?? [];
|
|
@@ -660,21 +764,11 @@ var Database = class {
|
|
|
660
764
|
*/
|
|
661
765
|
lookup(relationName, column, value) {
|
|
662
766
|
const relation = this.store.get(relationName);
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
index = /* @__PURE__ */ new Map();
|
|
669
|
-
for (const tuple of relation.tuples) {
|
|
670
|
-
const at = tuple[column];
|
|
671
|
-
if (at !== void 0) {
|
|
672
|
-
addToBucket(index, at, tuple);
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
relation.indexes.set(column, index);
|
|
676
|
-
}
|
|
677
|
-
return index.get(value) ?? [];
|
|
767
|
+
return relation === void 0 ? [] : bucketIn(relation, column, value);
|
|
768
|
+
}
|
|
769
|
+
/** @internal The join resolves a literal's relation once, then narrows on each fixed column. */
|
|
770
|
+
relationOf(relationName) {
|
|
771
|
+
return this.store.get(relationName);
|
|
678
772
|
}
|
|
679
773
|
/**
|
|
680
774
|
* Remove facts from a relation. Returns how many were actually there.
|
|
@@ -688,22 +782,34 @@ var Database = class {
|
|
|
688
782
|
if (relation === void 0) {
|
|
689
783
|
return 0;
|
|
690
784
|
}
|
|
785
|
+
const index = relation.index;
|
|
691
786
|
const going = /* @__PURE__ */ new Set();
|
|
787
|
+
const gone = [];
|
|
692
788
|
for (const tuple of tuples) {
|
|
693
|
-
const key =
|
|
694
|
-
if (
|
|
789
|
+
const key = index.find(tuple);
|
|
790
|
+
if (key !== void 0 && !going.has(key)) {
|
|
695
791
|
going.add(key);
|
|
792
|
+
gone.push(tuple);
|
|
696
793
|
}
|
|
697
794
|
}
|
|
698
795
|
if (going.size === 0) {
|
|
699
796
|
return 0;
|
|
700
797
|
}
|
|
701
798
|
for (const key of going) {
|
|
702
|
-
|
|
703
|
-
|
|
799
|
+
index.remove(key);
|
|
800
|
+
}
|
|
801
|
+
if (index.size === 0) {
|
|
802
|
+
relation.tuples = [];
|
|
803
|
+
index.clear();
|
|
804
|
+
} else {
|
|
805
|
+
relation.tuples = relation.tuples.filter(
|
|
806
|
+
(tuple) => index.find(tuple) !== void 0
|
|
807
|
+
);
|
|
808
|
+
for (const tuple of gone) {
|
|
809
|
+
index.prune(tuple);
|
|
810
|
+
}
|
|
704
811
|
}
|
|
705
|
-
relation.
|
|
706
|
-
relation.indexes.clear();
|
|
812
|
+
relation.columns.length = 0;
|
|
707
813
|
forgetFacts(this, relationName, going);
|
|
708
814
|
return going.size;
|
|
709
815
|
}
|
|
@@ -915,6 +1021,7 @@ function evaluateRule(db, deltas, r, deltaIndex, budget) {
|
|
|
915
1021
|
}
|
|
916
1022
|
var boundSource = (db, literal, bindings) => narrowedSource(db, literal, bindings) ?? db.facts(literal.relation);
|
|
917
1023
|
function narrowedSource(db, literal, bindings) {
|
|
1024
|
+
const relation = db.relationOf(literal.relation);
|
|
918
1025
|
let narrowest = null;
|
|
919
1026
|
for (let column = 0; column < literal.terms.length; column++) {
|
|
920
1027
|
const term = literal.terms[column];
|
|
@@ -922,7 +1029,7 @@ function narrowedSource(db, literal, bindings) {
|
|
|
922
1029
|
if (value === void 0) {
|
|
923
1030
|
continue;
|
|
924
1031
|
}
|
|
925
|
-
const bucket =
|
|
1032
|
+
const bucket = relation === void 0 ? [] : bucketIn(relation, column, value);
|
|
926
1033
|
if (bucket.length === 0) {
|
|
927
1034
|
return bucket;
|
|
928
1035
|
}
|
|
@@ -1018,7 +1125,7 @@ function claimFact(db, relation, key) {
|
|
|
1018
1125
|
state.derived.get(relation)?.delete(key);
|
|
1019
1126
|
}
|
|
1020
1127
|
}
|
|
1021
|
-
function forgetFacts(db, relation,
|
|
1128
|
+
function forgetFacts(db, relation, keys) {
|
|
1022
1129
|
const states = evaluated.get(db);
|
|
1023
1130
|
if (states === void 0) {
|
|
1024
1131
|
return;
|
|
@@ -1027,7 +1134,7 @@ function forgetFacts(db, relation, keys2) {
|
|
|
1027
1134
|
state.marks = null;
|
|
1028
1135
|
const ledger = state.derived.get(relation);
|
|
1029
1136
|
if (ledger !== void 0) {
|
|
1030
|
-
for (const key of
|
|
1137
|
+
for (const key of keys) {
|
|
1031
1138
|
ledger.delete(key);
|
|
1032
1139
|
}
|
|
1033
1140
|
}
|
|
@@ -1149,7 +1256,7 @@ function runRules(db, rules, algebra, given) {
|
|
|
1149
1256
|
ledger = /* @__PURE__ */ new Map();
|
|
1150
1257
|
derived.set(relation, ledger);
|
|
1151
1258
|
}
|
|
1152
|
-
ledger.set(
|
|
1259
|
+
ledger.set(db.keyFor(relation, tuple), tuple);
|
|
1153
1260
|
addedHere?.push([relation, tuple]);
|
|
1154
1261
|
};
|
|
1155
1262
|
const mergeStored = algebra === void 0 ? void 0 : storedMergeFor(algebra);
|
package/package.json
CHANGED