@suss/datalog 0.33.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -257,7 +257,7 @@ function boundFirst(r, headAdornment, labels) {
257
257
  }
258
258
  });
259
259
  const boundColumns = (literal) => literal.terms.filter(
260
- (term, column) => !labels.get(literal.relation)?.has(column) && (term.type === "constant" || bound.has(term.name))
260
+ (term, column) => !labels.get(literal.relation)?.has(column) && term.type === "variable" && bound.has(term.name)
261
261
  ).length;
262
262
  const remaining = [...r.body];
263
263
  const body = [];
@@ -354,6 +354,11 @@ function chargeEvaluationRows(examined) {
354
354
  }
355
355
  }
356
356
  }
357
+ function chargeEngine(part, ms) {
358
+ for (const collector of open) {
359
+ collector.engineMs[part] += ms;
360
+ }
361
+ }
357
362
  function chargeRound(ruleSet) {
358
363
  for (const collector of open) {
359
364
  collector.rounds += 1;
@@ -410,6 +415,7 @@ function openScope() {
410
415
  startedAt: performance.now(),
411
416
  rounds: 0,
412
417
  evaluations: 0,
418
+ engineMs: { evaluate: 0, clear: 0 },
413
419
  rules: /* @__PURE__ */ new Map(),
414
420
  relations: /* @__PURE__ */ new Map(),
415
421
  derivedRelations: /* @__PURE__ */ new Set(),
@@ -446,6 +452,7 @@ function summarise(mine) {
446
452
  return {
447
453
  wallMs: performance.now() - mine.startedAt,
448
454
  datalogMs: totalMs(mine.rules.values()),
455
+ engineMs: { ...mine.engineMs },
449
456
  rounds: mine.rounds,
450
457
  evaluations: mine.evaluations,
451
458
  examined: totalExamined(mine.rules.values()),
@@ -481,6 +488,9 @@ function formatProfile(profile) {
481
488
  lines.push(
482
489
  `datalog: ${profile.datalogMs.toFixed(0)}ms (${share(profile.datalogMs, profile.wallMs).trim()} of ${profile.wallMs.toFixed(0)}ms wall), ${profile.evaluations} evaluations, ${profile.rounds} rounds, ${profile.examined} rows read (${profile.largestEvaluation} in the biggest evaluation)`
483
490
  );
491
+ lines.push(
492
+ ` engine: ${profile.engineMs.evaluate.toFixed(0)}ms inside evaluate (${profile.datalogMs.toFixed(0)}ms of it in rules), ${profile.engineMs.clear.toFixed(0)}ms clearing relations between questions`
493
+ );
484
494
  const { asked, abandoned, skipped } = profile.questions;
485
495
  if (asked > 0) {
486
496
  lines.push(
@@ -513,6 +523,102 @@ function formatProfile(profile) {
513
523
  }
514
524
 
515
525
  // src/relation.ts
526
+ var sameTuple = (a, b) => a.length === b.length && a.every((atom, at) => atom === b[at]);
527
+ function storedTuplesOf(relation, facts) {
528
+ const column = relation.columns.findIndex((buckets2) => buckets2 !== void 0);
529
+ const buckets = relation.columns[column];
530
+ if (buckets === void 0) {
531
+ return null;
532
+ }
533
+ const stored = facts.map(
534
+ (fact) => buckets.get(fact[column])?.find((tuple) => sameTuple(tuple, fact))
535
+ );
536
+ return stored.every((tuple) => tuple !== void 0) ? stored : null;
537
+ }
538
+ function removeFrom(buckets, key, tuple) {
539
+ const bucket = buckets.get(key);
540
+ const at = bucket?.indexOf(tuple) ?? -1;
541
+ if (bucket === void 0 || at === -1) {
542
+ return;
543
+ }
544
+ bucket.splice(at, 1);
545
+ if (bucket.length === 0) {
546
+ buckets.delete(key);
547
+ }
548
+ }
549
+ function removeTuples(relation, stored) {
550
+ const leaving = new Set(stored);
551
+ relation.tuples = relation.tuples.filter((tuple) => !leaving.has(tuple));
552
+ for (const tuple of stored) {
553
+ relation.columns.forEach((buckets, column) => {
554
+ if (buckets !== void 0) {
555
+ removeFrom(buckets, tuple[column], tuple);
556
+ }
557
+ });
558
+ for (const index of relation.combined.values()) {
559
+ let level = index.root;
560
+ const last = index.columns.length - 1;
561
+ for (let at = 0; at < last && level !== void 0; at++) {
562
+ level = level.get(tuple[index.columns[at]]);
563
+ }
564
+ if (level !== void 0) {
565
+ removeFrom(
566
+ level,
567
+ tuple[index.columns[last]],
568
+ tuple
569
+ );
570
+ }
571
+ }
572
+ }
573
+ }
574
+ function columnsIn(mask) {
575
+ const columns = [];
576
+ for (let column = 0; mask >> column !== 0; column++) {
577
+ if (mask & 1 << column) {
578
+ columns.push(column);
579
+ }
580
+ }
581
+ return columns;
582
+ }
583
+ function addToCombined(index, tuple) {
584
+ const last = index.columns.length - 1;
585
+ if (index.columns[last] >= tuple.length) {
586
+ return;
587
+ }
588
+ let level = index.root;
589
+ for (let at = 0; at < last; at++) {
590
+ const value = tuple[index.columns[at]];
591
+ let next = level.get(value);
592
+ if (next === void 0) {
593
+ next = /* @__PURE__ */ new Map();
594
+ level.set(value, next);
595
+ }
596
+ level = next;
597
+ }
598
+ addToBucket(
599
+ level,
600
+ tuple[index.columns[last]],
601
+ tuple
602
+ );
603
+ }
604
+ function combinedBucketIn(relation, mask, values) {
605
+ let index = relation.combined.get(mask);
606
+ if (index === void 0) {
607
+ index = { columns: columnsIn(mask), root: /* @__PURE__ */ new Map() };
608
+ for (const tuple of relation.tuples) {
609
+ addToCombined(index, tuple);
610
+ }
611
+ relation.combined.set(mask, index);
612
+ }
613
+ let level = index.root;
614
+ for (const value of values) {
615
+ level = level.get(value);
616
+ if (level === void 0) {
617
+ return [];
618
+ }
619
+ }
620
+ return level;
621
+ }
516
622
  function addToBucket(buckets, key, tuple) {
517
623
  const bucket = buckets.get(key);
518
624
  if (bucket === void 0) {
@@ -536,6 +642,72 @@ function bucketIn(relation, column, value) {
536
642
  return buckets.get(value) ?? [];
537
643
  }
538
644
 
645
+ // src/stratum.ts
646
+ function planStratum(rules) {
647
+ const derived = /* @__PURE__ */ new Set();
648
+ const readers = /* @__PURE__ */ new Map();
649
+ for (let at = 0; at < rules.length; at++) {
650
+ const r = rules[at];
651
+ derived.add(r.head.relation);
652
+ for (const literal of r.body) {
653
+ if (literal.negated) {
654
+ continue;
655
+ }
656
+ const positions = readers.get(literal.relation);
657
+ if (positions === void 0) {
658
+ readers.set(literal.relation, [at]);
659
+ continue;
660
+ }
661
+ if (positions[positions.length - 1] !== at) {
662
+ positions.push(at);
663
+ }
664
+ }
665
+ }
666
+ return { rules, derived, readers, marked: new Uint8Array(rules.length) };
667
+ }
668
+ var NONE = [];
669
+ function rulesReading(stratum, seed, derivedOnly) {
670
+ const lists = [];
671
+ for (const [relation, tuples] of seed) {
672
+ if (tuples.length === 0) {
673
+ continue;
674
+ }
675
+ if (derivedOnly && !stratum.derived.has(relation)) {
676
+ continue;
677
+ }
678
+ const positions = stratum.readers.get(relation);
679
+ if (positions !== void 0) {
680
+ lists.push(positions);
681
+ }
682
+ }
683
+ if (lists.length === 0) {
684
+ return NONE;
685
+ }
686
+ if (lists.length === 1) {
687
+ return lists[0];
688
+ }
689
+ return unionInOrder(lists, stratum.marked);
690
+ }
691
+ function unionInOrder(lists, marked) {
692
+ let first = marked.length;
693
+ let last = -1;
694
+ for (const positions of lists) {
695
+ for (const at of positions) {
696
+ marked[at] = 1;
697
+ }
698
+ first = Math.min(first, positions[0]);
699
+ last = Math.max(last, positions[positions.length - 1]);
700
+ }
701
+ const union = [];
702
+ for (let at = first; at <= last; at++) {
703
+ if (marked[at] === 1) {
704
+ marked[at] = 0;
705
+ union.push(at);
706
+ }
707
+ }
708
+ return union;
709
+ }
710
+
539
711
  // src/confidence.ts
540
712
  var RANK = {
541
713
  low: 0,
@@ -614,9 +786,7 @@ var Witness = class {
614
786
  var witnesses = {
615
787
  asserted: "asserted",
616
788
  absent: "absent",
617
- // A witness is built from the derivation alone, and saying so lets
618
- // the evaluator skip the body-tag lookups, which were most of the
619
- // tagged path's cost on a large sweep.
789
+ // A witness is built from the derivation alone.
620
790
  ignoresBodyTags: true,
621
791
  combine: (_bodyTags, derivation) => new Witness(derivation.rule, derivation.body),
622
792
  merge: (stored) => stored
@@ -678,6 +848,7 @@ var notLit = (relation, ...terms) => ({
678
848
  var rule = (relation, terms, body, name) => name === void 0 ? { head: { relation, terms }, body } : { head: { relation, terms }, body, name };
679
849
  var literalName = (literal) => literal.negated ? `!${literal.relation}` : literal.relation;
680
850
  var ruleLabel = (r) => r.name ?? `${r.head.relation} :- ${r.body.map(literalName).join(", ")}`;
851
+ var FEW_REMOVED = 16;
681
852
  var Database = class {
682
853
  store = /* @__PURE__ */ new Map();
683
854
  relation(name) {
@@ -688,15 +859,16 @@ var Database = class {
688
859
  const created = {
689
860
  index: new FactIndex(),
690
861
  tuples: [],
691
- columns: []
862
+ columns: [],
863
+ combined: /* @__PURE__ */ new Map()
692
864
  };
693
865
  this.store.set(name, created);
694
866
  return created;
695
867
  }
696
868
  /**
697
869
  * The key this relation gives a tuple, whether or not the fact is
698
- * present. Evaluation keys its ledger of derived facts by it; a
699
- * caller outside this module has nowhere to spend one.
870
+ * present. Evaluation keys its ledger of derived facts by it. Nothing
871
+ * outside this module accepts a key.
700
872
  */
701
873
  keyFor(relationName, tuple) {
702
874
  return this.relation(relationName).index.key(tuple);
@@ -736,6 +908,9 @@ var Database = class {
736
908
  addToBucket(buckets, value, tuple);
737
909
  }
738
910
  }
911
+ for (const combined of relation.combined.values()) {
912
+ addToCombined(combined, tuple);
913
+ }
739
914
  return "added";
740
915
  }
741
916
  /** The tag stored for this fact, or undefined when there is none. */
@@ -801,18 +976,45 @@ var Database = class {
801
976
  if (index.size === 0) {
802
977
  relation.tuples = [];
803
978
  index.clear();
979
+ relation.columns.length = 0;
980
+ relation.combined.clear();
981
+ forgetFacts(this, relationName, going);
982
+ return going.size;
983
+ }
984
+ for (const tuple of gone) {
985
+ index.prune(tuple);
986
+ }
987
+ const stored = gone.length * FEW_REMOVED < relation.tuples.length ? storedTuplesOf(relation, gone) : null;
988
+ if (stored !== null) {
989
+ removeTuples(relation, stored);
804
990
  } else {
805
991
  relation.tuples = relation.tuples.filter(
806
992
  (tuple) => index.find(tuple) !== void 0
807
993
  );
808
- for (const tuple of gone) {
809
- index.prune(tuple);
810
- }
994
+ relation.columns.length = 0;
995
+ relation.combined.clear();
811
996
  }
812
- relation.columns.length = 0;
813
997
  forgetFacts(this, relationName, going);
814
998
  return going.size;
815
999
  }
1000
+ /**
1001
+ * Remove every fact in a relation, and return how many there were. The
1002
+ * database ends up as `retract` leaves it when handed every tuple, but
1003
+ * nothing looks a tuple up to remove it.
1004
+ */
1005
+ retractAll(relationName) {
1006
+ const relation = this.store.get(relationName);
1007
+ if (relation === void 0 || relation.tuples.length === 0) {
1008
+ return 0;
1009
+ }
1010
+ const removed = relation.tuples.length;
1011
+ relation.tuples = [];
1012
+ relation.index.clear();
1013
+ relation.columns.length = 0;
1014
+ relation.combined.clear();
1015
+ forgetRelation(this, relationName);
1016
+ return removed;
1017
+ }
816
1018
  size(relationName) {
817
1019
  return this.store.get(relationName)?.tuples.length ?? 0;
818
1020
  }
@@ -820,6 +1022,25 @@ var Database = class {
820
1022
  return [...this.store.keys()];
821
1023
  }
822
1024
  };
1025
+ function rulesDeriving(rules, relations) {
1026
+ const wanted = new Set(relations);
1027
+ const pending = [...relations];
1028
+ while (pending.length > 0) {
1029
+ const relation = pending.pop();
1030
+ for (const r of rules) {
1031
+ if (r.head.relation !== relation) {
1032
+ continue;
1033
+ }
1034
+ for (const literal of r.body) {
1035
+ if (!wanted.has(literal.relation)) {
1036
+ wanted.add(literal.relation);
1037
+ pending.push(literal.relation);
1038
+ }
1039
+ }
1040
+ }
1041
+ }
1042
+ return rules.filter((r) => wanted.has(r.head.relation));
1043
+ }
823
1044
  function stratify(rules) {
824
1045
  const derived = new Set(rules.map((r) => r.head.relation));
825
1046
  const stratum = /* @__PURE__ */ new Map();
@@ -919,13 +1140,16 @@ function headTuple(head, bindings) {
919
1140
  });
920
1141
  }
921
1142
  var positiveLiterals = /* @__PURE__ */ new WeakMap();
922
- function deltaLiteral(r, deltaIndex) {
1143
+ function positivesOf(r) {
923
1144
  let positives = positiveLiterals.get(r);
924
1145
  if (positives === void 0) {
925
1146
  positives = r.body.map((literal, index) => literal.negated ? -1 : index).filter((index) => index !== -1);
926
1147
  positiveLiterals.set(r, positives);
927
1148
  }
928
- return positives[deltaIndex] ?? -1;
1149
+ return positives;
1150
+ }
1151
+ function deltaLiteral(r, deltaIndex) {
1152
+ return positivesOf(r)[deltaIndex] ?? -1;
929
1153
  }
930
1154
  var allBound = (literal, bindings) => literal.terms.every(
931
1155
  (term) => term.type === "constant" || boundValue(bindings, term.name) !== void 0
@@ -968,58 +1192,139 @@ function evaluateRule(db, deltas, r, deltaIndex, budget) {
968
1192
  results.push(headTuple(r.head, bindings));
969
1193
  return;
970
1194
  }
971
- let pick = -1;
972
- let narrowest = null;
973
- for (let index = 0; index < body.length; index++) {
974
- if (taken & 1 << index) {
975
- continue;
976
- }
977
- const literal = body[index];
978
- if (literal.negated) {
979
- if (allBound(literal, bindings)) {
980
- if (!db.has(literal.relation, groundNegated(literal, bindings))) {
981
- step(taken | 1 << index, bindings);
982
- }
983
- return;
984
- }
985
- continue;
986
- }
987
- const source = narrowedSource(db, literal, bindings);
988
- if (source === null) {
989
- continue;
990
- }
991
- if (source.length === 0) {
992
- return;
993
- }
994
- if (narrowest === null || source.length < narrowest.length) {
995
- pick = index;
996
- narrowest = source;
997
- }
998
- }
999
- if (narrowest !== null) {
1000
- walk2(pick, narrowest, taken, bindings);
1195
+ const index = nextLiteral(db, body, taken, bindings);
1196
+ if (index === -1) {
1001
1197
  return;
1002
1198
  }
1003
- for (let index = 0; index < body.length; index++) {
1004
- if (!(taken & 1 << index) && !body[index].negated) {
1005
- walk2(index, db.facts(body[index].relation), taken, bindings);
1006
- return;
1007
- }
1008
- }
1009
- for (let index = 0; index < body.length; index++) {
1010
- if (!(taken & 1 << index)) {
1011
- groundNegated(body[index], bindings);
1199
+ const literal = body[index];
1200
+ if (literal.negated) {
1201
+ if (!db.has(literal.relation, groundNegated(literal, bindings))) {
1202
+ step(taken | 1 << index, bindings);
1012
1203
  }
1204
+ return;
1013
1205
  }
1206
+ walk2(index, nextSource, taken, bindings);
1014
1207
  };
1015
1208
  if (deltaAt === -1) {
1016
1209
  step(0, null);
1017
- } else {
1018
- walk2(deltaAt, deltas.get(body[deltaAt].relation) ?? [], 0, null);
1210
+ return results;
1211
+ }
1212
+ const rows = deltaRows(deltas, body[deltaAt], budget);
1213
+ const start = smallerStart(db, body, deltaAt, rows);
1214
+ if (start === null) {
1215
+ walk2(deltaAt, rows, 0, null);
1216
+ return results;
1217
+ }
1218
+ const byValue = newFactsByColumn(rows, start.column, budget);
1219
+ const first = body[start.index];
1220
+ for (const tuple of start.source) {
1221
+ budget.examined++;
1222
+ if (budget.examined > budget.limit) {
1223
+ throw OUT_OF_BUDGET;
1224
+ }
1225
+ const bindings = unify(first, tuple, null);
1226
+ if (bindings === NO_MATCH) {
1227
+ continue;
1228
+ }
1229
+ const value = boundValue(bindings, start.variable);
1230
+ const matching = value === void 0 ? void 0 : byValue.get(value);
1231
+ if (matching !== void 0) {
1232
+ walk2(deltaAt, matching, 1 << start.index, bindings);
1233
+ }
1019
1234
  }
1020
1235
  return results;
1021
1236
  }
1022
- var boundSource = (db, literal, bindings) => narrowedSource(db, literal, bindings) ?? db.facts(literal.relation);
1237
+ var newFactIndexes = /* @__PURE__ */ new WeakMap();
1238
+ function smallerStart(db, body, deltaAt, rows) {
1239
+ const newFacts = body[deltaAt];
1240
+ const indexed = newFactIndexes.get(rows);
1241
+ let best = null;
1242
+ for (let index = 0; index < body.length; index++) {
1243
+ const literal = body[index];
1244
+ if (index === deltaAt || literal.negated) {
1245
+ continue;
1246
+ }
1247
+ const column = sharedColumn(newFacts, literal);
1248
+ if (column === -1) {
1249
+ continue;
1250
+ }
1251
+ const source = narrowedSource(db, literal, null) ?? db.facts(literal.relation);
1252
+ const worthIt = indexed?.has(column) ? source.length < rows.length : source.length * 2 < rows.length;
1253
+ if (worthIt && (best === null || source.length < best.source.length)) {
1254
+ best = {
1255
+ index,
1256
+ source,
1257
+ variable: newFacts.terms[column].name,
1258
+ column
1259
+ };
1260
+ }
1261
+ }
1262
+ return best;
1263
+ }
1264
+ function sharedColumn(from, other) {
1265
+ return from.terms.findIndex(
1266
+ (term) => term.type === "variable" && other.terms.some((t) => t.type === "variable" && t.name === term.name)
1267
+ );
1268
+ }
1269
+ function newFactsByColumn(rows, column, budget) {
1270
+ let byColumn = newFactIndexes.get(rows);
1271
+ if (byColumn === void 0) {
1272
+ byColumn = /* @__PURE__ */ new Map();
1273
+ newFactIndexes.set(rows, byColumn);
1274
+ }
1275
+ let byValue = byColumn.get(column);
1276
+ if (byValue === void 0) {
1277
+ byValue = /* @__PURE__ */ new Map();
1278
+ for (const tuple of rows) {
1279
+ const value = tuple[column];
1280
+ if (value !== void 0) {
1281
+ addToBucket(byValue, value, tuple);
1282
+ }
1283
+ }
1284
+ budget.examined += rows.length;
1285
+ byColumn.set(column, byValue);
1286
+ }
1287
+ return byValue;
1288
+ }
1289
+ var nextSource = [];
1290
+ function nextLiteral(db, body, taken, bindings) {
1291
+ let pick = -1;
1292
+ let narrowest = null;
1293
+ for (let index = 0; index < body.length; index++) {
1294
+ if (taken & 1 << index) {
1295
+ continue;
1296
+ }
1297
+ const literal = body[index];
1298
+ if (literal.negated) {
1299
+ if (allBound(literal, bindings)) {
1300
+ return index;
1301
+ }
1302
+ continue;
1303
+ }
1304
+ const source = narrowedSource(db, literal, bindings);
1305
+ if (source === null) {
1306
+ continue;
1307
+ }
1308
+ if (source.length === 0) {
1309
+ return -1;
1310
+ }
1311
+ if (narrowest === null || source.length < narrowest.length) {
1312
+ pick = index;
1313
+ narrowest = source;
1314
+ }
1315
+ }
1316
+ if (narrowest !== null) {
1317
+ nextSource = narrowedFurther(db, body[pick], bindings, narrowest);
1318
+ return nextSource.length === 0 ? -1 : pick;
1319
+ }
1320
+ for (let index = 0; index < body.length; index++) {
1321
+ if (!(taken & 1 << index) && !body[index].negated) {
1322
+ nextSource = db.facts(body[index].relation);
1323
+ return index;
1324
+ }
1325
+ }
1326
+ return body.findIndex((_, index) => !(taken & 1 << index));
1327
+ }
1023
1328
  function narrowedSource(db, literal, bindings) {
1024
1329
  const relation = db.relationOf(literal.relation);
1025
1330
  let narrowest = null;
@@ -1039,14 +1344,119 @@ function narrowedSource(db, literal, bindings) {
1039
1344
  }
1040
1345
  return narrowest;
1041
1346
  }
1347
+ function narrowedFurther(db, literal, bindings, source) {
1348
+ if (source.length <= 1) {
1349
+ return source;
1350
+ }
1351
+ let mask = 0;
1352
+ const values = [];
1353
+ for (let column = 0; column < literal.terms.length && column < 31; column++) {
1354
+ const term = literal.terms[column];
1355
+ const value = term.type === "constant" ? term.value : boundValue(bindings, term.name);
1356
+ if (value !== void 0) {
1357
+ mask |= 1 << column;
1358
+ values.push(value);
1359
+ }
1360
+ }
1361
+ const relation = db.relationOf(literal.relation);
1362
+ if (values.length < 2 || relation === void 0) {
1363
+ return source;
1364
+ }
1365
+ return combinedBucketIn(relation, mask, values);
1366
+ }
1367
+ var constantColumns = /* @__PURE__ */ new WeakMap();
1368
+ function constantColumnsOf(literal) {
1369
+ let known = constantColumns.get(literal);
1370
+ if (known === void 0) {
1371
+ const columns = [];
1372
+ const values = [];
1373
+ literal.terms.forEach((term, column) => {
1374
+ if (term.type === "constant") {
1375
+ columns.push(column);
1376
+ values.push(term.value);
1377
+ }
1378
+ });
1379
+ known = columns.length === 0 ? null : { columns, values };
1380
+ constantColumns.set(literal, known);
1381
+ }
1382
+ return known;
1383
+ }
1384
+ var deltaBuckets = /* @__PURE__ */ new WeakMap();
1385
+ var deltaBucketKey = (relation, column) => `${relation}\0${column}`;
1386
+ function hasNoNewRows(deltas, literal) {
1387
+ const rows = deltas.get(literal.relation) ?? [];
1388
+ const constants = constantColumnsOf(literal);
1389
+ if (rows.length === 0 || constants === null) {
1390
+ return rows.length === 0;
1391
+ }
1392
+ const buckets = deltaBuckets.get(deltas)?.get(deltaBucketKey(literal.relation, constants.columns[0]));
1393
+ return buckets !== void 0 && !buckets.has(constants.values[0]);
1394
+ }
1395
+ function deltaRows(deltas, literal, budget) {
1396
+ const rows = deltas.get(literal.relation) ?? [];
1397
+ const constants = constantColumnsOf(literal);
1398
+ if (constants === null || rows.length <= 1) {
1399
+ return rows;
1400
+ }
1401
+ let byColumn = deltaBuckets.get(deltas);
1402
+ if (byColumn === void 0) {
1403
+ byColumn = /* @__PURE__ */ new Map();
1404
+ deltaBuckets.set(deltas, byColumn);
1405
+ }
1406
+ const column = constants.columns[0];
1407
+ const key = deltaBucketKey(literal.relation, column);
1408
+ let buckets = byColumn.get(key);
1409
+ if (buckets === void 0) {
1410
+ buckets = /* @__PURE__ */ new Map();
1411
+ for (const tuple of rows) {
1412
+ const value = tuple[column];
1413
+ if (value !== void 0) {
1414
+ addToBucket(buckets, value, tuple);
1415
+ }
1416
+ }
1417
+ budget.examined += rows.length;
1418
+ byColumn.set(key, buckets);
1419
+ }
1420
+ const bucket = buckets.get(constants.values[0]) ?? [];
1421
+ if (constants.columns.length === 1) {
1422
+ return bucket;
1423
+ }
1424
+ return bucket.filter(
1425
+ (tuple) => constants.columns.every(
1426
+ (at, i) => at === column || tuple[at] === constants.values[i]
1427
+ )
1428
+ );
1429
+ }
1042
1430
  var EMPTY_TAGS = [];
1043
1431
  function evaluateRuleTagged(db, deltas, r, deltaIndex, algebra, budget) {
1044
1432
  const results = [];
1045
- const bodyTags = [];
1046
- const bodyMatches = [];
1433
+ const body = r.body;
1434
+ const deltaAt = deltaLiteral(r, deltaIndex);
1435
+ const whole = 2 ** body.length - 1;
1436
+ const bodyTags = new Array(body.length);
1437
+ const bodyMatches = new Array(body.length);
1047
1438
  const readsTags = algebra.ignoresBodyTags !== true;
1048
- const step = (literalIndex, positiveIndex, bindings) => {
1049
- if (literalIndex === r.body.length) {
1439
+ const walk2 = (index, source, taken, bindings) => {
1440
+ const literal = body[index];
1441
+ for (const tuple of source) {
1442
+ budget.examined++;
1443
+ const next = unify(literal, tuple, bindings);
1444
+ if (next !== NO_MATCH) {
1445
+ if (readsTags) {
1446
+ const stored = db.tagOf(literal.relation, tuple);
1447
+ bodyTags[index] = stored === void 0 ? algebra.asserted : stored;
1448
+ }
1449
+ bodyMatches[index] = {
1450
+ kind: "fact",
1451
+ relation: literal.relation,
1452
+ tuple
1453
+ };
1454
+ step(taken | 1 << index, next);
1455
+ }
1456
+ }
1457
+ };
1458
+ const step = (taken, bindings) => {
1459
+ if (taken === whole) {
1050
1460
  results.push({
1051
1461
  tuple: headTuple(r.head, bindings),
1052
1462
  tag: algebra.combine(readsTags ? bodyTags.slice() : EMPTY_TAGS, {
@@ -1056,51 +1466,33 @@ function evaluateRuleTagged(db, deltas, r, deltaIndex, algebra, budget) {
1056
1466
  });
1057
1467
  return;
1058
1468
  }
1059
- const literal = r.body[literalIndex];
1469
+ const index = nextLiteral(db, body, taken, bindings);
1470
+ if (index === -1) {
1471
+ return;
1472
+ }
1473
+ const literal = body[index];
1060
1474
  if (literal.negated) {
1061
1475
  const grounded = groundNegated(literal, bindings);
1062
1476
  if (!db.has(literal.relation, grounded)) {
1063
1477
  if (readsTags) {
1064
- bodyTags.push(algebra.absent);
1478
+ bodyTags[index] = algebra.absent;
1065
1479
  }
1066
- bodyMatches.push({
1480
+ bodyMatches[index] = {
1067
1481
  kind: "absence",
1068
1482
  relation: literal.relation,
1069
1483
  tuple: grounded
1070
- });
1071
- step(literalIndex + 1, positiveIndex, bindings);
1072
- if (readsTags) {
1073
- bodyTags.pop();
1074
- }
1075
- bodyMatches.pop();
1484
+ };
1485
+ step(taken | 1 << index, bindings);
1076
1486
  }
1077
1487
  return;
1078
1488
  }
1079
- const source = positiveIndex === deltaIndex ? deltas.get(literal.relation) ?? [] : boundSource(db, literal, bindings);
1080
- for (const tuple of source) {
1081
- budget.examined++;
1082
- const next = unify(literal, tuple, bindings);
1083
- if (next !== NO_MATCH) {
1084
- if (readsTags) {
1085
- const stored = db.tagOf(literal.relation, tuple);
1086
- bodyTags.push(
1087
- stored === void 0 ? algebra.asserted : stored
1088
- );
1089
- }
1090
- bodyMatches.push({
1091
- kind: "fact",
1092
- relation: literal.relation,
1093
- tuple
1094
- });
1095
- step(literalIndex + 1, positiveIndex + 1, next);
1096
- if (readsTags) {
1097
- bodyTags.pop();
1098
- }
1099
- bodyMatches.pop();
1100
- }
1101
- }
1489
+ walk2(index, nextSource, taken, bindings);
1102
1490
  };
1103
- step(0, 0, null);
1491
+ if (deltaAt === -1) {
1492
+ step(0, null);
1493
+ } else {
1494
+ walk2(deltaAt, deltaRows(deltas, body[deltaAt], budget), 0, null);
1495
+ }
1104
1496
  return results;
1105
1497
  }
1106
1498
  var evaluated = /* @__PURE__ */ new WeakMap();
@@ -1140,6 +1532,16 @@ function forgetFacts(db, relation, keys) {
1140
1532
  }
1141
1533
  }
1142
1534
  }
1535
+ function forgetRelation(db, relation) {
1536
+ const states = evaluated.get(db);
1537
+ if (states === void 0) {
1538
+ return;
1539
+ }
1540
+ for (const state of states.values()) {
1541
+ state.marks = null;
1542
+ state.derived.get(relation)?.clear();
1543
+ }
1544
+ }
1143
1545
  var usesNegation = (rules) => rules.some((r) => r.body.some((l) => l.negated));
1144
1546
  function canResume(rules, state) {
1145
1547
  return !usesNegation(rules) && state.marks !== null;
@@ -1172,6 +1574,8 @@ function evaluate(db, rules, algebra, budget) {
1172
1574
  throw new Error("cannot evaluate with both a tag algebra and a row budget");
1173
1575
  }
1174
1576
  deriving.set(db, (deriving.get(db) ?? 0) + 1);
1577
+ const profiling = isProfiling();
1578
+ const startedAt = profiling ? performance.now() : 0;
1175
1579
  try {
1176
1580
  return runRules(db, rules, algebra, budget);
1177
1581
  } finally {
@@ -1181,16 +1585,24 @@ function evaluate(db, rules, algebra, budget) {
1181
1585
  } else {
1182
1586
  deriving.set(db, depth);
1183
1587
  }
1588
+ if (profiling) {
1589
+ chargeEngine("evaluate", performance.now() - startedAt);
1590
+ }
1184
1591
  }
1185
1592
  }
1186
1593
  function clearRelations(db, rules, relations) {
1594
+ const profiling = isProfiling();
1595
+ const startedAt = profiling ? performance.now() : 0;
1187
1596
  for (const relation of relations) {
1188
- db.retract(relation, [...db.facts(relation)]);
1597
+ db.retractAll(relation);
1189
1598
  }
1190
1599
  const state = statesFor(db).get(signatureOf(rules));
1191
1600
  if (state !== void 0) {
1192
1601
  state.marks = currentMarks(db);
1193
1602
  }
1603
+ if (profiling) {
1604
+ chargeEngine("clear", performance.now() - startedAt);
1605
+ }
1194
1606
  }
1195
1607
  var signatureOf = (rules) => shapeOf(rules).signature;
1196
1608
  var shapes = /* @__PURE__ */ new WeakMap();
@@ -1204,7 +1616,7 @@ function shapeOf(rules) {
1204
1616
  signature: JSON.stringify(rules),
1205
1617
  name: [...derivedRelations].sort().join(", "),
1206
1618
  derivedRelations,
1207
- strata: stratify(rules)
1619
+ strata: stratify(rules).map(planStratum)
1208
1620
  };
1209
1621
  shapes.set(rules, shape);
1210
1622
  return shape;
@@ -1263,7 +1675,6 @@ function runRules(db, rules, algebra, given) {
1263
1675
  const marks = canResume(rules, state) ? state.marks : void 0;
1264
1676
  const runStratum = (stratum) => {
1265
1677
  let delta = /* @__PURE__ */ new Map();
1266
- const derivedHere = new Set(stratum.map((r) => r.head.relation));
1267
1678
  const record = (relation, tuple) => {
1268
1679
  if (db.add(relation, tuple) === "added") {
1269
1680
  addTo(delta, relation, tuple);
@@ -1317,17 +1728,18 @@ function runRules(db, rules, algebra, given) {
1317
1728
  );
1318
1729
  };
1319
1730
  const applyDelta = (seed, derivedOnly) => {
1320
- for (const r of stratum) {
1731
+ for (const at of rulesReading(stratum, seed, derivedOnly)) {
1732
+ const r = stratum.rules[at];
1321
1733
  if (!couldProduce(db, r)) {
1322
1734
  continue;
1323
1735
  }
1324
- const positives = r.body.filter((l) => !l.negated);
1736
+ const positives = positivesOf(r);
1325
1737
  for (let i = 0; i < positives.length; i++) {
1326
- const literal = positives[i];
1327
- if (derivedOnly && !derivedHere.has(literal.relation)) {
1738
+ const literal = r.body[positives[i]];
1739
+ if (derivedOnly && !stratum.derived.has(literal.relation)) {
1328
1740
  continue;
1329
1741
  }
1330
- if ((seed.get(literal.relation) ?? []).length === 0) {
1742
+ if (hasNoNewRows(seed, literal)) {
1331
1743
  continue;
1332
1744
  }
1333
1745
  runOneRule(r, seed, i);
@@ -1335,7 +1747,7 @@ function runRules(db, rules, algebra, given) {
1335
1747
  }
1336
1748
  };
1337
1749
  if (marks === void 0 || marks === null) {
1338
- for (const r of stratum) {
1750
+ for (const r of stratum.rules) {
1339
1751
  if (!couldProduce(db, r)) {
1340
1752
  continue;
1341
1753
  }
@@ -1421,6 +1833,7 @@ export {
1421
1833
  rowBudget,
1422
1834
  rule,
1423
1835
  ruleLabel,
1836
+ rulesDeriving,
1424
1837
  stratify,
1425
1838
  tupleKey,
1426
1839
  tupleKeyParts,