@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.js
CHANGED
|
@@ -1,43 +1,91 @@
|
|
|
1
1
|
// src/factIndex.ts
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
var FactKey = class {
|
|
3
|
+
constructor(tuple) {
|
|
4
|
+
this.tuple = tuple;
|
|
5
|
+
}
|
|
6
|
+
tuple;
|
|
7
|
+
present = false;
|
|
8
|
+
tag = void 0;
|
|
9
|
+
};
|
|
10
|
+
var Branch = class {
|
|
11
|
+
/** The key of a tuple that ends at this column, as a shorter one in a relation of mixed arity does. */
|
|
12
|
+
here = void 0;
|
|
13
|
+
next = /* @__PURE__ */ new Map();
|
|
14
|
+
};
|
|
15
|
+
function sameFrom(a, b, from) {
|
|
16
|
+
if (a.length !== b.length) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
for (let at = from; at < a.length; at++) {
|
|
20
|
+
if (a[at] !== b[at]) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
function place(branch, key, depth) {
|
|
27
|
+
if (depth === key.tuple.length) {
|
|
28
|
+
branch.here = key;
|
|
29
|
+
} else {
|
|
30
|
+
branch.next.set(key.tuple[depth], key);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function soleKey(branch) {
|
|
34
|
+
if (branch.here !== void 0) {
|
|
35
|
+
return branch.next.size === 0 ? branch.here : void 0;
|
|
36
|
+
}
|
|
37
|
+
if (branch.next.size !== 1) {
|
|
38
|
+
return void 0;
|
|
39
|
+
}
|
|
40
|
+
const only = branch.next.values().next().value;
|
|
41
|
+
return only instanceof FactKey ? only : void 0;
|
|
42
|
+
}
|
|
7
43
|
var FactIndex = class {
|
|
8
44
|
/** How many facts are present. */
|
|
9
45
|
size = 0;
|
|
10
|
-
root =
|
|
46
|
+
root = new Branch();
|
|
11
47
|
/** This tuple's key here, whether or not the fact is present. */
|
|
12
48
|
key(tuple) {
|
|
13
|
-
let
|
|
14
|
-
for (let
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
node.next = next;
|
|
49
|
+
let branch = this.root;
|
|
50
|
+
for (let depth = 0; ; depth++) {
|
|
51
|
+
if (depth === tuple.length) {
|
|
52
|
+
branch.here ??= new FactKey(tuple);
|
|
53
|
+
return branch.here;
|
|
19
54
|
}
|
|
20
|
-
const atom = tuple[
|
|
21
|
-
|
|
55
|
+
const atom = tuple[depth];
|
|
56
|
+
const child = branch.next.get(atom);
|
|
22
57
|
if (child === void 0) {
|
|
23
|
-
|
|
24
|
-
next.set(atom,
|
|
58
|
+
const key = new FactKey(tuple);
|
|
59
|
+
branch.next.set(atom, key);
|
|
60
|
+
return key;
|
|
25
61
|
}
|
|
26
|
-
|
|
62
|
+
if (child instanceof Branch) {
|
|
63
|
+
branch = child;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (sameFrom(child.tuple, tuple, depth + 1)) {
|
|
67
|
+
return child;
|
|
68
|
+
}
|
|
69
|
+
const split = new Branch();
|
|
70
|
+
place(split, child, depth + 1);
|
|
71
|
+
branch.next.set(atom, split);
|
|
72
|
+
branch = split;
|
|
27
73
|
}
|
|
28
|
-
return node;
|
|
29
74
|
}
|
|
30
75
|
/** The key of a fact this relation has, or undefined when it has none. */
|
|
31
76
|
find(tuple) {
|
|
32
|
-
let
|
|
33
|
-
for (let
|
|
34
|
-
const child =
|
|
77
|
+
let branch = this.root;
|
|
78
|
+
for (let depth = 0; ; depth++) {
|
|
79
|
+
const child = depth === tuple.length ? branch.here : branch.next.get(tuple[depth]);
|
|
35
80
|
if (child === void 0) {
|
|
36
81
|
return void 0;
|
|
37
82
|
}
|
|
38
|
-
|
|
83
|
+
if (child instanceof Branch) {
|
|
84
|
+
branch = child;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
return child.present && sameFrom(child.tuple, tuple, depth + 1) ? child : void 0;
|
|
39
88
|
}
|
|
40
|
-
return node.present ? node : void 0;
|
|
41
89
|
}
|
|
42
90
|
has(key) {
|
|
43
91
|
return key.present;
|
|
@@ -61,32 +109,45 @@ var FactIndex = class {
|
|
|
61
109
|
this.size -= 1;
|
|
62
110
|
}
|
|
63
111
|
/**
|
|
64
|
-
* Drop
|
|
65
|
-
*
|
|
66
|
-
*
|
|
112
|
+
* Drop a removed tuple's key, and any branch left with one key to
|
|
113
|
+
* tell apart. Without this a relation that is retracted from over and
|
|
114
|
+
* over keeps a key per tuple it ever had.
|
|
67
115
|
*/
|
|
68
116
|
prune(tuple) {
|
|
69
|
-
const path = [
|
|
70
|
-
let
|
|
71
|
-
|
|
72
|
-
|
|
117
|
+
const path = [];
|
|
118
|
+
let branch = this.root;
|
|
119
|
+
let depth = 0;
|
|
120
|
+
for (; ; depth++) {
|
|
121
|
+
path.push(branch);
|
|
122
|
+
const child = depth === tuple.length ? branch.here : branch.next.get(tuple[depth]);
|
|
73
123
|
if (child === void 0) {
|
|
74
124
|
return;
|
|
75
125
|
}
|
|
76
|
-
|
|
77
|
-
|
|
126
|
+
if (child instanceof Branch) {
|
|
127
|
+
branch = child;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (child.present || !sameFrom(child.tuple, tuple, depth + 1)) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
if (depth === tuple.length) {
|
|
136
|
+
branch.here = void 0;
|
|
137
|
+
} else {
|
|
138
|
+
branch.next.delete(tuple[depth]);
|
|
78
139
|
}
|
|
79
|
-
for (let
|
|
80
|
-
const
|
|
81
|
-
if (
|
|
140
|
+
for (let at = path.length - 1; at > 0; at--) {
|
|
141
|
+
const sole = soleKey(path[at]);
|
|
142
|
+
if (sole === void 0) {
|
|
82
143
|
return;
|
|
83
144
|
}
|
|
84
|
-
path[
|
|
145
|
+
path[at - 1].next.set(tuple[at - 1], sole);
|
|
85
146
|
}
|
|
86
147
|
}
|
|
87
148
|
/** Drop every fact, and whatever was kept in order to find them. */
|
|
88
149
|
clear() {
|
|
89
|
-
this.root =
|
|
150
|
+
this.root = new Branch();
|
|
90
151
|
this.size = 0;
|
|
91
152
|
}
|
|
92
153
|
};
|
|
@@ -523,6 +584,24 @@ function formatProfile(profile) {
|
|
|
523
584
|
}
|
|
524
585
|
|
|
525
586
|
// src/relation.ts
|
|
587
|
+
var isLone = (bucket) => typeof bucket[0] !== "object";
|
|
588
|
+
var NO_TUPLES = [];
|
|
589
|
+
function tuplesIn(bucket) {
|
|
590
|
+
if (bucket === void 0) {
|
|
591
|
+
return NO_TUPLES;
|
|
592
|
+
}
|
|
593
|
+
return isLone(bucket) ? [bucket] : bucket;
|
|
594
|
+
}
|
|
595
|
+
function fileUnder(buckets, key, tuple) {
|
|
596
|
+
const bucket = buckets.get(key);
|
|
597
|
+
if (bucket === void 0) {
|
|
598
|
+
buckets.set(key, tuple);
|
|
599
|
+
} else if (isLone(bucket)) {
|
|
600
|
+
buckets.set(key, [bucket, tuple]);
|
|
601
|
+
} else {
|
|
602
|
+
bucket.push(tuple);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
526
605
|
var sameTuple = (a, b) => a.length === b.length && a.every((atom, at) => atom === b[at]);
|
|
527
606
|
function storedTuplesOf(relation, facts) {
|
|
528
607
|
const column = relation.columns.findIndex((buckets2) => buckets2 !== void 0);
|
|
@@ -531,19 +610,24 @@ function storedTuplesOf(relation, facts) {
|
|
|
531
610
|
return null;
|
|
532
611
|
}
|
|
533
612
|
const stored = facts.map(
|
|
534
|
-
(fact) => buckets.get(fact[column])
|
|
613
|
+
(fact) => tuplesIn(buckets.get(fact[column])).find(
|
|
614
|
+
(tuple) => sameTuple(tuple, fact)
|
|
615
|
+
)
|
|
535
616
|
);
|
|
536
617
|
return stored.every((tuple) => tuple !== void 0) ? stored : null;
|
|
537
618
|
}
|
|
538
619
|
function removeFrom(buckets, key, tuple) {
|
|
539
620
|
const bucket = buckets.get(key);
|
|
540
|
-
|
|
541
|
-
if (bucket === void 0 || at === -1) {
|
|
621
|
+
if (bucket === void 0) {
|
|
542
622
|
return;
|
|
543
623
|
}
|
|
544
|
-
bucket
|
|
545
|
-
if (bucket.length === 0) {
|
|
624
|
+
if (isLone(bucket)) {
|
|
546
625
|
buckets.delete(key);
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
bucket.splice(bucket.indexOf(tuple), 1);
|
|
629
|
+
if (bucket.length === 1) {
|
|
630
|
+
buckets.set(key, bucket[0]);
|
|
547
631
|
}
|
|
548
632
|
}
|
|
549
633
|
function removeTuples(relation, stored) {
|
|
@@ -595,7 +679,7 @@ function addToCombined(index, tuple) {
|
|
|
595
679
|
}
|
|
596
680
|
level = next;
|
|
597
681
|
}
|
|
598
|
-
|
|
682
|
+
fileUnder(
|
|
599
683
|
level,
|
|
600
684
|
tuple[index.columns[last]],
|
|
601
685
|
tuple
|
|
@@ -614,10 +698,10 @@ function combinedBucketIn(relation, mask, values) {
|
|
|
614
698
|
for (const value of values) {
|
|
615
699
|
level = level.get(value);
|
|
616
700
|
if (level === void 0) {
|
|
617
|
-
return
|
|
701
|
+
return NO_TUPLES;
|
|
618
702
|
}
|
|
619
703
|
}
|
|
620
|
-
return level;
|
|
704
|
+
return tuplesIn(level);
|
|
621
705
|
}
|
|
622
706
|
function addToBucket(buckets, key, tuple) {
|
|
623
707
|
const bucket = buckets.get(key);
|
|
@@ -627,19 +711,291 @@ function addToBucket(buckets, key, tuple) {
|
|
|
627
711
|
bucket.push(tuple);
|
|
628
712
|
}
|
|
629
713
|
}
|
|
630
|
-
function
|
|
714
|
+
function columnIndex(relation, column) {
|
|
631
715
|
let buckets = relation.columns[column];
|
|
632
716
|
if (buckets === void 0) {
|
|
633
717
|
buckets = /* @__PURE__ */ new Map();
|
|
634
718
|
for (const tuple of relation.tuples) {
|
|
635
719
|
const at = tuple[column];
|
|
636
720
|
if (at !== void 0) {
|
|
637
|
-
|
|
721
|
+
fileUnder(buckets, at, tuple);
|
|
638
722
|
}
|
|
639
723
|
}
|
|
640
724
|
relation.columns[column] = buckets;
|
|
641
725
|
}
|
|
642
|
-
return buckets
|
|
726
|
+
return buckets;
|
|
727
|
+
}
|
|
728
|
+
function bucketIn(relation, column, value) {
|
|
729
|
+
return tuplesIn(columnIndex(relation, column).get(value));
|
|
730
|
+
}
|
|
731
|
+
function bucketSize(relation, column, value) {
|
|
732
|
+
const bucket = columnIndex(relation, column).get(value);
|
|
733
|
+
if (bucket === void 0) {
|
|
734
|
+
return 0;
|
|
735
|
+
}
|
|
736
|
+
return isLone(bucket) ? 1 : bucket.length;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// src/reads.ts
|
|
740
|
+
function noteIn(reads, column, value) {
|
|
741
|
+
if (reads.whole) {
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
let values = reads.columns[column];
|
|
745
|
+
if (values === void 0) {
|
|
746
|
+
values = /* @__PURE__ */ new Set();
|
|
747
|
+
reads.columns[column] = values;
|
|
748
|
+
}
|
|
749
|
+
values.add(value);
|
|
750
|
+
}
|
|
751
|
+
var ReadLog = class {
|
|
752
|
+
constructor(skipped) {
|
|
753
|
+
this.skipped = skipped;
|
|
754
|
+
}
|
|
755
|
+
skipped;
|
|
756
|
+
/** Null for a relation the caller said to leave out. */
|
|
757
|
+
byRelation = /* @__PURE__ */ new Map();
|
|
758
|
+
lookedUp(relation, column, value) {
|
|
759
|
+
const reads = this.readsOf(relation);
|
|
760
|
+
if (reads !== null) {
|
|
761
|
+
noteIn(reads, column, value);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
readWhole(relation) {
|
|
765
|
+
const reads = this.readsOf(relation);
|
|
766
|
+
if (reads !== null) {
|
|
767
|
+
reads.whole = true;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
/** Note everything an inner log noted, for an answer cached inside another. */
|
|
771
|
+
absorb(inner) {
|
|
772
|
+
for (const [relation, reads] of inner.byRelation) {
|
|
773
|
+
if (reads === null) {
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
if (reads.whole) {
|
|
777
|
+
this.readWhole(relation);
|
|
778
|
+
continue;
|
|
779
|
+
}
|
|
780
|
+
reads.columns.forEach((values, column) => {
|
|
781
|
+
for (const value of values ?? []) {
|
|
782
|
+
this.lookedUp(relation, column, value);
|
|
783
|
+
}
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* The reads with what each one finds in `db` now. One question looks
|
|
789
|
+
* up the same values in many columns, so each value is kept once, and
|
|
790
|
+
* each column keeps a bit per value saying whether it was looked up
|
|
791
|
+
* there.
|
|
792
|
+
*/
|
|
793
|
+
counted(db) {
|
|
794
|
+
const names = [];
|
|
795
|
+
const groups = [];
|
|
796
|
+
const columns = [];
|
|
797
|
+
const lookedUp = [];
|
|
798
|
+
const positions = /* @__PURE__ */ new Map();
|
|
799
|
+
const values = [];
|
|
800
|
+
const addGroup = (column, seen) => {
|
|
801
|
+
groups.push(names.length - 1);
|
|
802
|
+
columns.push(column);
|
|
803
|
+
lookedUp.push(seen);
|
|
804
|
+
for (const value of seen) {
|
|
805
|
+
if (!positions.has(value)) {
|
|
806
|
+
positions.set(value, values.length);
|
|
807
|
+
values.push(value);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
};
|
|
811
|
+
for (const [relation, reads] of this.byRelation) {
|
|
812
|
+
if (reads === null) {
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
names.push(relation);
|
|
816
|
+
if (reads.whole) {
|
|
817
|
+
addGroup(WHOLE, NONE);
|
|
818
|
+
continue;
|
|
819
|
+
}
|
|
820
|
+
reads.columns.forEach((seen, column) => {
|
|
821
|
+
if (seen !== void 0) {
|
|
822
|
+
addGroup(column, seen);
|
|
823
|
+
}
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
const words = Math.ceil(values.length / 32);
|
|
827
|
+
const bits = new Uint32Array(groups.length * words);
|
|
828
|
+
let found = 0;
|
|
829
|
+
lookedUp.forEach((seen, group) => {
|
|
830
|
+
const relation = db.relationOf(names[groups[group]]);
|
|
831
|
+
const column = columns[group];
|
|
832
|
+
if (column === WHOLE) {
|
|
833
|
+
found += relation?.tuples.length ?? 0;
|
|
834
|
+
}
|
|
835
|
+
for (const value of seen) {
|
|
836
|
+
const at = positions.get(value);
|
|
837
|
+
bits[group * words + (at >>> 5)] |= 1 << (at & 31);
|
|
838
|
+
found += relation === void 0 ? 0 : bucketSize(relation, column, value);
|
|
839
|
+
}
|
|
840
|
+
});
|
|
841
|
+
return new ReadsNoted(
|
|
842
|
+
db,
|
|
843
|
+
this.skipped,
|
|
844
|
+
{ names, groups, columns, values, bits },
|
|
845
|
+
found
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
/** @internal Null for a relation the caller said to leave out. */
|
|
849
|
+
readsOf(relation) {
|
|
850
|
+
let reads = this.byRelation.get(relation);
|
|
851
|
+
if (reads === void 0) {
|
|
852
|
+
reads = this.skipped.has(relation) ? null : { whole: false, columns: [] };
|
|
853
|
+
this.byRelation.set(relation, reads);
|
|
854
|
+
}
|
|
855
|
+
return reads;
|
|
856
|
+
}
|
|
857
|
+
};
|
|
858
|
+
var WHOLE = -1;
|
|
859
|
+
var NONE = /* @__PURE__ */ new Set();
|
|
860
|
+
var ReadsNoted = class {
|
|
861
|
+
constructor(db, skipped, lookups, found) {
|
|
862
|
+
this.skipped = skipped;
|
|
863
|
+
this.found = found;
|
|
864
|
+
this.names = lookups.names;
|
|
865
|
+
this.groups = lookups.groups;
|
|
866
|
+
this.columns = lookups.columns;
|
|
867
|
+
this.values = lookups.values;
|
|
868
|
+
this.bits = lookups.bits;
|
|
869
|
+
this.words = Math.ceil(lookups.values.length / 32);
|
|
870
|
+
this.relations = lookups.names.map((name) => db.relationOf(name));
|
|
871
|
+
this.removed = this.sumOver(db, removedFrom);
|
|
872
|
+
this.facts = this.sumOver(db, factsIn);
|
|
873
|
+
}
|
|
874
|
+
skipped;
|
|
875
|
+
found;
|
|
876
|
+
names;
|
|
877
|
+
groups;
|
|
878
|
+
columns;
|
|
879
|
+
values;
|
|
880
|
+
bits;
|
|
881
|
+
words;
|
|
882
|
+
removed;
|
|
883
|
+
/** The facts in every relation read, when the counts last matched. */
|
|
884
|
+
facts;
|
|
885
|
+
/** Each relation read, found by name the first time a check needs it. */
|
|
886
|
+
relations;
|
|
887
|
+
/**
|
|
888
|
+
* Whether every read would find what it found then. A fact left in a
|
|
889
|
+
* skipped relation would join a question asked now without anything
|
|
890
|
+
* here having looked it up, so one there makes the answer no. The
|
|
891
|
+
* counts are taken again only when a relation read has grown.
|
|
892
|
+
*/
|
|
893
|
+
unchangedIn(db) {
|
|
894
|
+
if (!allEmpty(db, this.skipped)) {
|
|
895
|
+
return false;
|
|
896
|
+
}
|
|
897
|
+
if (this.sumOver(db, removedFrom) !== this.removed) {
|
|
898
|
+
return false;
|
|
899
|
+
}
|
|
900
|
+
const facts = this.sumOver(db, factsIn);
|
|
901
|
+
if (facts === this.facts) {
|
|
902
|
+
return true;
|
|
903
|
+
}
|
|
904
|
+
if (this.countIn(db, this.found) !== this.found) {
|
|
905
|
+
return false;
|
|
906
|
+
}
|
|
907
|
+
this.facts = facts;
|
|
908
|
+
return true;
|
|
909
|
+
}
|
|
910
|
+
relationAt(db, at) {
|
|
911
|
+
let relation = this.relations[at];
|
|
912
|
+
if (relation === void 0) {
|
|
913
|
+
relation = db.relationOf(this.names[at]);
|
|
914
|
+
this.relations[at] = relation;
|
|
915
|
+
}
|
|
916
|
+
return relation;
|
|
917
|
+
}
|
|
918
|
+
sumOver(db, measure) {
|
|
919
|
+
let sum = 0;
|
|
920
|
+
for (let at = 0; at < this.names.length; at++) {
|
|
921
|
+
const relation = this.relationAt(db, at);
|
|
922
|
+
sum += relation === void 0 ? 0 : measure(relation);
|
|
923
|
+
}
|
|
924
|
+
return sum;
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* The facts the reads find, stopping once past `limit`. With nothing
|
|
928
|
+
* retracted every count only grows, so passing the old total already
|
|
929
|
+
* says that one of them grew.
|
|
930
|
+
*/
|
|
931
|
+
countIn(db, limit) {
|
|
932
|
+
let found = 0;
|
|
933
|
+
for (let group = 0; group < this.groups.length && found <= limit; group++) {
|
|
934
|
+
const relation = this.relationAt(db, this.groups[group]);
|
|
935
|
+
const column = this.columns[group];
|
|
936
|
+
if (column === WHOLE) {
|
|
937
|
+
found += relation?.tuples.length ?? 0;
|
|
938
|
+
} else if (relation !== void 0) {
|
|
939
|
+
found += this.countGroup(relation, group, column);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
return found;
|
|
943
|
+
}
|
|
944
|
+
countGroup(relation, group, column) {
|
|
945
|
+
let found = 0;
|
|
946
|
+
const first = group * this.words;
|
|
947
|
+
for (let word = 0; word < this.words; word++) {
|
|
948
|
+
let left = this.bits[first + word];
|
|
949
|
+
while (left !== 0) {
|
|
950
|
+
const bit = 31 - Math.clz32(left & -left);
|
|
951
|
+
left &= left - 1;
|
|
952
|
+
const value = this.values[word * 32 + bit];
|
|
953
|
+
found += bucketSize(relation, column, value);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
return found;
|
|
957
|
+
}
|
|
958
|
+
};
|
|
959
|
+
var removedFrom = (relation) => relation.removed;
|
|
960
|
+
var factsIn = (relation) => relation.tuples.length;
|
|
961
|
+
var skippedIn = /* @__PURE__ */ new WeakMap();
|
|
962
|
+
function allEmpty(db, skipped) {
|
|
963
|
+
let bySet = skippedIn.get(db);
|
|
964
|
+
if (bySet === void 0) {
|
|
965
|
+
bySet = /* @__PURE__ */ new WeakMap();
|
|
966
|
+
skippedIn.set(db, bySet);
|
|
967
|
+
}
|
|
968
|
+
let known = bySet.get(skipped);
|
|
969
|
+
if (known === void 0) {
|
|
970
|
+
known = { found: [], missing: [...skipped] };
|
|
971
|
+
bySet.set(skipped, known);
|
|
972
|
+
}
|
|
973
|
+
if (known.missing.length > 0) {
|
|
974
|
+
const stillMissing = [];
|
|
975
|
+
for (const name of known.missing) {
|
|
976
|
+
const relation = db.relationOf(name);
|
|
977
|
+
if (relation === void 0) {
|
|
978
|
+
stillMissing.push(name);
|
|
979
|
+
} else {
|
|
980
|
+
known.found.push(relation);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
known.missing = stillMissing;
|
|
984
|
+
}
|
|
985
|
+
return known.found.every((relation) => relation.tuples.length === 0);
|
|
986
|
+
}
|
|
987
|
+
function noteReads(db, skipped, work) {
|
|
988
|
+
const outer = db.readLog;
|
|
989
|
+
const log = new ReadLog(skipped);
|
|
990
|
+
const emptyAtStart = allEmpty(db, skipped);
|
|
991
|
+
db.readLog = log;
|
|
992
|
+
try {
|
|
993
|
+
const value = work();
|
|
994
|
+
return { value, reads: emptyAtStart ? log.counted(db) : null };
|
|
995
|
+
} finally {
|
|
996
|
+
db.readLog = outer;
|
|
997
|
+
outer?.absorb(log);
|
|
998
|
+
}
|
|
643
999
|
}
|
|
644
1000
|
|
|
645
1001
|
// src/stratum.ts
|
|
@@ -665,7 +1021,7 @@ function planStratum(rules) {
|
|
|
665
1021
|
}
|
|
666
1022
|
return { rules, derived, readers, marked: new Uint8Array(rules.length) };
|
|
667
1023
|
}
|
|
668
|
-
var
|
|
1024
|
+
var NONE2 = [];
|
|
669
1025
|
function rulesReading(stratum, seed, derivedOnly) {
|
|
670
1026
|
const lists = [];
|
|
671
1027
|
for (const [relation, tuples] of seed) {
|
|
@@ -681,7 +1037,7 @@ function rulesReading(stratum, seed, derivedOnly) {
|
|
|
681
1037
|
}
|
|
682
1038
|
}
|
|
683
1039
|
if (lists.length === 0) {
|
|
684
|
-
return
|
|
1040
|
+
return NONE2;
|
|
685
1041
|
}
|
|
686
1042
|
if (lists.length === 1) {
|
|
687
1043
|
return lists[0];
|
|
@@ -851,6 +1207,8 @@ var ruleLabel = (r) => r.name ?? `${r.head.relation} :- ${r.body.map(literalName
|
|
|
851
1207
|
var FEW_REMOVED = 16;
|
|
852
1208
|
var Database = class {
|
|
853
1209
|
store = /* @__PURE__ */ new Map();
|
|
1210
|
+
/** @internal Where reads are noted while `noteReads` runs. */
|
|
1211
|
+
readLog = null;
|
|
854
1212
|
relation(name) {
|
|
855
1213
|
const existing = this.store.get(name);
|
|
856
1214
|
if (existing !== void 0) {
|
|
@@ -860,7 +1218,8 @@ var Database = class {
|
|
|
860
1218
|
index: new FactIndex(),
|
|
861
1219
|
tuples: [],
|
|
862
1220
|
columns: [],
|
|
863
|
-
combined: /* @__PURE__ */ new Map()
|
|
1221
|
+
combined: /* @__PURE__ */ new Map(),
|
|
1222
|
+
removed: 0
|
|
864
1223
|
};
|
|
865
1224
|
this.store.set(name, created);
|
|
866
1225
|
return created;
|
|
@@ -905,7 +1264,7 @@ var Database = class {
|
|
|
905
1264
|
const buckets = relation.columns[column];
|
|
906
1265
|
const value = tuple[column];
|
|
907
1266
|
if (buckets !== void 0 && value !== void 0) {
|
|
908
|
-
|
|
1267
|
+
fileUnder(buckets, value, tuple);
|
|
909
1268
|
}
|
|
910
1269
|
}
|
|
911
1270
|
for (const combined of relation.combined.values()) {
|
|
@@ -915,6 +1274,7 @@ var Database = class {
|
|
|
915
1274
|
}
|
|
916
1275
|
/** The tag stored for this fact, or undefined when there is none. */
|
|
917
1276
|
tagOf(relationName, tuple) {
|
|
1277
|
+
this.noteTupleRead(relationName, tuple);
|
|
918
1278
|
const relation = this.store.get(relationName);
|
|
919
1279
|
if (relation === void 0) {
|
|
920
1280
|
return void 0;
|
|
@@ -923,9 +1283,20 @@ var Database = class {
|
|
|
923
1283
|
return key === void 0 ? void 0 : relation.index.tagOf(key);
|
|
924
1284
|
}
|
|
925
1285
|
has(relationName, tuple) {
|
|
1286
|
+
this.noteTupleRead(relationName, tuple);
|
|
926
1287
|
return this.store.get(relationName)?.index.find(tuple) !== void 0;
|
|
927
1288
|
}
|
|
1289
|
+
/** A fact matching the whole tuple agrees on its first column, so that lookup is enough to note. */
|
|
1290
|
+
noteTupleRead(relationName, tuple) {
|
|
1291
|
+
const first = tuple[0];
|
|
1292
|
+
if (first === void 0) {
|
|
1293
|
+
this.readLog?.readWhole(relationName);
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
this.readLog?.lookedUp(relationName, 0, first);
|
|
1297
|
+
}
|
|
928
1298
|
facts(relationName) {
|
|
1299
|
+
this.readLog?.readWhole(relationName);
|
|
929
1300
|
return this.store.get(relationName)?.tuples ?? [];
|
|
930
1301
|
}
|
|
931
1302
|
/**
|
|
@@ -938,6 +1309,7 @@ var Database = class {
|
|
|
938
1309
|
* gets an index.
|
|
939
1310
|
*/
|
|
940
1311
|
lookup(relationName, column, value) {
|
|
1312
|
+
this.readLog?.lookedUp(relationName, column, value);
|
|
941
1313
|
const relation = this.store.get(relationName);
|
|
942
1314
|
return relation === void 0 ? [] : bucketIn(relation, column, value);
|
|
943
1315
|
}
|
|
@@ -973,6 +1345,7 @@ var Database = class {
|
|
|
973
1345
|
for (const key of going) {
|
|
974
1346
|
index.remove(key);
|
|
975
1347
|
}
|
|
1348
|
+
relation.removed += going.size;
|
|
976
1349
|
if (index.size === 0) {
|
|
977
1350
|
relation.tuples = [];
|
|
978
1351
|
index.clear();
|
|
@@ -1008,6 +1381,7 @@ var Database = class {
|
|
|
1008
1381
|
return 0;
|
|
1009
1382
|
}
|
|
1010
1383
|
const removed = relation.tuples.length;
|
|
1384
|
+
relation.removed += removed;
|
|
1011
1385
|
relation.tuples = [];
|
|
1012
1386
|
relation.index.clear();
|
|
1013
1387
|
relation.columns.length = 0;
|
|
@@ -1016,12 +1390,15 @@ var Database = class {
|
|
|
1016
1390
|
return removed;
|
|
1017
1391
|
}
|
|
1018
1392
|
size(relationName) {
|
|
1019
|
-
|
|
1393
|
+
this.readLog?.readWhole(relationName);
|
|
1394
|
+
return sizeOf(this, relationName);
|
|
1020
1395
|
}
|
|
1021
1396
|
relationNames() {
|
|
1022
1397
|
return [...this.store.keys()];
|
|
1023
1398
|
}
|
|
1024
1399
|
};
|
|
1400
|
+
var tuplesOf = (db, relation) => db.relationOf(relation)?.tuples ?? [];
|
|
1401
|
+
var sizeOf = (db, relation) => db.relationOf(relation)?.tuples.length ?? 0;
|
|
1025
1402
|
function rulesDeriving(rules, relations) {
|
|
1026
1403
|
const wanted = new Set(relations);
|
|
1027
1404
|
const pending = [...relations];
|
|
@@ -1217,6 +1594,7 @@ function evaluateRule(db, deltas, r, deltaIndex, budget) {
|
|
|
1217
1594
|
}
|
|
1218
1595
|
const byValue = newFactsByColumn(rows, start.column, budget);
|
|
1219
1596
|
const first = body[start.index];
|
|
1597
|
+
noteSharedLookups(db, first, start, rows);
|
|
1220
1598
|
for (const tuple of start.source) {
|
|
1221
1599
|
budget.examined++;
|
|
1222
1600
|
if (budget.examined > budget.limit) {
|
|
@@ -1248,7 +1626,7 @@ function smallerStart(db, body, deltaAt, rows) {
|
|
|
1248
1626
|
if (column === -1) {
|
|
1249
1627
|
continue;
|
|
1250
1628
|
}
|
|
1251
|
-
const source = narrowedSource(db, literal, null) ?? db
|
|
1629
|
+
const source = narrowedSource(db, literal, null) ?? tuplesOf(db, literal.relation);
|
|
1252
1630
|
const worthIt = indexed?.has(column) ? source.length < rows.length : source.length * 2 < rows.length;
|
|
1253
1631
|
if (worthIt && (best === null || source.length < best.source.length)) {
|
|
1254
1632
|
best = {
|
|
@@ -1306,6 +1684,7 @@ function nextLiteral(db, body, taken, bindings) {
|
|
|
1306
1684
|
continue;
|
|
1307
1685
|
}
|
|
1308
1686
|
if (source.length === 0) {
|
|
1687
|
+
noteLookup(db, literal, bindings);
|
|
1309
1688
|
return -1;
|
|
1310
1689
|
}
|
|
1311
1690
|
if (narrowest === null || source.length < narrowest.length) {
|
|
@@ -1314,6 +1693,7 @@ function nextLiteral(db, body, taken, bindings) {
|
|
|
1314
1693
|
}
|
|
1315
1694
|
}
|
|
1316
1695
|
if (narrowest !== null) {
|
|
1696
|
+
noteLookup(db, body[pick], bindings);
|
|
1317
1697
|
nextSource = narrowedFurther(db, body[pick], bindings, narrowest);
|
|
1318
1698
|
return nextSource.length === 0 ? -1 : pick;
|
|
1319
1699
|
}
|
|
@@ -1325,6 +1705,59 @@ function nextLiteral(db, body, taken, bindings) {
|
|
|
1325
1705
|
}
|
|
1326
1706
|
return body.findIndex((_, index) => !(taken & 1 << index));
|
|
1327
1707
|
}
|
|
1708
|
+
function noteLookup(db, literal, bindings) {
|
|
1709
|
+
const reads = db.readLog?.readsOf(literal.relation) ?? null;
|
|
1710
|
+
if (reads === null) {
|
|
1711
|
+
return;
|
|
1712
|
+
}
|
|
1713
|
+
for (let column2 = 0; column2 < literal.terms.length; column2++) {
|
|
1714
|
+
const term2 = literal.terms[column2];
|
|
1715
|
+
if (term2.type === "variable") {
|
|
1716
|
+
const value = boundValue(bindings, term2.name);
|
|
1717
|
+
if (value !== void 0) {
|
|
1718
|
+
noteIn(reads, column2, value);
|
|
1719
|
+
return;
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
const column = fewestFactsUnderConstant(db, literal);
|
|
1724
|
+
const term = literal.terms[column];
|
|
1725
|
+
if (term?.type === "constant") {
|
|
1726
|
+
noteIn(reads, column, term.value);
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
function fewestFactsUnderConstant(db, literal) {
|
|
1730
|
+
const relation = db.relationOf(literal.relation);
|
|
1731
|
+
let best = -1;
|
|
1732
|
+
let bestFacts = Number.POSITIVE_INFINITY;
|
|
1733
|
+
for (let column = 0; column < literal.terms.length; column++) {
|
|
1734
|
+
const term = literal.terms[column];
|
|
1735
|
+
if (term.type !== "constant") {
|
|
1736
|
+
continue;
|
|
1737
|
+
}
|
|
1738
|
+
const facts = relation === void 0 ? 0 : bucketSize(relation, column, term.value);
|
|
1739
|
+
if (facts < bestFacts) {
|
|
1740
|
+
best = column;
|
|
1741
|
+
bestFacts = facts;
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
return best;
|
|
1745
|
+
}
|
|
1746
|
+
function noteSharedLookups(db, first, start, rows) {
|
|
1747
|
+
const reads = db.readLog?.readsOf(first.relation) ?? null;
|
|
1748
|
+
if (reads === null) {
|
|
1749
|
+
return;
|
|
1750
|
+
}
|
|
1751
|
+
const column = first.terms.findIndex(
|
|
1752
|
+
(term) => term.type === "variable" && term.name === start.variable
|
|
1753
|
+
);
|
|
1754
|
+
for (const row of rows) {
|
|
1755
|
+
const value = row[start.column];
|
|
1756
|
+
if (value !== void 0) {
|
|
1757
|
+
noteIn(reads, column, value);
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1328
1761
|
function narrowedSource(db, literal, bindings) {
|
|
1329
1762
|
const relation = db.relationOf(literal.relation);
|
|
1330
1763
|
let narrowest = null;
|
|
@@ -1376,7 +1809,11 @@ function constantColumnsOf(literal) {
|
|
|
1376
1809
|
values.push(term.value);
|
|
1377
1810
|
}
|
|
1378
1811
|
});
|
|
1379
|
-
known = columns.length === 0 ? null : {
|
|
1812
|
+
known = columns.length === 0 ? null : {
|
|
1813
|
+
columns,
|
|
1814
|
+
values,
|
|
1815
|
+
bucketKey: deltaBucketKey(literal.relation, columns[0])
|
|
1816
|
+
};
|
|
1380
1817
|
constantColumns.set(literal, known);
|
|
1381
1818
|
}
|
|
1382
1819
|
return known;
|
|
@@ -1389,7 +1826,7 @@ function hasNoNewRows(deltas, literal) {
|
|
|
1389
1826
|
if (rows.length === 0 || constants === null) {
|
|
1390
1827
|
return rows.length === 0;
|
|
1391
1828
|
}
|
|
1392
|
-
const buckets = deltaBuckets.get(deltas)?.get(
|
|
1829
|
+
const buckets = deltaBuckets.get(deltas)?.get(constants.bucketKey);
|
|
1393
1830
|
return buckets !== void 0 && !buckets.has(constants.values[0]);
|
|
1394
1831
|
}
|
|
1395
1832
|
function deltaRows(deltas, literal, budget) {
|
|
@@ -1404,7 +1841,7 @@ function deltaRows(deltas, literal, budget) {
|
|
|
1404
1841
|
deltaBuckets.set(deltas, byColumn);
|
|
1405
1842
|
}
|
|
1406
1843
|
const column = constants.columns[0];
|
|
1407
|
-
const key =
|
|
1844
|
+
const key = constants.bucketKey;
|
|
1408
1845
|
let buckets = byColumn.get(key);
|
|
1409
1846
|
if (buckets === void 0) {
|
|
1410
1847
|
buckets = /* @__PURE__ */ new Map();
|
|
@@ -1549,7 +1986,7 @@ function canResume(rules, state) {
|
|
|
1549
1986
|
function factsSince(db, marks) {
|
|
1550
1987
|
const added = /* @__PURE__ */ new Map();
|
|
1551
1988
|
for (const name of db.relationNames()) {
|
|
1552
|
-
const all = db
|
|
1989
|
+
const all = tuplesOf(db, name);
|
|
1553
1990
|
const mark = marks.get(name) ?? 0;
|
|
1554
1991
|
if (all.length > mark) {
|
|
1555
1992
|
added.set(name, all.slice(mark));
|
|
@@ -1560,7 +1997,7 @@ function factsSince(db, marks) {
|
|
|
1560
1997
|
function currentMarks(db) {
|
|
1561
1998
|
const marks = /* @__PURE__ */ new Map();
|
|
1562
1999
|
for (const name of db.relationNames()) {
|
|
1563
|
-
marks.set(name, db
|
|
2000
|
+
marks.set(name, sizeOf(db, name));
|
|
1564
2001
|
}
|
|
1565
2002
|
return marks;
|
|
1566
2003
|
}
|
|
@@ -1622,9 +2059,14 @@ function shapeOf(rules) {
|
|
|
1622
2059
|
return shape;
|
|
1623
2060
|
}
|
|
1624
2061
|
function couldProduce(db, rule2) {
|
|
1625
|
-
|
|
1626
|
-
(literal) => literal.negated
|
|
2062
|
+
const empty = rule2.body.find(
|
|
2063
|
+
(literal) => !literal.negated && sizeOf(db, literal.relation) === 0
|
|
1627
2064
|
);
|
|
2065
|
+
if (empty === void 0) {
|
|
2066
|
+
return true;
|
|
2067
|
+
}
|
|
2068
|
+
db.readLog?.readWhole(empty.relation);
|
|
2069
|
+
return false;
|
|
1628
2070
|
}
|
|
1629
2071
|
function storedMergeFor(algebra) {
|
|
1630
2072
|
return (stored, incoming) => {
|
|
@@ -1673,6 +2115,17 @@ function runRules(db, rules, algebra, given) {
|
|
|
1673
2115
|
};
|
|
1674
2116
|
const mergeStored = algebra === void 0 ? void 0 : storedMergeFor(algebra);
|
|
1675
2117
|
const marks = canResume(rules, state) ? state.marks : void 0;
|
|
2118
|
+
const producible = /* @__PURE__ */ new Set();
|
|
2119
|
+
const canProduce = (r) => {
|
|
2120
|
+
if (producible.has(r)) {
|
|
2121
|
+
return true;
|
|
2122
|
+
}
|
|
2123
|
+
if (!couldProduce(db, r)) {
|
|
2124
|
+
return false;
|
|
2125
|
+
}
|
|
2126
|
+
producible.add(r);
|
|
2127
|
+
return true;
|
|
2128
|
+
};
|
|
1676
2129
|
const runStratum = (stratum) => {
|
|
1677
2130
|
let delta = /* @__PURE__ */ new Map();
|
|
1678
2131
|
const record = (relation, tuple) => {
|
|
@@ -1715,7 +2168,7 @@ function runRules(db, rules, algebra, given) {
|
|
|
1715
2168
|
return;
|
|
1716
2169
|
}
|
|
1717
2170
|
const startedAt2 = performance.now();
|
|
1718
|
-
const before = db
|
|
2171
|
+
const before = sizeOf(db, r.head.relation);
|
|
1719
2172
|
const readBefore = budget.examined;
|
|
1720
2173
|
deriveInto(r, seed, deltaIndex);
|
|
1721
2174
|
chargeRule(
|
|
@@ -1723,14 +2176,14 @@ function runRules(db, rules, algebra, given) {
|
|
|
1723
2176
|
r.head.relation,
|
|
1724
2177
|
r.body.map(literalName),
|
|
1725
2178
|
performance.now() - startedAt2,
|
|
1726
|
-
db
|
|
2179
|
+
sizeOf(db, r.head.relation) - before,
|
|
1727
2180
|
budget.examined - readBefore
|
|
1728
2181
|
);
|
|
1729
2182
|
};
|
|
1730
2183
|
const applyDelta = (seed, derivedOnly) => {
|
|
1731
2184
|
for (const at of rulesReading(stratum, seed, derivedOnly)) {
|
|
1732
2185
|
const r = stratum.rules[at];
|
|
1733
|
-
if (!
|
|
2186
|
+
if (!canProduce(r)) {
|
|
1734
2187
|
continue;
|
|
1735
2188
|
}
|
|
1736
2189
|
const positives = positivesOf(r);
|
|
@@ -1748,13 +2201,13 @@ function runRules(db, rules, algebra, given) {
|
|
|
1748
2201
|
};
|
|
1749
2202
|
if (marks === void 0 || marks === null) {
|
|
1750
2203
|
for (const r of stratum.rules) {
|
|
1751
|
-
if (!
|
|
2204
|
+
if (!canProduce(r)) {
|
|
1752
2205
|
continue;
|
|
1753
2206
|
}
|
|
1754
2207
|
const all = /* @__PURE__ */ new Map();
|
|
1755
2208
|
for (const l of r.body) {
|
|
1756
2209
|
if (!l.negated) {
|
|
1757
|
-
all.set(l.relation, db
|
|
2210
|
+
all.set(l.relation, tuplesOf(db, l.relation));
|
|
1758
2211
|
}
|
|
1759
2212
|
}
|
|
1760
2213
|
runOneRule(r, all, -1);
|
|
@@ -1791,7 +2244,7 @@ function runRules(db, rules, algebra, given) {
|
|
|
1791
2244
|
state.marks = currentMarks(db);
|
|
1792
2245
|
if (isProfiling()) {
|
|
1793
2246
|
chargeRelationSizes(
|
|
1794
|
-
db.relationNames().map((name) => [name, db
|
|
2247
|
+
db.relationNames().map((name) => [name, sizeOf(db, name)]),
|
|
1795
2248
|
derivedRelations
|
|
1796
2249
|
);
|
|
1797
2250
|
}
|
|
@@ -1827,6 +2280,7 @@ export {
|
|
|
1827
2280
|
formatProfile,
|
|
1828
2281
|
lit,
|
|
1829
2282
|
notLit,
|
|
2283
|
+
noteReads,
|
|
1830
2284
|
profileEvaluation,
|
|
1831
2285
|
profileEvaluationAsync,
|
|
1832
2286
|
proofOf,
|