@suss/datalog 0.38.0 → 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 +391 -20
- package/dist/index.d.cts +109 -1
- package/dist/index.d.ts +109 -1
- package/dist/index.js +390 -20
- 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,
|
|
@@ -762,7 +763,7 @@ function addToBucket(buckets, key, tuple) {
|
|
|
762
763
|
bucket.push(tuple);
|
|
763
764
|
}
|
|
764
765
|
}
|
|
765
|
-
function
|
|
766
|
+
function columnIndex(relation, column) {
|
|
766
767
|
let buckets = relation.columns[column];
|
|
767
768
|
if (buckets === void 0) {
|
|
768
769
|
buckets = /* @__PURE__ */ new Map();
|
|
@@ -774,7 +775,279 @@ function bucketIn(relation, column, value) {
|
|
|
774
775
|
}
|
|
775
776
|
relation.columns[column] = buckets;
|
|
776
777
|
}
|
|
777
|
-
return
|
|
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
|
+
}
|
|
778
1051
|
}
|
|
779
1052
|
|
|
780
1053
|
// src/stratum.ts
|
|
@@ -800,7 +1073,7 @@ function planStratum(rules) {
|
|
|
800
1073
|
}
|
|
801
1074
|
return { rules, derived, readers, marked: new Uint8Array(rules.length) };
|
|
802
1075
|
}
|
|
803
|
-
var
|
|
1076
|
+
var NONE2 = [];
|
|
804
1077
|
function rulesReading(stratum, seed, derivedOnly) {
|
|
805
1078
|
const lists = [];
|
|
806
1079
|
for (const [relation, tuples] of seed) {
|
|
@@ -816,7 +1089,7 @@ function rulesReading(stratum, seed, derivedOnly) {
|
|
|
816
1089
|
}
|
|
817
1090
|
}
|
|
818
1091
|
if (lists.length === 0) {
|
|
819
|
-
return
|
|
1092
|
+
return NONE2;
|
|
820
1093
|
}
|
|
821
1094
|
if (lists.length === 1) {
|
|
822
1095
|
return lists[0];
|
|
@@ -986,6 +1259,8 @@ var ruleLabel = (r) => r.name ?? `${r.head.relation} :- ${r.body.map(literalName
|
|
|
986
1259
|
var FEW_REMOVED = 16;
|
|
987
1260
|
var Database = class {
|
|
988
1261
|
store = /* @__PURE__ */ new Map();
|
|
1262
|
+
/** @internal Where reads are noted while `noteReads` runs. */
|
|
1263
|
+
readLog = null;
|
|
989
1264
|
relation(name) {
|
|
990
1265
|
const existing = this.store.get(name);
|
|
991
1266
|
if (existing !== void 0) {
|
|
@@ -995,7 +1270,8 @@ var Database = class {
|
|
|
995
1270
|
index: new FactIndex(),
|
|
996
1271
|
tuples: [],
|
|
997
1272
|
columns: [],
|
|
998
|
-
combined: /* @__PURE__ */ new Map()
|
|
1273
|
+
combined: /* @__PURE__ */ new Map(),
|
|
1274
|
+
removed: 0
|
|
999
1275
|
};
|
|
1000
1276
|
this.store.set(name, created);
|
|
1001
1277
|
return created;
|
|
@@ -1050,6 +1326,7 @@ var Database = class {
|
|
|
1050
1326
|
}
|
|
1051
1327
|
/** The tag stored for this fact, or undefined when there is none. */
|
|
1052
1328
|
tagOf(relationName, tuple) {
|
|
1329
|
+
this.noteTupleRead(relationName, tuple);
|
|
1053
1330
|
const relation = this.store.get(relationName);
|
|
1054
1331
|
if (relation === void 0) {
|
|
1055
1332
|
return void 0;
|
|
@@ -1058,9 +1335,20 @@ var Database = class {
|
|
|
1058
1335
|
return key === void 0 ? void 0 : relation.index.tagOf(key);
|
|
1059
1336
|
}
|
|
1060
1337
|
has(relationName, tuple) {
|
|
1338
|
+
this.noteTupleRead(relationName, tuple);
|
|
1061
1339
|
return this.store.get(relationName)?.index.find(tuple) !== void 0;
|
|
1062
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
|
+
}
|
|
1063
1350
|
facts(relationName) {
|
|
1351
|
+
this.readLog?.readWhole(relationName);
|
|
1064
1352
|
return this.store.get(relationName)?.tuples ?? [];
|
|
1065
1353
|
}
|
|
1066
1354
|
/**
|
|
@@ -1073,6 +1361,7 @@ var Database = class {
|
|
|
1073
1361
|
* gets an index.
|
|
1074
1362
|
*/
|
|
1075
1363
|
lookup(relationName, column, value) {
|
|
1364
|
+
this.readLog?.lookedUp(relationName, column, value);
|
|
1076
1365
|
const relation = this.store.get(relationName);
|
|
1077
1366
|
return relation === void 0 ? [] : bucketIn(relation, column, value);
|
|
1078
1367
|
}
|
|
@@ -1108,6 +1397,7 @@ var Database = class {
|
|
|
1108
1397
|
for (const key of going) {
|
|
1109
1398
|
index.remove(key);
|
|
1110
1399
|
}
|
|
1400
|
+
relation.removed += going.size;
|
|
1111
1401
|
if (index.size === 0) {
|
|
1112
1402
|
relation.tuples = [];
|
|
1113
1403
|
index.clear();
|
|
@@ -1143,6 +1433,7 @@ var Database = class {
|
|
|
1143
1433
|
return 0;
|
|
1144
1434
|
}
|
|
1145
1435
|
const removed = relation.tuples.length;
|
|
1436
|
+
relation.removed += removed;
|
|
1146
1437
|
relation.tuples = [];
|
|
1147
1438
|
relation.index.clear();
|
|
1148
1439
|
relation.columns.length = 0;
|
|
@@ -1151,12 +1442,15 @@ var Database = class {
|
|
|
1151
1442
|
return removed;
|
|
1152
1443
|
}
|
|
1153
1444
|
size(relationName) {
|
|
1154
|
-
|
|
1445
|
+
this.readLog?.readWhole(relationName);
|
|
1446
|
+
return sizeOf(this, relationName);
|
|
1155
1447
|
}
|
|
1156
1448
|
relationNames() {
|
|
1157
1449
|
return [...this.store.keys()];
|
|
1158
1450
|
}
|
|
1159
1451
|
};
|
|
1452
|
+
var tuplesOf = (db, relation) => db.relationOf(relation)?.tuples ?? [];
|
|
1453
|
+
var sizeOf = (db, relation) => db.relationOf(relation)?.tuples.length ?? 0;
|
|
1160
1454
|
function rulesDeriving(rules, relations) {
|
|
1161
1455
|
const wanted = new Set(relations);
|
|
1162
1456
|
const pending = [...relations];
|
|
@@ -1352,6 +1646,7 @@ function evaluateRule(db, deltas, r, deltaIndex, budget) {
|
|
|
1352
1646
|
}
|
|
1353
1647
|
const byValue = newFactsByColumn(rows, start.column, budget);
|
|
1354
1648
|
const first = body[start.index];
|
|
1649
|
+
noteSharedLookups(db, first, start, rows);
|
|
1355
1650
|
for (const tuple of start.source) {
|
|
1356
1651
|
budget.examined++;
|
|
1357
1652
|
if (budget.examined > budget.limit) {
|
|
@@ -1383,7 +1678,7 @@ function smallerStart(db, body, deltaAt, rows) {
|
|
|
1383
1678
|
if (column === -1) {
|
|
1384
1679
|
continue;
|
|
1385
1680
|
}
|
|
1386
|
-
const source = narrowedSource(db, literal, null) ?? db
|
|
1681
|
+
const source = narrowedSource(db, literal, null) ?? tuplesOf(db, literal.relation);
|
|
1387
1682
|
const worthIt = indexed?.has(column) ? source.length < rows.length : source.length * 2 < rows.length;
|
|
1388
1683
|
if (worthIt && (best === null || source.length < best.source.length)) {
|
|
1389
1684
|
best = {
|
|
@@ -1441,6 +1736,7 @@ function nextLiteral(db, body, taken, bindings) {
|
|
|
1441
1736
|
continue;
|
|
1442
1737
|
}
|
|
1443
1738
|
if (source.length === 0) {
|
|
1739
|
+
noteLookup(db, literal, bindings);
|
|
1444
1740
|
return -1;
|
|
1445
1741
|
}
|
|
1446
1742
|
if (narrowest === null || source.length < narrowest.length) {
|
|
@@ -1449,6 +1745,7 @@ function nextLiteral(db, body, taken, bindings) {
|
|
|
1449
1745
|
}
|
|
1450
1746
|
}
|
|
1451
1747
|
if (narrowest !== null) {
|
|
1748
|
+
noteLookup(db, body[pick], bindings);
|
|
1452
1749
|
nextSource = narrowedFurther(db, body[pick], bindings, narrowest);
|
|
1453
1750
|
return nextSource.length === 0 ? -1 : pick;
|
|
1454
1751
|
}
|
|
@@ -1460,6 +1757,59 @@ function nextLiteral(db, body, taken, bindings) {
|
|
|
1460
1757
|
}
|
|
1461
1758
|
return body.findIndex((_, index) => !(taken & 1 << index));
|
|
1462
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
|
+
}
|
|
1463
1813
|
function narrowedSource(db, literal, bindings) {
|
|
1464
1814
|
const relation = db.relationOf(literal.relation);
|
|
1465
1815
|
let narrowest = null;
|
|
@@ -1511,7 +1861,11 @@ function constantColumnsOf(literal) {
|
|
|
1511
1861
|
values.push(term.value);
|
|
1512
1862
|
}
|
|
1513
1863
|
});
|
|
1514
|
-
known = columns.length === 0 ? null : {
|
|
1864
|
+
known = columns.length === 0 ? null : {
|
|
1865
|
+
columns,
|
|
1866
|
+
values,
|
|
1867
|
+
bucketKey: deltaBucketKey(literal.relation, columns[0])
|
|
1868
|
+
};
|
|
1515
1869
|
constantColumns.set(literal, known);
|
|
1516
1870
|
}
|
|
1517
1871
|
return known;
|
|
@@ -1524,7 +1878,7 @@ function hasNoNewRows(deltas, literal) {
|
|
|
1524
1878
|
if (rows.length === 0 || constants === null) {
|
|
1525
1879
|
return rows.length === 0;
|
|
1526
1880
|
}
|
|
1527
|
-
const buckets = deltaBuckets.get(deltas)?.get(
|
|
1881
|
+
const buckets = deltaBuckets.get(deltas)?.get(constants.bucketKey);
|
|
1528
1882
|
return buckets !== void 0 && !buckets.has(constants.values[0]);
|
|
1529
1883
|
}
|
|
1530
1884
|
function deltaRows(deltas, literal, budget) {
|
|
@@ -1539,7 +1893,7 @@ function deltaRows(deltas, literal, budget) {
|
|
|
1539
1893
|
deltaBuckets.set(deltas, byColumn);
|
|
1540
1894
|
}
|
|
1541
1895
|
const column = constants.columns[0];
|
|
1542
|
-
const key =
|
|
1896
|
+
const key = constants.bucketKey;
|
|
1543
1897
|
let buckets = byColumn.get(key);
|
|
1544
1898
|
if (buckets === void 0) {
|
|
1545
1899
|
buckets = /* @__PURE__ */ new Map();
|
|
@@ -1684,7 +2038,7 @@ function canResume(rules, state) {
|
|
|
1684
2038
|
function factsSince(db, marks) {
|
|
1685
2039
|
const added = /* @__PURE__ */ new Map();
|
|
1686
2040
|
for (const name of db.relationNames()) {
|
|
1687
|
-
const all = db
|
|
2041
|
+
const all = tuplesOf(db, name);
|
|
1688
2042
|
const mark = marks.get(name) ?? 0;
|
|
1689
2043
|
if (all.length > mark) {
|
|
1690
2044
|
added.set(name, all.slice(mark));
|
|
@@ -1695,7 +2049,7 @@ function factsSince(db, marks) {
|
|
|
1695
2049
|
function currentMarks(db) {
|
|
1696
2050
|
const marks = /* @__PURE__ */ new Map();
|
|
1697
2051
|
for (const name of db.relationNames()) {
|
|
1698
|
-
marks.set(name, db
|
|
2052
|
+
marks.set(name, sizeOf(db, name));
|
|
1699
2053
|
}
|
|
1700
2054
|
return marks;
|
|
1701
2055
|
}
|
|
@@ -1757,9 +2111,14 @@ function shapeOf(rules) {
|
|
|
1757
2111
|
return shape;
|
|
1758
2112
|
}
|
|
1759
2113
|
function couldProduce(db, rule2) {
|
|
1760
|
-
|
|
1761
|
-
(literal) => literal.negated
|
|
2114
|
+
const empty = rule2.body.find(
|
|
2115
|
+
(literal) => !literal.negated && sizeOf(db, literal.relation) === 0
|
|
1762
2116
|
);
|
|
2117
|
+
if (empty === void 0) {
|
|
2118
|
+
return true;
|
|
2119
|
+
}
|
|
2120
|
+
db.readLog?.readWhole(empty.relation);
|
|
2121
|
+
return false;
|
|
1763
2122
|
}
|
|
1764
2123
|
function storedMergeFor(algebra) {
|
|
1765
2124
|
return (stored, incoming) => {
|
|
@@ -1808,6 +2167,17 @@ function runRules(db, rules, algebra, given) {
|
|
|
1808
2167
|
};
|
|
1809
2168
|
const mergeStored = algebra === void 0 ? void 0 : storedMergeFor(algebra);
|
|
1810
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
|
+
};
|
|
1811
2181
|
const runStratum = (stratum) => {
|
|
1812
2182
|
let delta = /* @__PURE__ */ new Map();
|
|
1813
2183
|
const record = (relation, tuple) => {
|
|
@@ -1850,7 +2220,7 @@ function runRules(db, rules, algebra, given) {
|
|
|
1850
2220
|
return;
|
|
1851
2221
|
}
|
|
1852
2222
|
const startedAt2 = performance.now();
|
|
1853
|
-
const before = db
|
|
2223
|
+
const before = sizeOf(db, r.head.relation);
|
|
1854
2224
|
const readBefore = budget.examined;
|
|
1855
2225
|
deriveInto(r, seed, deltaIndex);
|
|
1856
2226
|
chargeRule(
|
|
@@ -1858,14 +2228,14 @@ function runRules(db, rules, algebra, given) {
|
|
|
1858
2228
|
r.head.relation,
|
|
1859
2229
|
r.body.map(literalName),
|
|
1860
2230
|
performance.now() - startedAt2,
|
|
1861
|
-
db
|
|
2231
|
+
sizeOf(db, r.head.relation) - before,
|
|
1862
2232
|
budget.examined - readBefore
|
|
1863
2233
|
);
|
|
1864
2234
|
};
|
|
1865
2235
|
const applyDelta = (seed, derivedOnly) => {
|
|
1866
2236
|
for (const at of rulesReading(stratum, seed, derivedOnly)) {
|
|
1867
2237
|
const r = stratum.rules[at];
|
|
1868
|
-
if (!
|
|
2238
|
+
if (!canProduce(r)) {
|
|
1869
2239
|
continue;
|
|
1870
2240
|
}
|
|
1871
2241
|
const positives = positivesOf(r);
|
|
@@ -1883,13 +2253,13 @@ function runRules(db, rules, algebra, given) {
|
|
|
1883
2253
|
};
|
|
1884
2254
|
if (marks === void 0 || marks === null) {
|
|
1885
2255
|
for (const r of stratum.rules) {
|
|
1886
|
-
if (!
|
|
2256
|
+
if (!canProduce(r)) {
|
|
1887
2257
|
continue;
|
|
1888
2258
|
}
|
|
1889
2259
|
const all = /* @__PURE__ */ new Map();
|
|
1890
2260
|
for (const l of r.body) {
|
|
1891
2261
|
if (!l.negated) {
|
|
1892
|
-
all.set(l.relation, db
|
|
2262
|
+
all.set(l.relation, tuplesOf(db, l.relation));
|
|
1893
2263
|
}
|
|
1894
2264
|
}
|
|
1895
2265
|
runOneRule(r, all, -1);
|
|
@@ -1926,7 +2296,7 @@ function runRules(db, rules, algebra, given) {
|
|
|
1926
2296
|
state.marks = currentMarks(db);
|
|
1927
2297
|
if (isProfiling()) {
|
|
1928
2298
|
chargeRelationSizes(
|
|
1929
|
-
db.relationNames().map((name) => [name, db
|
|
2299
|
+
db.relationNames().map((name) => [name, sizeOf(db, name)]),
|
|
1930
2300
|
derivedRelations
|
|
1931
2301
|
);
|
|
1932
2302
|
}
|
|
@@ -1963,6 +2333,7 @@ function addTo(buckets, relation, tuple) {
|
|
|
1963
2333
|
formatProfile,
|
|
1964
2334
|
lit,
|
|
1965
2335
|
notLit,
|
|
2336
|
+
noteReads,
|
|
1966
2337
|
profileEvaluation,
|
|
1967
2338
|
profileEvaluationAsync,
|
|
1968
2339
|
proofOf,
|