@suss/datalog 0.37.1 → 0.38.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/dist/index.cjs +522 -67
- package/dist/index.d.cts +127 -11
- package/dist/index.d.ts +127 -11
- package/dist/index.js +521 -67
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -34,6 +34,7 @@ __export(index_exports, {
|
|
|
34
34
|
formatProfile: () => formatProfile,
|
|
35
35
|
lit: () => lit,
|
|
36
36
|
notLit: () => notLit,
|
|
37
|
+
noteReads: () => noteReads,
|
|
37
38
|
profileEvaluation: () => profileEvaluation,
|
|
38
39
|
profileEvaluationAsync: () => profileEvaluationAsync,
|
|
39
40
|
proofOf: () => proofOf,
|
|
@@ -50,45 +51,93 @@ __export(index_exports, {
|
|
|
50
51
|
module.exports = __toCommonJS(index_exports);
|
|
51
52
|
|
|
52
53
|
// src/factIndex.ts
|
|
53
|
-
var
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
var FactKey = class {
|
|
55
|
+
constructor(tuple) {
|
|
56
|
+
this.tuple = tuple;
|
|
57
|
+
}
|
|
58
|
+
tuple;
|
|
59
|
+
present = false;
|
|
60
|
+
tag = void 0;
|
|
61
|
+
};
|
|
62
|
+
var Branch = class {
|
|
63
|
+
/** The key of a tuple that ends at this column, as a shorter one in a relation of mixed arity does. */
|
|
64
|
+
here = void 0;
|
|
65
|
+
next = /* @__PURE__ */ new Map();
|
|
66
|
+
};
|
|
67
|
+
function sameFrom(a, b, from) {
|
|
68
|
+
if (a.length !== b.length) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
for (let at = from; at < a.length; at++) {
|
|
72
|
+
if (a[at] !== b[at]) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
function place(branch, key, depth) {
|
|
79
|
+
if (depth === key.tuple.length) {
|
|
80
|
+
branch.here = key;
|
|
81
|
+
} else {
|
|
82
|
+
branch.next.set(key.tuple[depth], key);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function soleKey(branch) {
|
|
86
|
+
if (branch.here !== void 0) {
|
|
87
|
+
return branch.next.size === 0 ? branch.here : void 0;
|
|
88
|
+
}
|
|
89
|
+
if (branch.next.size !== 1) {
|
|
90
|
+
return void 0;
|
|
91
|
+
}
|
|
92
|
+
const only = branch.next.values().next().value;
|
|
93
|
+
return only instanceof FactKey ? only : void 0;
|
|
94
|
+
}
|
|
58
95
|
var FactIndex = class {
|
|
59
96
|
/** How many facts are present. */
|
|
60
97
|
size = 0;
|
|
61
|
-
root =
|
|
98
|
+
root = new Branch();
|
|
62
99
|
/** This tuple's key here, whether or not the fact is present. */
|
|
63
100
|
key(tuple) {
|
|
64
|
-
let
|
|
65
|
-
for (let
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
node.next = next;
|
|
101
|
+
let branch = this.root;
|
|
102
|
+
for (let depth = 0; ; depth++) {
|
|
103
|
+
if (depth === tuple.length) {
|
|
104
|
+
branch.here ??= new FactKey(tuple);
|
|
105
|
+
return branch.here;
|
|
70
106
|
}
|
|
71
|
-
const atom = tuple[
|
|
72
|
-
|
|
107
|
+
const atom = tuple[depth];
|
|
108
|
+
const child = branch.next.get(atom);
|
|
73
109
|
if (child === void 0) {
|
|
74
|
-
|
|
75
|
-
next.set(atom,
|
|
110
|
+
const key = new FactKey(tuple);
|
|
111
|
+
branch.next.set(atom, key);
|
|
112
|
+
return key;
|
|
76
113
|
}
|
|
77
|
-
|
|
114
|
+
if (child instanceof Branch) {
|
|
115
|
+
branch = child;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (sameFrom(child.tuple, tuple, depth + 1)) {
|
|
119
|
+
return child;
|
|
120
|
+
}
|
|
121
|
+
const split = new Branch();
|
|
122
|
+
place(split, child, depth + 1);
|
|
123
|
+
branch.next.set(atom, split);
|
|
124
|
+
branch = split;
|
|
78
125
|
}
|
|
79
|
-
return node;
|
|
80
126
|
}
|
|
81
127
|
/** The key of a fact this relation has, or undefined when it has none. */
|
|
82
128
|
find(tuple) {
|
|
83
|
-
let
|
|
84
|
-
for (let
|
|
85
|
-
const child =
|
|
129
|
+
let branch = this.root;
|
|
130
|
+
for (let depth = 0; ; depth++) {
|
|
131
|
+
const child = depth === tuple.length ? branch.here : branch.next.get(tuple[depth]);
|
|
86
132
|
if (child === void 0) {
|
|
87
133
|
return void 0;
|
|
88
134
|
}
|
|
89
|
-
|
|
135
|
+
if (child instanceof Branch) {
|
|
136
|
+
branch = child;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
return child.present && sameFrom(child.tuple, tuple, depth + 1) ? child : void 0;
|
|
90
140
|
}
|
|
91
|
-
return node.present ? node : void 0;
|
|
92
141
|
}
|
|
93
142
|
has(key) {
|
|
94
143
|
return key.present;
|
|
@@ -112,32 +161,45 @@ var FactIndex = class {
|
|
|
112
161
|
this.size -= 1;
|
|
113
162
|
}
|
|
114
163
|
/**
|
|
115
|
-
* Drop
|
|
116
|
-
*
|
|
117
|
-
*
|
|
164
|
+
* Drop a removed tuple's key, and any branch left with one key to
|
|
165
|
+
* tell apart. Without this a relation that is retracted from over and
|
|
166
|
+
* over keeps a key per tuple it ever had.
|
|
118
167
|
*/
|
|
119
168
|
prune(tuple) {
|
|
120
|
-
const path = [
|
|
121
|
-
let
|
|
122
|
-
|
|
123
|
-
|
|
169
|
+
const path = [];
|
|
170
|
+
let branch = this.root;
|
|
171
|
+
let depth = 0;
|
|
172
|
+
for (; ; depth++) {
|
|
173
|
+
path.push(branch);
|
|
174
|
+
const child = depth === tuple.length ? branch.here : branch.next.get(tuple[depth]);
|
|
124
175
|
if (child === void 0) {
|
|
125
176
|
return;
|
|
126
177
|
}
|
|
127
|
-
|
|
128
|
-
|
|
178
|
+
if (child instanceof Branch) {
|
|
179
|
+
branch = child;
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (child.present || !sameFrom(child.tuple, tuple, depth + 1)) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
if (depth === tuple.length) {
|
|
188
|
+
branch.here = void 0;
|
|
189
|
+
} else {
|
|
190
|
+
branch.next.delete(tuple[depth]);
|
|
129
191
|
}
|
|
130
|
-
for (let
|
|
131
|
-
const
|
|
132
|
-
if (
|
|
192
|
+
for (let at = path.length - 1; at > 0; at--) {
|
|
193
|
+
const sole = soleKey(path[at]);
|
|
194
|
+
if (sole === void 0) {
|
|
133
195
|
return;
|
|
134
196
|
}
|
|
135
|
-
path[
|
|
197
|
+
path[at - 1].next.set(tuple[at - 1], sole);
|
|
136
198
|
}
|
|
137
199
|
}
|
|
138
200
|
/** Drop every fact, and whatever was kept in order to find them. */
|
|
139
201
|
clear() {
|
|
140
|
-
this.root =
|
|
202
|
+
this.root = new Branch();
|
|
141
203
|
this.size = 0;
|
|
142
204
|
}
|
|
143
205
|
};
|
|
@@ -574,6 +636,24 @@ function formatProfile(profile) {
|
|
|
574
636
|
}
|
|
575
637
|
|
|
576
638
|
// src/relation.ts
|
|
639
|
+
var isLone = (bucket) => typeof bucket[0] !== "object";
|
|
640
|
+
var NO_TUPLES = [];
|
|
641
|
+
function tuplesIn(bucket) {
|
|
642
|
+
if (bucket === void 0) {
|
|
643
|
+
return NO_TUPLES;
|
|
644
|
+
}
|
|
645
|
+
return isLone(bucket) ? [bucket] : bucket;
|
|
646
|
+
}
|
|
647
|
+
function fileUnder(buckets, key, tuple) {
|
|
648
|
+
const bucket = buckets.get(key);
|
|
649
|
+
if (bucket === void 0) {
|
|
650
|
+
buckets.set(key, tuple);
|
|
651
|
+
} else if (isLone(bucket)) {
|
|
652
|
+
buckets.set(key, [bucket, tuple]);
|
|
653
|
+
} else {
|
|
654
|
+
bucket.push(tuple);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
577
657
|
var sameTuple = (a, b) => a.length === b.length && a.every((atom, at) => atom === b[at]);
|
|
578
658
|
function storedTuplesOf(relation, facts) {
|
|
579
659
|
const column = relation.columns.findIndex((buckets2) => buckets2 !== void 0);
|
|
@@ -582,19 +662,24 @@ function storedTuplesOf(relation, facts) {
|
|
|
582
662
|
return null;
|
|
583
663
|
}
|
|
584
664
|
const stored = facts.map(
|
|
585
|
-
(fact) => buckets.get(fact[column])
|
|
665
|
+
(fact) => tuplesIn(buckets.get(fact[column])).find(
|
|
666
|
+
(tuple) => sameTuple(tuple, fact)
|
|
667
|
+
)
|
|
586
668
|
);
|
|
587
669
|
return stored.every((tuple) => tuple !== void 0) ? stored : null;
|
|
588
670
|
}
|
|
589
671
|
function removeFrom(buckets, key, tuple) {
|
|
590
672
|
const bucket = buckets.get(key);
|
|
591
|
-
|
|
592
|
-
if (bucket === void 0 || at === -1) {
|
|
673
|
+
if (bucket === void 0) {
|
|
593
674
|
return;
|
|
594
675
|
}
|
|
595
|
-
bucket
|
|
596
|
-
if (bucket.length === 0) {
|
|
676
|
+
if (isLone(bucket)) {
|
|
597
677
|
buckets.delete(key);
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
bucket.splice(bucket.indexOf(tuple), 1);
|
|
681
|
+
if (bucket.length === 1) {
|
|
682
|
+
buckets.set(key, bucket[0]);
|
|
598
683
|
}
|
|
599
684
|
}
|
|
600
685
|
function removeTuples(relation, stored) {
|
|
@@ -646,7 +731,7 @@ function addToCombined(index, tuple) {
|
|
|
646
731
|
}
|
|
647
732
|
level = next;
|
|
648
733
|
}
|
|
649
|
-
|
|
734
|
+
fileUnder(
|
|
650
735
|
level,
|
|
651
736
|
tuple[index.columns[last]],
|
|
652
737
|
tuple
|
|
@@ -665,10 +750,10 @@ function combinedBucketIn(relation, mask, values) {
|
|
|
665
750
|
for (const value of values) {
|
|
666
751
|
level = level.get(value);
|
|
667
752
|
if (level === void 0) {
|
|
668
|
-
return
|
|
753
|
+
return NO_TUPLES;
|
|
669
754
|
}
|
|
670
755
|
}
|
|
671
|
-
return level;
|
|
756
|
+
return tuplesIn(level);
|
|
672
757
|
}
|
|
673
758
|
function addToBucket(buckets, key, tuple) {
|
|
674
759
|
const bucket = buckets.get(key);
|
|
@@ -678,19 +763,291 @@ function addToBucket(buckets, key, tuple) {
|
|
|
678
763
|
bucket.push(tuple);
|
|
679
764
|
}
|
|
680
765
|
}
|
|
681
|
-
function
|
|
766
|
+
function columnIndex(relation, column) {
|
|
682
767
|
let buckets = relation.columns[column];
|
|
683
768
|
if (buckets === void 0) {
|
|
684
769
|
buckets = /* @__PURE__ */ new Map();
|
|
685
770
|
for (const tuple of relation.tuples) {
|
|
686
771
|
const at = tuple[column];
|
|
687
772
|
if (at !== void 0) {
|
|
688
|
-
|
|
773
|
+
fileUnder(buckets, at, tuple);
|
|
689
774
|
}
|
|
690
775
|
}
|
|
691
776
|
relation.columns[column] = buckets;
|
|
692
777
|
}
|
|
693
|
-
return buckets
|
|
778
|
+
return buckets;
|
|
779
|
+
}
|
|
780
|
+
function bucketIn(relation, column, value) {
|
|
781
|
+
return tuplesIn(columnIndex(relation, column).get(value));
|
|
782
|
+
}
|
|
783
|
+
function bucketSize(relation, column, value) {
|
|
784
|
+
const bucket = columnIndex(relation, column).get(value);
|
|
785
|
+
if (bucket === void 0) {
|
|
786
|
+
return 0;
|
|
787
|
+
}
|
|
788
|
+
return isLone(bucket) ? 1 : bucket.length;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// src/reads.ts
|
|
792
|
+
function noteIn(reads, column, value) {
|
|
793
|
+
if (reads.whole) {
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
let values = reads.columns[column];
|
|
797
|
+
if (values === void 0) {
|
|
798
|
+
values = /* @__PURE__ */ new Set();
|
|
799
|
+
reads.columns[column] = values;
|
|
800
|
+
}
|
|
801
|
+
values.add(value);
|
|
802
|
+
}
|
|
803
|
+
var ReadLog = class {
|
|
804
|
+
constructor(skipped) {
|
|
805
|
+
this.skipped = skipped;
|
|
806
|
+
}
|
|
807
|
+
skipped;
|
|
808
|
+
/** Null for a relation the caller said to leave out. */
|
|
809
|
+
byRelation = /* @__PURE__ */ new Map();
|
|
810
|
+
lookedUp(relation, column, value) {
|
|
811
|
+
const reads = this.readsOf(relation);
|
|
812
|
+
if (reads !== null) {
|
|
813
|
+
noteIn(reads, column, value);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
readWhole(relation) {
|
|
817
|
+
const reads = this.readsOf(relation);
|
|
818
|
+
if (reads !== null) {
|
|
819
|
+
reads.whole = true;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
/** Note everything an inner log noted, for an answer cached inside another. */
|
|
823
|
+
absorb(inner) {
|
|
824
|
+
for (const [relation, reads] of inner.byRelation) {
|
|
825
|
+
if (reads === null) {
|
|
826
|
+
continue;
|
|
827
|
+
}
|
|
828
|
+
if (reads.whole) {
|
|
829
|
+
this.readWhole(relation);
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
reads.columns.forEach((values, column) => {
|
|
833
|
+
for (const value of values ?? []) {
|
|
834
|
+
this.lookedUp(relation, column, value);
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* The reads with what each one finds in `db` now. One question looks
|
|
841
|
+
* up the same values in many columns, so each value is kept once, and
|
|
842
|
+
* each column keeps a bit per value saying whether it was looked up
|
|
843
|
+
* there.
|
|
844
|
+
*/
|
|
845
|
+
counted(db) {
|
|
846
|
+
const names = [];
|
|
847
|
+
const groups = [];
|
|
848
|
+
const columns = [];
|
|
849
|
+
const lookedUp = [];
|
|
850
|
+
const positions = /* @__PURE__ */ new Map();
|
|
851
|
+
const values = [];
|
|
852
|
+
const addGroup = (column, seen) => {
|
|
853
|
+
groups.push(names.length - 1);
|
|
854
|
+
columns.push(column);
|
|
855
|
+
lookedUp.push(seen);
|
|
856
|
+
for (const value of seen) {
|
|
857
|
+
if (!positions.has(value)) {
|
|
858
|
+
positions.set(value, values.length);
|
|
859
|
+
values.push(value);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
for (const [relation, reads] of this.byRelation) {
|
|
864
|
+
if (reads === null) {
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
867
|
+
names.push(relation);
|
|
868
|
+
if (reads.whole) {
|
|
869
|
+
addGroup(WHOLE, NONE);
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
reads.columns.forEach((seen, column) => {
|
|
873
|
+
if (seen !== void 0) {
|
|
874
|
+
addGroup(column, seen);
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
const words = Math.ceil(values.length / 32);
|
|
879
|
+
const bits = new Uint32Array(groups.length * words);
|
|
880
|
+
let found = 0;
|
|
881
|
+
lookedUp.forEach((seen, group) => {
|
|
882
|
+
const relation = db.relationOf(names[groups[group]]);
|
|
883
|
+
const column = columns[group];
|
|
884
|
+
if (column === WHOLE) {
|
|
885
|
+
found += relation?.tuples.length ?? 0;
|
|
886
|
+
}
|
|
887
|
+
for (const value of seen) {
|
|
888
|
+
const at = positions.get(value);
|
|
889
|
+
bits[group * words + (at >>> 5)] |= 1 << (at & 31);
|
|
890
|
+
found += relation === void 0 ? 0 : bucketSize(relation, column, value);
|
|
891
|
+
}
|
|
892
|
+
});
|
|
893
|
+
return new ReadsNoted(
|
|
894
|
+
db,
|
|
895
|
+
this.skipped,
|
|
896
|
+
{ names, groups, columns, values, bits },
|
|
897
|
+
found
|
|
898
|
+
);
|
|
899
|
+
}
|
|
900
|
+
/** @internal Null for a relation the caller said to leave out. */
|
|
901
|
+
readsOf(relation) {
|
|
902
|
+
let reads = this.byRelation.get(relation);
|
|
903
|
+
if (reads === void 0) {
|
|
904
|
+
reads = this.skipped.has(relation) ? null : { whole: false, columns: [] };
|
|
905
|
+
this.byRelation.set(relation, reads);
|
|
906
|
+
}
|
|
907
|
+
return reads;
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
var WHOLE = -1;
|
|
911
|
+
var NONE = /* @__PURE__ */ new Set();
|
|
912
|
+
var ReadsNoted = class {
|
|
913
|
+
constructor(db, skipped, lookups, found) {
|
|
914
|
+
this.skipped = skipped;
|
|
915
|
+
this.found = found;
|
|
916
|
+
this.names = lookups.names;
|
|
917
|
+
this.groups = lookups.groups;
|
|
918
|
+
this.columns = lookups.columns;
|
|
919
|
+
this.values = lookups.values;
|
|
920
|
+
this.bits = lookups.bits;
|
|
921
|
+
this.words = Math.ceil(lookups.values.length / 32);
|
|
922
|
+
this.relations = lookups.names.map((name) => db.relationOf(name));
|
|
923
|
+
this.removed = this.sumOver(db, removedFrom);
|
|
924
|
+
this.facts = this.sumOver(db, factsIn);
|
|
925
|
+
}
|
|
926
|
+
skipped;
|
|
927
|
+
found;
|
|
928
|
+
names;
|
|
929
|
+
groups;
|
|
930
|
+
columns;
|
|
931
|
+
values;
|
|
932
|
+
bits;
|
|
933
|
+
words;
|
|
934
|
+
removed;
|
|
935
|
+
/** The facts in every relation read, when the counts last matched. */
|
|
936
|
+
facts;
|
|
937
|
+
/** Each relation read, found by name the first time a check needs it. */
|
|
938
|
+
relations;
|
|
939
|
+
/**
|
|
940
|
+
* Whether every read would find what it found then. A fact left in a
|
|
941
|
+
* skipped relation would join a question asked now without anything
|
|
942
|
+
* here having looked it up, so one there makes the answer no. The
|
|
943
|
+
* counts are taken again only when a relation read has grown.
|
|
944
|
+
*/
|
|
945
|
+
unchangedIn(db) {
|
|
946
|
+
if (!allEmpty(db, this.skipped)) {
|
|
947
|
+
return false;
|
|
948
|
+
}
|
|
949
|
+
if (this.sumOver(db, removedFrom) !== this.removed) {
|
|
950
|
+
return false;
|
|
951
|
+
}
|
|
952
|
+
const facts = this.sumOver(db, factsIn);
|
|
953
|
+
if (facts === this.facts) {
|
|
954
|
+
return true;
|
|
955
|
+
}
|
|
956
|
+
if (this.countIn(db, this.found) !== this.found) {
|
|
957
|
+
return false;
|
|
958
|
+
}
|
|
959
|
+
this.facts = facts;
|
|
960
|
+
return true;
|
|
961
|
+
}
|
|
962
|
+
relationAt(db, at) {
|
|
963
|
+
let relation = this.relations[at];
|
|
964
|
+
if (relation === void 0) {
|
|
965
|
+
relation = db.relationOf(this.names[at]);
|
|
966
|
+
this.relations[at] = relation;
|
|
967
|
+
}
|
|
968
|
+
return relation;
|
|
969
|
+
}
|
|
970
|
+
sumOver(db, measure) {
|
|
971
|
+
let sum = 0;
|
|
972
|
+
for (let at = 0; at < this.names.length; at++) {
|
|
973
|
+
const relation = this.relationAt(db, at);
|
|
974
|
+
sum += relation === void 0 ? 0 : measure(relation);
|
|
975
|
+
}
|
|
976
|
+
return sum;
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* The facts the reads find, stopping once past `limit`. With nothing
|
|
980
|
+
* retracted every count only grows, so passing the old total already
|
|
981
|
+
* says that one of them grew.
|
|
982
|
+
*/
|
|
983
|
+
countIn(db, limit) {
|
|
984
|
+
let found = 0;
|
|
985
|
+
for (let group = 0; group < this.groups.length && found <= limit; group++) {
|
|
986
|
+
const relation = this.relationAt(db, this.groups[group]);
|
|
987
|
+
const column = this.columns[group];
|
|
988
|
+
if (column === WHOLE) {
|
|
989
|
+
found += relation?.tuples.length ?? 0;
|
|
990
|
+
} else if (relation !== void 0) {
|
|
991
|
+
found += this.countGroup(relation, group, column);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
return found;
|
|
995
|
+
}
|
|
996
|
+
countGroup(relation, group, column) {
|
|
997
|
+
let found = 0;
|
|
998
|
+
const first = group * this.words;
|
|
999
|
+
for (let word = 0; word < this.words; word++) {
|
|
1000
|
+
let left = this.bits[first + word];
|
|
1001
|
+
while (left !== 0) {
|
|
1002
|
+
const bit = 31 - Math.clz32(left & -left);
|
|
1003
|
+
left &= left - 1;
|
|
1004
|
+
const value = this.values[word * 32 + bit];
|
|
1005
|
+
found += bucketSize(relation, column, value);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
return found;
|
|
1009
|
+
}
|
|
1010
|
+
};
|
|
1011
|
+
var removedFrom = (relation) => relation.removed;
|
|
1012
|
+
var factsIn = (relation) => relation.tuples.length;
|
|
1013
|
+
var skippedIn = /* @__PURE__ */ new WeakMap();
|
|
1014
|
+
function allEmpty(db, skipped) {
|
|
1015
|
+
let bySet = skippedIn.get(db);
|
|
1016
|
+
if (bySet === void 0) {
|
|
1017
|
+
bySet = /* @__PURE__ */ new WeakMap();
|
|
1018
|
+
skippedIn.set(db, bySet);
|
|
1019
|
+
}
|
|
1020
|
+
let known = bySet.get(skipped);
|
|
1021
|
+
if (known === void 0) {
|
|
1022
|
+
known = { found: [], missing: [...skipped] };
|
|
1023
|
+
bySet.set(skipped, known);
|
|
1024
|
+
}
|
|
1025
|
+
if (known.missing.length > 0) {
|
|
1026
|
+
const stillMissing = [];
|
|
1027
|
+
for (const name of known.missing) {
|
|
1028
|
+
const relation = db.relationOf(name);
|
|
1029
|
+
if (relation === void 0) {
|
|
1030
|
+
stillMissing.push(name);
|
|
1031
|
+
} else {
|
|
1032
|
+
known.found.push(relation);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
known.missing = stillMissing;
|
|
1036
|
+
}
|
|
1037
|
+
return known.found.every((relation) => relation.tuples.length === 0);
|
|
1038
|
+
}
|
|
1039
|
+
function noteReads(db, skipped, work) {
|
|
1040
|
+
const outer = db.readLog;
|
|
1041
|
+
const log = new ReadLog(skipped);
|
|
1042
|
+
const emptyAtStart = allEmpty(db, skipped);
|
|
1043
|
+
db.readLog = log;
|
|
1044
|
+
try {
|
|
1045
|
+
const value = work();
|
|
1046
|
+
return { value, reads: emptyAtStart ? log.counted(db) : null };
|
|
1047
|
+
} finally {
|
|
1048
|
+
db.readLog = outer;
|
|
1049
|
+
outer?.absorb(log);
|
|
1050
|
+
}
|
|
694
1051
|
}
|
|
695
1052
|
|
|
696
1053
|
// src/stratum.ts
|
|
@@ -716,7 +1073,7 @@ function planStratum(rules) {
|
|
|
716
1073
|
}
|
|
717
1074
|
return { rules, derived, readers, marked: new Uint8Array(rules.length) };
|
|
718
1075
|
}
|
|
719
|
-
var
|
|
1076
|
+
var NONE2 = [];
|
|
720
1077
|
function rulesReading(stratum, seed, derivedOnly) {
|
|
721
1078
|
const lists = [];
|
|
722
1079
|
for (const [relation, tuples] of seed) {
|
|
@@ -732,7 +1089,7 @@ function rulesReading(stratum, seed, derivedOnly) {
|
|
|
732
1089
|
}
|
|
733
1090
|
}
|
|
734
1091
|
if (lists.length === 0) {
|
|
735
|
-
return
|
|
1092
|
+
return NONE2;
|
|
736
1093
|
}
|
|
737
1094
|
if (lists.length === 1) {
|
|
738
1095
|
return lists[0];
|
|
@@ -902,6 +1259,8 @@ var ruleLabel = (r) => r.name ?? `${r.head.relation} :- ${r.body.map(literalName
|
|
|
902
1259
|
var FEW_REMOVED = 16;
|
|
903
1260
|
var Database = class {
|
|
904
1261
|
store = /* @__PURE__ */ new Map();
|
|
1262
|
+
/** @internal Where reads are noted while `noteReads` runs. */
|
|
1263
|
+
readLog = null;
|
|
905
1264
|
relation(name) {
|
|
906
1265
|
const existing = this.store.get(name);
|
|
907
1266
|
if (existing !== void 0) {
|
|
@@ -911,7 +1270,8 @@ var Database = class {
|
|
|
911
1270
|
index: new FactIndex(),
|
|
912
1271
|
tuples: [],
|
|
913
1272
|
columns: [],
|
|
914
|
-
combined: /* @__PURE__ */ new Map()
|
|
1273
|
+
combined: /* @__PURE__ */ new Map(),
|
|
1274
|
+
removed: 0
|
|
915
1275
|
};
|
|
916
1276
|
this.store.set(name, created);
|
|
917
1277
|
return created;
|
|
@@ -956,7 +1316,7 @@ var Database = class {
|
|
|
956
1316
|
const buckets = relation.columns[column];
|
|
957
1317
|
const value = tuple[column];
|
|
958
1318
|
if (buckets !== void 0 && value !== void 0) {
|
|
959
|
-
|
|
1319
|
+
fileUnder(buckets, value, tuple);
|
|
960
1320
|
}
|
|
961
1321
|
}
|
|
962
1322
|
for (const combined of relation.combined.values()) {
|
|
@@ -966,6 +1326,7 @@ var Database = class {
|
|
|
966
1326
|
}
|
|
967
1327
|
/** The tag stored for this fact, or undefined when there is none. */
|
|
968
1328
|
tagOf(relationName, tuple) {
|
|
1329
|
+
this.noteTupleRead(relationName, tuple);
|
|
969
1330
|
const relation = this.store.get(relationName);
|
|
970
1331
|
if (relation === void 0) {
|
|
971
1332
|
return void 0;
|
|
@@ -974,9 +1335,20 @@ var Database = class {
|
|
|
974
1335
|
return key === void 0 ? void 0 : relation.index.tagOf(key);
|
|
975
1336
|
}
|
|
976
1337
|
has(relationName, tuple) {
|
|
1338
|
+
this.noteTupleRead(relationName, tuple);
|
|
977
1339
|
return this.store.get(relationName)?.index.find(tuple) !== void 0;
|
|
978
1340
|
}
|
|
1341
|
+
/** A fact matching the whole tuple agrees on its first column, so that lookup is enough to note. */
|
|
1342
|
+
noteTupleRead(relationName, tuple) {
|
|
1343
|
+
const first = tuple[0];
|
|
1344
|
+
if (first === void 0) {
|
|
1345
|
+
this.readLog?.readWhole(relationName);
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
this.readLog?.lookedUp(relationName, 0, first);
|
|
1349
|
+
}
|
|
979
1350
|
facts(relationName) {
|
|
1351
|
+
this.readLog?.readWhole(relationName);
|
|
980
1352
|
return this.store.get(relationName)?.tuples ?? [];
|
|
981
1353
|
}
|
|
982
1354
|
/**
|
|
@@ -989,6 +1361,7 @@ var Database = class {
|
|
|
989
1361
|
* gets an index.
|
|
990
1362
|
*/
|
|
991
1363
|
lookup(relationName, column, value) {
|
|
1364
|
+
this.readLog?.lookedUp(relationName, column, value);
|
|
992
1365
|
const relation = this.store.get(relationName);
|
|
993
1366
|
return relation === void 0 ? [] : bucketIn(relation, column, value);
|
|
994
1367
|
}
|
|
@@ -1024,6 +1397,7 @@ var Database = class {
|
|
|
1024
1397
|
for (const key of going) {
|
|
1025
1398
|
index.remove(key);
|
|
1026
1399
|
}
|
|
1400
|
+
relation.removed += going.size;
|
|
1027
1401
|
if (index.size === 0) {
|
|
1028
1402
|
relation.tuples = [];
|
|
1029
1403
|
index.clear();
|
|
@@ -1059,6 +1433,7 @@ var Database = class {
|
|
|
1059
1433
|
return 0;
|
|
1060
1434
|
}
|
|
1061
1435
|
const removed = relation.tuples.length;
|
|
1436
|
+
relation.removed += removed;
|
|
1062
1437
|
relation.tuples = [];
|
|
1063
1438
|
relation.index.clear();
|
|
1064
1439
|
relation.columns.length = 0;
|
|
@@ -1067,12 +1442,15 @@ var Database = class {
|
|
|
1067
1442
|
return removed;
|
|
1068
1443
|
}
|
|
1069
1444
|
size(relationName) {
|
|
1070
|
-
|
|
1445
|
+
this.readLog?.readWhole(relationName);
|
|
1446
|
+
return sizeOf(this, relationName);
|
|
1071
1447
|
}
|
|
1072
1448
|
relationNames() {
|
|
1073
1449
|
return [...this.store.keys()];
|
|
1074
1450
|
}
|
|
1075
1451
|
};
|
|
1452
|
+
var tuplesOf = (db, relation) => db.relationOf(relation)?.tuples ?? [];
|
|
1453
|
+
var sizeOf = (db, relation) => db.relationOf(relation)?.tuples.length ?? 0;
|
|
1076
1454
|
function rulesDeriving(rules, relations) {
|
|
1077
1455
|
const wanted = new Set(relations);
|
|
1078
1456
|
const pending = [...relations];
|
|
@@ -1268,6 +1646,7 @@ function evaluateRule(db, deltas, r, deltaIndex, budget) {
|
|
|
1268
1646
|
}
|
|
1269
1647
|
const byValue = newFactsByColumn(rows, start.column, budget);
|
|
1270
1648
|
const first = body[start.index];
|
|
1649
|
+
noteSharedLookups(db, first, start, rows);
|
|
1271
1650
|
for (const tuple of start.source) {
|
|
1272
1651
|
budget.examined++;
|
|
1273
1652
|
if (budget.examined > budget.limit) {
|
|
@@ -1299,7 +1678,7 @@ function smallerStart(db, body, deltaAt, rows) {
|
|
|
1299
1678
|
if (column === -1) {
|
|
1300
1679
|
continue;
|
|
1301
1680
|
}
|
|
1302
|
-
const source = narrowedSource(db, literal, null) ?? db
|
|
1681
|
+
const source = narrowedSource(db, literal, null) ?? tuplesOf(db, literal.relation);
|
|
1303
1682
|
const worthIt = indexed?.has(column) ? source.length < rows.length : source.length * 2 < rows.length;
|
|
1304
1683
|
if (worthIt && (best === null || source.length < best.source.length)) {
|
|
1305
1684
|
best = {
|
|
@@ -1357,6 +1736,7 @@ function nextLiteral(db, body, taken, bindings) {
|
|
|
1357
1736
|
continue;
|
|
1358
1737
|
}
|
|
1359
1738
|
if (source.length === 0) {
|
|
1739
|
+
noteLookup(db, literal, bindings);
|
|
1360
1740
|
return -1;
|
|
1361
1741
|
}
|
|
1362
1742
|
if (narrowest === null || source.length < narrowest.length) {
|
|
@@ -1365,6 +1745,7 @@ function nextLiteral(db, body, taken, bindings) {
|
|
|
1365
1745
|
}
|
|
1366
1746
|
}
|
|
1367
1747
|
if (narrowest !== null) {
|
|
1748
|
+
noteLookup(db, body[pick], bindings);
|
|
1368
1749
|
nextSource = narrowedFurther(db, body[pick], bindings, narrowest);
|
|
1369
1750
|
return nextSource.length === 0 ? -1 : pick;
|
|
1370
1751
|
}
|
|
@@ -1376,6 +1757,59 @@ function nextLiteral(db, body, taken, bindings) {
|
|
|
1376
1757
|
}
|
|
1377
1758
|
return body.findIndex((_, index) => !(taken & 1 << index));
|
|
1378
1759
|
}
|
|
1760
|
+
function noteLookup(db, literal, bindings) {
|
|
1761
|
+
const reads = db.readLog?.readsOf(literal.relation) ?? null;
|
|
1762
|
+
if (reads === null) {
|
|
1763
|
+
return;
|
|
1764
|
+
}
|
|
1765
|
+
for (let column2 = 0; column2 < literal.terms.length; column2++) {
|
|
1766
|
+
const term2 = literal.terms[column2];
|
|
1767
|
+
if (term2.type === "variable") {
|
|
1768
|
+
const value = boundValue(bindings, term2.name);
|
|
1769
|
+
if (value !== void 0) {
|
|
1770
|
+
noteIn(reads, column2, value);
|
|
1771
|
+
return;
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
const column = fewestFactsUnderConstant(db, literal);
|
|
1776
|
+
const term = literal.terms[column];
|
|
1777
|
+
if (term?.type === "constant") {
|
|
1778
|
+
noteIn(reads, column, term.value);
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
function fewestFactsUnderConstant(db, literal) {
|
|
1782
|
+
const relation = db.relationOf(literal.relation);
|
|
1783
|
+
let best = -1;
|
|
1784
|
+
let bestFacts = Number.POSITIVE_INFINITY;
|
|
1785
|
+
for (let column = 0; column < literal.terms.length; column++) {
|
|
1786
|
+
const term = literal.terms[column];
|
|
1787
|
+
if (term.type !== "constant") {
|
|
1788
|
+
continue;
|
|
1789
|
+
}
|
|
1790
|
+
const facts = relation === void 0 ? 0 : bucketSize(relation, column, term.value);
|
|
1791
|
+
if (facts < bestFacts) {
|
|
1792
|
+
best = column;
|
|
1793
|
+
bestFacts = facts;
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
return best;
|
|
1797
|
+
}
|
|
1798
|
+
function noteSharedLookups(db, first, start, rows) {
|
|
1799
|
+
const reads = db.readLog?.readsOf(first.relation) ?? null;
|
|
1800
|
+
if (reads === null) {
|
|
1801
|
+
return;
|
|
1802
|
+
}
|
|
1803
|
+
const column = first.terms.findIndex(
|
|
1804
|
+
(term) => term.type === "variable" && term.name === start.variable
|
|
1805
|
+
);
|
|
1806
|
+
for (const row of rows) {
|
|
1807
|
+
const value = row[start.column];
|
|
1808
|
+
if (value !== void 0) {
|
|
1809
|
+
noteIn(reads, column, value);
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1379
1813
|
function narrowedSource(db, literal, bindings) {
|
|
1380
1814
|
const relation = db.relationOf(literal.relation);
|
|
1381
1815
|
let narrowest = null;
|
|
@@ -1427,7 +1861,11 @@ function constantColumnsOf(literal) {
|
|
|
1427
1861
|
values.push(term.value);
|
|
1428
1862
|
}
|
|
1429
1863
|
});
|
|
1430
|
-
known = columns.length === 0 ? null : {
|
|
1864
|
+
known = columns.length === 0 ? null : {
|
|
1865
|
+
columns,
|
|
1866
|
+
values,
|
|
1867
|
+
bucketKey: deltaBucketKey(literal.relation, columns[0])
|
|
1868
|
+
};
|
|
1431
1869
|
constantColumns.set(literal, known);
|
|
1432
1870
|
}
|
|
1433
1871
|
return known;
|
|
@@ -1440,7 +1878,7 @@ function hasNoNewRows(deltas, literal) {
|
|
|
1440
1878
|
if (rows.length === 0 || constants === null) {
|
|
1441
1879
|
return rows.length === 0;
|
|
1442
1880
|
}
|
|
1443
|
-
const buckets = deltaBuckets.get(deltas)?.get(
|
|
1881
|
+
const buckets = deltaBuckets.get(deltas)?.get(constants.bucketKey);
|
|
1444
1882
|
return buckets !== void 0 && !buckets.has(constants.values[0]);
|
|
1445
1883
|
}
|
|
1446
1884
|
function deltaRows(deltas, literal, budget) {
|
|
@@ -1455,7 +1893,7 @@ function deltaRows(deltas, literal, budget) {
|
|
|
1455
1893
|
deltaBuckets.set(deltas, byColumn);
|
|
1456
1894
|
}
|
|
1457
1895
|
const column = constants.columns[0];
|
|
1458
|
-
const key =
|
|
1896
|
+
const key = constants.bucketKey;
|
|
1459
1897
|
let buckets = byColumn.get(key);
|
|
1460
1898
|
if (buckets === void 0) {
|
|
1461
1899
|
buckets = /* @__PURE__ */ new Map();
|
|
@@ -1600,7 +2038,7 @@ function canResume(rules, state) {
|
|
|
1600
2038
|
function factsSince(db, marks) {
|
|
1601
2039
|
const added = /* @__PURE__ */ new Map();
|
|
1602
2040
|
for (const name of db.relationNames()) {
|
|
1603
|
-
const all = db
|
|
2041
|
+
const all = tuplesOf(db, name);
|
|
1604
2042
|
const mark = marks.get(name) ?? 0;
|
|
1605
2043
|
if (all.length > mark) {
|
|
1606
2044
|
added.set(name, all.slice(mark));
|
|
@@ -1611,7 +2049,7 @@ function factsSince(db, marks) {
|
|
|
1611
2049
|
function currentMarks(db) {
|
|
1612
2050
|
const marks = /* @__PURE__ */ new Map();
|
|
1613
2051
|
for (const name of db.relationNames()) {
|
|
1614
|
-
marks.set(name, db
|
|
2052
|
+
marks.set(name, sizeOf(db, name));
|
|
1615
2053
|
}
|
|
1616
2054
|
return marks;
|
|
1617
2055
|
}
|
|
@@ -1673,9 +2111,14 @@ function shapeOf(rules) {
|
|
|
1673
2111
|
return shape;
|
|
1674
2112
|
}
|
|
1675
2113
|
function couldProduce(db, rule2) {
|
|
1676
|
-
|
|
1677
|
-
(literal) => literal.negated
|
|
2114
|
+
const empty = rule2.body.find(
|
|
2115
|
+
(literal) => !literal.negated && sizeOf(db, literal.relation) === 0
|
|
1678
2116
|
);
|
|
2117
|
+
if (empty === void 0) {
|
|
2118
|
+
return true;
|
|
2119
|
+
}
|
|
2120
|
+
db.readLog?.readWhole(empty.relation);
|
|
2121
|
+
return false;
|
|
1679
2122
|
}
|
|
1680
2123
|
function storedMergeFor(algebra) {
|
|
1681
2124
|
return (stored, incoming) => {
|
|
@@ -1724,6 +2167,17 @@ function runRules(db, rules, algebra, given) {
|
|
|
1724
2167
|
};
|
|
1725
2168
|
const mergeStored = algebra === void 0 ? void 0 : storedMergeFor(algebra);
|
|
1726
2169
|
const marks = canResume(rules, state) ? state.marks : void 0;
|
|
2170
|
+
const producible = /* @__PURE__ */ new Set();
|
|
2171
|
+
const canProduce = (r) => {
|
|
2172
|
+
if (producible.has(r)) {
|
|
2173
|
+
return true;
|
|
2174
|
+
}
|
|
2175
|
+
if (!couldProduce(db, r)) {
|
|
2176
|
+
return false;
|
|
2177
|
+
}
|
|
2178
|
+
producible.add(r);
|
|
2179
|
+
return true;
|
|
2180
|
+
};
|
|
1727
2181
|
const runStratum = (stratum) => {
|
|
1728
2182
|
let delta = /* @__PURE__ */ new Map();
|
|
1729
2183
|
const record = (relation, tuple) => {
|
|
@@ -1766,7 +2220,7 @@ function runRules(db, rules, algebra, given) {
|
|
|
1766
2220
|
return;
|
|
1767
2221
|
}
|
|
1768
2222
|
const startedAt2 = performance.now();
|
|
1769
|
-
const before = db
|
|
2223
|
+
const before = sizeOf(db, r.head.relation);
|
|
1770
2224
|
const readBefore = budget.examined;
|
|
1771
2225
|
deriveInto(r, seed, deltaIndex);
|
|
1772
2226
|
chargeRule(
|
|
@@ -1774,14 +2228,14 @@ function runRules(db, rules, algebra, given) {
|
|
|
1774
2228
|
r.head.relation,
|
|
1775
2229
|
r.body.map(literalName),
|
|
1776
2230
|
performance.now() - startedAt2,
|
|
1777
|
-
db
|
|
2231
|
+
sizeOf(db, r.head.relation) - before,
|
|
1778
2232
|
budget.examined - readBefore
|
|
1779
2233
|
);
|
|
1780
2234
|
};
|
|
1781
2235
|
const applyDelta = (seed, derivedOnly) => {
|
|
1782
2236
|
for (const at of rulesReading(stratum, seed, derivedOnly)) {
|
|
1783
2237
|
const r = stratum.rules[at];
|
|
1784
|
-
if (!
|
|
2238
|
+
if (!canProduce(r)) {
|
|
1785
2239
|
continue;
|
|
1786
2240
|
}
|
|
1787
2241
|
const positives = positivesOf(r);
|
|
@@ -1799,13 +2253,13 @@ function runRules(db, rules, algebra, given) {
|
|
|
1799
2253
|
};
|
|
1800
2254
|
if (marks === void 0 || marks === null) {
|
|
1801
2255
|
for (const r of stratum.rules) {
|
|
1802
|
-
if (!
|
|
2256
|
+
if (!canProduce(r)) {
|
|
1803
2257
|
continue;
|
|
1804
2258
|
}
|
|
1805
2259
|
const all = /* @__PURE__ */ new Map();
|
|
1806
2260
|
for (const l of r.body) {
|
|
1807
2261
|
if (!l.negated) {
|
|
1808
|
-
all.set(l.relation, db
|
|
2262
|
+
all.set(l.relation, tuplesOf(db, l.relation));
|
|
1809
2263
|
}
|
|
1810
2264
|
}
|
|
1811
2265
|
runOneRule(r, all, -1);
|
|
@@ -1842,7 +2296,7 @@ function runRules(db, rules, algebra, given) {
|
|
|
1842
2296
|
state.marks = currentMarks(db);
|
|
1843
2297
|
if (isProfiling()) {
|
|
1844
2298
|
chargeRelationSizes(
|
|
1845
|
-
db.relationNames().map((name) => [name, db
|
|
2299
|
+
db.relationNames().map((name) => [name, sizeOf(db, name)]),
|
|
1846
2300
|
derivedRelations
|
|
1847
2301
|
);
|
|
1848
2302
|
}
|
|
@@ -1879,6 +2333,7 @@ function addTo(buckets, relation, tuple) {
|
|
|
1879
2333
|
formatProfile,
|
|
1880
2334
|
lit,
|
|
1881
2335
|
notLit,
|
|
2336
|
+
noteReads,
|
|
1882
2337
|
profileEvaluation,
|
|
1883
2338
|
profileEvaluationAsync,
|
|
1884
2339
|
proofOf,
|