@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.js
CHANGED
|
@@ -711,7 +711,7 @@ function addToBucket(buckets, key, tuple) {
|
|
|
711
711
|
bucket.push(tuple);
|
|
712
712
|
}
|
|
713
713
|
}
|
|
714
|
-
function
|
|
714
|
+
function columnIndex(relation, column) {
|
|
715
715
|
let buckets = relation.columns[column];
|
|
716
716
|
if (buckets === void 0) {
|
|
717
717
|
buckets = /* @__PURE__ */ new Map();
|
|
@@ -723,7 +723,279 @@ function bucketIn(relation, column, value) {
|
|
|
723
723
|
}
|
|
724
724
|
relation.columns[column] = buckets;
|
|
725
725
|
}
|
|
726
|
-
return
|
|
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
|
+
}
|
|
727
999
|
}
|
|
728
1000
|
|
|
729
1001
|
// src/stratum.ts
|
|
@@ -749,7 +1021,7 @@ function planStratum(rules) {
|
|
|
749
1021
|
}
|
|
750
1022
|
return { rules, derived, readers, marked: new Uint8Array(rules.length) };
|
|
751
1023
|
}
|
|
752
|
-
var
|
|
1024
|
+
var NONE2 = [];
|
|
753
1025
|
function rulesReading(stratum, seed, derivedOnly) {
|
|
754
1026
|
const lists = [];
|
|
755
1027
|
for (const [relation, tuples] of seed) {
|
|
@@ -765,7 +1037,7 @@ function rulesReading(stratum, seed, derivedOnly) {
|
|
|
765
1037
|
}
|
|
766
1038
|
}
|
|
767
1039
|
if (lists.length === 0) {
|
|
768
|
-
return
|
|
1040
|
+
return NONE2;
|
|
769
1041
|
}
|
|
770
1042
|
if (lists.length === 1) {
|
|
771
1043
|
return lists[0];
|
|
@@ -935,6 +1207,8 @@ var ruleLabel = (r) => r.name ?? `${r.head.relation} :- ${r.body.map(literalName
|
|
|
935
1207
|
var FEW_REMOVED = 16;
|
|
936
1208
|
var Database = class {
|
|
937
1209
|
store = /* @__PURE__ */ new Map();
|
|
1210
|
+
/** @internal Where reads are noted while `noteReads` runs. */
|
|
1211
|
+
readLog = null;
|
|
938
1212
|
relation(name) {
|
|
939
1213
|
const existing = this.store.get(name);
|
|
940
1214
|
if (existing !== void 0) {
|
|
@@ -944,7 +1218,8 @@ var Database = class {
|
|
|
944
1218
|
index: new FactIndex(),
|
|
945
1219
|
tuples: [],
|
|
946
1220
|
columns: [],
|
|
947
|
-
combined: /* @__PURE__ */ new Map()
|
|
1221
|
+
combined: /* @__PURE__ */ new Map(),
|
|
1222
|
+
removed: 0
|
|
948
1223
|
};
|
|
949
1224
|
this.store.set(name, created);
|
|
950
1225
|
return created;
|
|
@@ -999,6 +1274,7 @@ var Database = class {
|
|
|
999
1274
|
}
|
|
1000
1275
|
/** The tag stored for this fact, or undefined when there is none. */
|
|
1001
1276
|
tagOf(relationName, tuple) {
|
|
1277
|
+
this.noteTupleRead(relationName, tuple);
|
|
1002
1278
|
const relation = this.store.get(relationName);
|
|
1003
1279
|
if (relation === void 0) {
|
|
1004
1280
|
return void 0;
|
|
@@ -1007,9 +1283,20 @@ var Database = class {
|
|
|
1007
1283
|
return key === void 0 ? void 0 : relation.index.tagOf(key);
|
|
1008
1284
|
}
|
|
1009
1285
|
has(relationName, tuple) {
|
|
1286
|
+
this.noteTupleRead(relationName, tuple);
|
|
1010
1287
|
return this.store.get(relationName)?.index.find(tuple) !== void 0;
|
|
1011
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
|
+
}
|
|
1012
1298
|
facts(relationName) {
|
|
1299
|
+
this.readLog?.readWhole(relationName);
|
|
1013
1300
|
return this.store.get(relationName)?.tuples ?? [];
|
|
1014
1301
|
}
|
|
1015
1302
|
/**
|
|
@@ -1022,6 +1309,7 @@ var Database = class {
|
|
|
1022
1309
|
* gets an index.
|
|
1023
1310
|
*/
|
|
1024
1311
|
lookup(relationName, column, value) {
|
|
1312
|
+
this.readLog?.lookedUp(relationName, column, value);
|
|
1025
1313
|
const relation = this.store.get(relationName);
|
|
1026
1314
|
return relation === void 0 ? [] : bucketIn(relation, column, value);
|
|
1027
1315
|
}
|
|
@@ -1057,6 +1345,7 @@ var Database = class {
|
|
|
1057
1345
|
for (const key of going) {
|
|
1058
1346
|
index.remove(key);
|
|
1059
1347
|
}
|
|
1348
|
+
relation.removed += going.size;
|
|
1060
1349
|
if (index.size === 0) {
|
|
1061
1350
|
relation.tuples = [];
|
|
1062
1351
|
index.clear();
|
|
@@ -1092,6 +1381,7 @@ var Database = class {
|
|
|
1092
1381
|
return 0;
|
|
1093
1382
|
}
|
|
1094
1383
|
const removed = relation.tuples.length;
|
|
1384
|
+
relation.removed += removed;
|
|
1095
1385
|
relation.tuples = [];
|
|
1096
1386
|
relation.index.clear();
|
|
1097
1387
|
relation.columns.length = 0;
|
|
@@ -1100,12 +1390,15 @@ var Database = class {
|
|
|
1100
1390
|
return removed;
|
|
1101
1391
|
}
|
|
1102
1392
|
size(relationName) {
|
|
1103
|
-
|
|
1393
|
+
this.readLog?.readWhole(relationName);
|
|
1394
|
+
return sizeOf(this, relationName);
|
|
1104
1395
|
}
|
|
1105
1396
|
relationNames() {
|
|
1106
1397
|
return [...this.store.keys()];
|
|
1107
1398
|
}
|
|
1108
1399
|
};
|
|
1400
|
+
var tuplesOf = (db, relation) => db.relationOf(relation)?.tuples ?? [];
|
|
1401
|
+
var sizeOf = (db, relation) => db.relationOf(relation)?.tuples.length ?? 0;
|
|
1109
1402
|
function rulesDeriving(rules, relations) {
|
|
1110
1403
|
const wanted = new Set(relations);
|
|
1111
1404
|
const pending = [...relations];
|
|
@@ -1301,6 +1594,7 @@ function evaluateRule(db, deltas, r, deltaIndex, budget) {
|
|
|
1301
1594
|
}
|
|
1302
1595
|
const byValue = newFactsByColumn(rows, start.column, budget);
|
|
1303
1596
|
const first = body[start.index];
|
|
1597
|
+
noteSharedLookups(db, first, start, rows);
|
|
1304
1598
|
for (const tuple of start.source) {
|
|
1305
1599
|
budget.examined++;
|
|
1306
1600
|
if (budget.examined > budget.limit) {
|
|
@@ -1332,7 +1626,7 @@ function smallerStart(db, body, deltaAt, rows) {
|
|
|
1332
1626
|
if (column === -1) {
|
|
1333
1627
|
continue;
|
|
1334
1628
|
}
|
|
1335
|
-
const source = narrowedSource(db, literal, null) ?? db
|
|
1629
|
+
const source = narrowedSource(db, literal, null) ?? tuplesOf(db, literal.relation);
|
|
1336
1630
|
const worthIt = indexed?.has(column) ? source.length < rows.length : source.length * 2 < rows.length;
|
|
1337
1631
|
if (worthIt && (best === null || source.length < best.source.length)) {
|
|
1338
1632
|
best = {
|
|
@@ -1390,6 +1684,7 @@ function nextLiteral(db, body, taken, bindings) {
|
|
|
1390
1684
|
continue;
|
|
1391
1685
|
}
|
|
1392
1686
|
if (source.length === 0) {
|
|
1687
|
+
noteLookup(db, literal, bindings);
|
|
1393
1688
|
return -1;
|
|
1394
1689
|
}
|
|
1395
1690
|
if (narrowest === null || source.length < narrowest.length) {
|
|
@@ -1398,6 +1693,7 @@ function nextLiteral(db, body, taken, bindings) {
|
|
|
1398
1693
|
}
|
|
1399
1694
|
}
|
|
1400
1695
|
if (narrowest !== null) {
|
|
1696
|
+
noteLookup(db, body[pick], bindings);
|
|
1401
1697
|
nextSource = narrowedFurther(db, body[pick], bindings, narrowest);
|
|
1402
1698
|
return nextSource.length === 0 ? -1 : pick;
|
|
1403
1699
|
}
|
|
@@ -1409,6 +1705,59 @@ function nextLiteral(db, body, taken, bindings) {
|
|
|
1409
1705
|
}
|
|
1410
1706
|
return body.findIndex((_, index) => !(taken & 1 << index));
|
|
1411
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
|
+
}
|
|
1412
1761
|
function narrowedSource(db, literal, bindings) {
|
|
1413
1762
|
const relation = db.relationOf(literal.relation);
|
|
1414
1763
|
let narrowest = null;
|
|
@@ -1460,7 +1809,11 @@ function constantColumnsOf(literal) {
|
|
|
1460
1809
|
values.push(term.value);
|
|
1461
1810
|
}
|
|
1462
1811
|
});
|
|
1463
|
-
known = columns.length === 0 ? null : {
|
|
1812
|
+
known = columns.length === 0 ? null : {
|
|
1813
|
+
columns,
|
|
1814
|
+
values,
|
|
1815
|
+
bucketKey: deltaBucketKey(literal.relation, columns[0])
|
|
1816
|
+
};
|
|
1464
1817
|
constantColumns.set(literal, known);
|
|
1465
1818
|
}
|
|
1466
1819
|
return known;
|
|
@@ -1473,7 +1826,7 @@ function hasNoNewRows(deltas, literal) {
|
|
|
1473
1826
|
if (rows.length === 0 || constants === null) {
|
|
1474
1827
|
return rows.length === 0;
|
|
1475
1828
|
}
|
|
1476
|
-
const buckets = deltaBuckets.get(deltas)?.get(
|
|
1829
|
+
const buckets = deltaBuckets.get(deltas)?.get(constants.bucketKey);
|
|
1477
1830
|
return buckets !== void 0 && !buckets.has(constants.values[0]);
|
|
1478
1831
|
}
|
|
1479
1832
|
function deltaRows(deltas, literal, budget) {
|
|
@@ -1488,7 +1841,7 @@ function deltaRows(deltas, literal, budget) {
|
|
|
1488
1841
|
deltaBuckets.set(deltas, byColumn);
|
|
1489
1842
|
}
|
|
1490
1843
|
const column = constants.columns[0];
|
|
1491
|
-
const key =
|
|
1844
|
+
const key = constants.bucketKey;
|
|
1492
1845
|
let buckets = byColumn.get(key);
|
|
1493
1846
|
if (buckets === void 0) {
|
|
1494
1847
|
buckets = /* @__PURE__ */ new Map();
|
|
@@ -1633,7 +1986,7 @@ function canResume(rules, state) {
|
|
|
1633
1986
|
function factsSince(db, marks) {
|
|
1634
1987
|
const added = /* @__PURE__ */ new Map();
|
|
1635
1988
|
for (const name of db.relationNames()) {
|
|
1636
|
-
const all = db
|
|
1989
|
+
const all = tuplesOf(db, name);
|
|
1637
1990
|
const mark = marks.get(name) ?? 0;
|
|
1638
1991
|
if (all.length > mark) {
|
|
1639
1992
|
added.set(name, all.slice(mark));
|
|
@@ -1644,7 +1997,7 @@ function factsSince(db, marks) {
|
|
|
1644
1997
|
function currentMarks(db) {
|
|
1645
1998
|
const marks = /* @__PURE__ */ new Map();
|
|
1646
1999
|
for (const name of db.relationNames()) {
|
|
1647
|
-
marks.set(name, db
|
|
2000
|
+
marks.set(name, sizeOf(db, name));
|
|
1648
2001
|
}
|
|
1649
2002
|
return marks;
|
|
1650
2003
|
}
|
|
@@ -1706,9 +2059,14 @@ function shapeOf(rules) {
|
|
|
1706
2059
|
return shape;
|
|
1707
2060
|
}
|
|
1708
2061
|
function couldProduce(db, rule2) {
|
|
1709
|
-
|
|
1710
|
-
(literal) => literal.negated
|
|
2062
|
+
const empty = rule2.body.find(
|
|
2063
|
+
(literal) => !literal.negated && sizeOf(db, literal.relation) === 0
|
|
1711
2064
|
);
|
|
2065
|
+
if (empty === void 0) {
|
|
2066
|
+
return true;
|
|
2067
|
+
}
|
|
2068
|
+
db.readLog?.readWhole(empty.relation);
|
|
2069
|
+
return false;
|
|
1712
2070
|
}
|
|
1713
2071
|
function storedMergeFor(algebra) {
|
|
1714
2072
|
return (stored, incoming) => {
|
|
@@ -1757,6 +2115,17 @@ function runRules(db, rules, algebra, given) {
|
|
|
1757
2115
|
};
|
|
1758
2116
|
const mergeStored = algebra === void 0 ? void 0 : storedMergeFor(algebra);
|
|
1759
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
|
+
};
|
|
1760
2129
|
const runStratum = (stratum) => {
|
|
1761
2130
|
let delta = /* @__PURE__ */ new Map();
|
|
1762
2131
|
const record = (relation, tuple) => {
|
|
@@ -1799,7 +2168,7 @@ function runRules(db, rules, algebra, given) {
|
|
|
1799
2168
|
return;
|
|
1800
2169
|
}
|
|
1801
2170
|
const startedAt2 = performance.now();
|
|
1802
|
-
const before = db
|
|
2171
|
+
const before = sizeOf(db, r.head.relation);
|
|
1803
2172
|
const readBefore = budget.examined;
|
|
1804
2173
|
deriveInto(r, seed, deltaIndex);
|
|
1805
2174
|
chargeRule(
|
|
@@ -1807,14 +2176,14 @@ function runRules(db, rules, algebra, given) {
|
|
|
1807
2176
|
r.head.relation,
|
|
1808
2177
|
r.body.map(literalName),
|
|
1809
2178
|
performance.now() - startedAt2,
|
|
1810
|
-
db
|
|
2179
|
+
sizeOf(db, r.head.relation) - before,
|
|
1811
2180
|
budget.examined - readBefore
|
|
1812
2181
|
);
|
|
1813
2182
|
};
|
|
1814
2183
|
const applyDelta = (seed, derivedOnly) => {
|
|
1815
2184
|
for (const at of rulesReading(stratum, seed, derivedOnly)) {
|
|
1816
2185
|
const r = stratum.rules[at];
|
|
1817
|
-
if (!
|
|
2186
|
+
if (!canProduce(r)) {
|
|
1818
2187
|
continue;
|
|
1819
2188
|
}
|
|
1820
2189
|
const positives = positivesOf(r);
|
|
@@ -1832,13 +2201,13 @@ function runRules(db, rules, algebra, given) {
|
|
|
1832
2201
|
};
|
|
1833
2202
|
if (marks === void 0 || marks === null) {
|
|
1834
2203
|
for (const r of stratum.rules) {
|
|
1835
|
-
if (!
|
|
2204
|
+
if (!canProduce(r)) {
|
|
1836
2205
|
continue;
|
|
1837
2206
|
}
|
|
1838
2207
|
const all = /* @__PURE__ */ new Map();
|
|
1839
2208
|
for (const l of r.body) {
|
|
1840
2209
|
if (!l.negated) {
|
|
1841
|
-
all.set(l.relation, db
|
|
2210
|
+
all.set(l.relation, tuplesOf(db, l.relation));
|
|
1842
2211
|
}
|
|
1843
2212
|
}
|
|
1844
2213
|
runOneRule(r, all, -1);
|
|
@@ -1875,7 +2244,7 @@ function runRules(db, rules, algebra, given) {
|
|
|
1875
2244
|
state.marks = currentMarks(db);
|
|
1876
2245
|
if (isProfiling()) {
|
|
1877
2246
|
chargeRelationSizes(
|
|
1878
|
-
db.relationNames().map((name) => [name, db
|
|
2247
|
+
db.relationNames().map((name) => [name, sizeOf(db, name)]),
|
|
1879
2248
|
derivedRelations
|
|
1880
2249
|
);
|
|
1881
2250
|
}
|
|
@@ -1911,6 +2280,7 @@ export {
|
|
|
1911
2280
|
formatProfile,
|
|
1912
2281
|
lit,
|
|
1913
2282
|
notLit,
|
|
2283
|
+
noteReads,
|
|
1914
2284
|
profileEvaluation,
|
|
1915
2285
|
profileEvaluationAsync,
|
|
1916
2286
|
proofOf,
|
package/package.json
CHANGED