@suss/datalog 0.31.1 → 0.31.2

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.
Files changed (3) hide show
  1. package/dist/index.cjs +102 -81
  2. package/dist/index.js +102 -81
  3. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -77,6 +77,7 @@ function deriveOnDemand(rules, complete) {
77
77
  }
78
78
  }
79
79
  const derives = (relation) => rulesByHead.has(relation);
80
+ const labels = labelColumns(rules, derives);
80
81
  const asked = /* @__PURE__ */ new Map();
81
82
  const pending = [];
82
83
  const want = (adorned) => {
@@ -98,7 +99,7 @@ function deriveOnDemand(rules, complete) {
98
99
  while (pending.length > 0) {
99
100
  const adorned = pending.pop();
100
101
  for (const written of rulesByHead.get(adorned.relation) ?? []) {
101
- const r = boundFirst(written, adorned.adornment);
102
+ const r = boundFirst(written, adorned.adornment, labels);
102
103
  const adornments = bodyAdornments(r, adorned.adornment, derives);
103
104
  r.body.forEach((literal, at) => {
104
105
  const adornment = adornments[at];
@@ -141,7 +142,7 @@ function deriveOnDemand(rules, complete) {
141
142
  });
142
143
  }
143
144
  for (const written of rulesByHead.get(adorned.relation) ?? []) {
144
- const r = boundFirst(written, adorned.adornment);
145
+ const r = boundFirst(written, adorned.adornment, labels);
145
146
  const guard = anyBound(adorned.adornment) ? [
146
147
  positiveLiteral(
147
148
  demandOn(adorned.relation, adorned.adornment),
@@ -182,7 +183,30 @@ function deriveOnDemand(rules, complete) {
182
183
  return { rules: rewritten, demandDriven: [...demandDriven], demands };
183
184
  }
184
185
  var sameLiteral = (premises, conclusion) => premises.length === 1 && JSON.stringify(premises[0]) === JSON.stringify(conclusion);
185
- function boundFirst(r, headAdornment) {
186
+ function labelColumns(rules, derives) {
187
+ const labels = /* @__PURE__ */ new Map();
188
+ const note = ({
189
+ relation,
190
+ terms
191
+ }) => {
192
+ if (!derives(relation)) {
193
+ return;
194
+ }
195
+ terms.forEach((term, column) => {
196
+ if (term.type === "constant") {
197
+ const columns = labels.get(relation) ?? /* @__PURE__ */ new Set();
198
+ columns.add(column);
199
+ labels.set(relation, columns);
200
+ }
201
+ });
202
+ };
203
+ for (const r of rules) {
204
+ note(r.head);
205
+ r.body.forEach(note);
206
+ }
207
+ return labels;
208
+ }
209
+ function boundFirst(r, headAdornment, labels) {
186
210
  const bound = /* @__PURE__ */ new Set();
187
211
  r.head.terms.forEach((term, column) => {
188
212
  if (headAdornment[column] && term.type === "variable") {
@@ -190,7 +214,7 @@ function boundFirst(r, headAdornment) {
190
214
  }
191
215
  });
192
216
  const boundColumns = (literal) => literal.terms.filter(
193
- (term) => term.type === "constant" || bound.has(term.name)
217
+ (term, column) => !labels.get(literal.relation)?.has(column) && (term.type === "constant" || bound.has(term.name))
194
218
  ).length;
195
219
  const remaining = [...r.body];
196
220
  const body = [];
@@ -838,65 +862,18 @@ function headTuple(head, bindings) {
838
862
  return bound;
839
863
  });
840
864
  }
841
- var variablesOf = (literal) => literal.terms.filter((term) => term.type === "variable").map((term) => term.name);
842
- var plans = /* @__PURE__ */ new WeakMap();
843
- function bodyPlan(r, deltaIndex) {
844
- let byDelta = plans.get(r);
845
- if (byDelta === void 0) {
846
- byDelta = /* @__PURE__ */ new Map();
847
- plans.set(r, byDelta);
848
- }
849
- const known = byDelta.get(deltaIndex);
850
- if (known !== void 0) {
851
- return known;
852
- }
853
- const positives = r.body.map((literal, index) => literal.negated ? -1 : index).filter((index) => index !== -1);
854
- const deltaAt = positives[deltaIndex] ?? -1;
855
- const computed = { deltaAt, order: planBodyOrder(r, deltaAt) };
856
- byDelta.set(deltaIndex, computed);
857
- return computed;
858
- }
859
- function planBodyOrder(r, deltaAt) {
860
- const natural = r.body.map((_, i) => i);
861
- if (deltaAt <= 0) {
862
- return natural;
863
- }
864
- const taken = r.body.map(() => false);
865
- const bound = /* @__PURE__ */ new Set();
866
- const order = [];
867
- const take = (index) => {
868
- taken[index] = true;
869
- order.push(index);
870
- for (const name of variablesOf(r.body[index])) {
871
- bound.add(name);
872
- }
873
- };
874
- const firstWhere = (want) => natural.find((index) => !taken[index] && want(index)) ?? -1;
875
- const nextLiteral = () => {
876
- const ready = firstWhere(
877
- (i) => r.body[i].negated && variablesOf(r.body[i]).every((name) => bound.has(name))
878
- );
879
- if (ready !== -1) {
880
- return ready;
881
- }
882
- const joined = firstWhere(
883
- (i) => !r.body[i].negated && variablesOf(r.body[i]).some((name) => bound.has(name))
884
- );
885
- if (joined !== -1) {
886
- return joined;
887
- }
888
- const disconnected = firstWhere((i) => !r.body[i].negated);
889
- if (disconnected !== -1) {
890
- return disconnected;
891
- }
892
- return firstWhere(() => true);
893
- };
894
- take(deltaAt);
895
- while (order.length < r.body.length) {
896
- take(nextLiteral());
897
- }
898
- return order;
865
+ var positiveLiterals = /* @__PURE__ */ new WeakMap();
866
+ function deltaLiteral(r, deltaIndex) {
867
+ let positives = positiveLiterals.get(r);
868
+ if (positives === void 0) {
869
+ positives = r.body.map((literal, index) => literal.negated ? -1 : index).filter((index) => index !== -1);
870
+ positiveLiterals.set(r, positives);
871
+ }
872
+ return positives[deltaIndex] ?? -1;
899
873
  }
874
+ var allBound = (literal, bindings) => literal.terms.every(
875
+ (term) => term.type === "constant" || boundValue(bindings, term.name) !== void 0
876
+ );
900
877
  var rowBudget = (limit) => ({
901
878
  examined: 0,
902
879
  limit
@@ -914,21 +891,11 @@ var BudgetExhausted = class extends Error {
914
891
  };
915
892
  function evaluateRule(db, deltas, r, deltaIndex, budget) {
916
893
  const results = [];
917
- const { deltaAt, order } = bodyPlan(r, deltaIndex);
918
- const step = (orderIndex, bindings) => {
919
- if (orderIndex === order.length) {
920
- results.push(headTuple(r.head, bindings));
921
- return;
922
- }
923
- const literalIndex = order[orderIndex];
924
- const literal = r.body[literalIndex];
925
- if (literal.negated) {
926
- if (!db.has(literal.relation, groundNegated(literal, bindings))) {
927
- step(orderIndex + 1, bindings);
928
- }
929
- return;
930
- }
931
- const source = literalIndex === deltaAt ? deltas.get(literal.relation) ?? [] : boundSource(db, literal, bindings);
894
+ const body = r.body;
895
+ const deltaAt = deltaLiteral(r, deltaIndex);
896
+ const whole = 2 ** body.length - 1;
897
+ const walk2 = (index, source, taken, bindings) => {
898
+ const literal = body[index];
932
899
  for (const tuple of source) {
933
900
  budget.examined++;
934
901
  if (budget.examined > budget.limit) {
@@ -936,14 +903,68 @@ function evaluateRule(db, deltas, r, deltaIndex, budget) {
936
903
  }
937
904
  const next = unify(literal, tuple, bindings);
938
905
  if (next !== NO_MATCH) {
939
- step(orderIndex + 1, next);
906
+ step(taken | 1 << index, next);
940
907
  }
941
908
  }
942
909
  };
943
- step(0, null);
910
+ const step = (taken, bindings) => {
911
+ if (taken === whole) {
912
+ results.push(headTuple(r.head, bindings));
913
+ return;
914
+ }
915
+ let pick = -1;
916
+ let narrowest = null;
917
+ for (let index = 0; index < body.length; index++) {
918
+ if (taken & 1 << index) {
919
+ continue;
920
+ }
921
+ const literal = body[index];
922
+ if (literal.negated) {
923
+ if (allBound(literal, bindings)) {
924
+ if (!db.has(literal.relation, groundNegated(literal, bindings))) {
925
+ step(taken | 1 << index, bindings);
926
+ }
927
+ return;
928
+ }
929
+ continue;
930
+ }
931
+ const source = narrowedSource(db, literal, bindings);
932
+ if (source === null) {
933
+ continue;
934
+ }
935
+ if (source.length === 0) {
936
+ return;
937
+ }
938
+ if (narrowest === null || source.length < narrowest.length) {
939
+ pick = index;
940
+ narrowest = source;
941
+ }
942
+ }
943
+ if (narrowest !== null) {
944
+ walk2(pick, narrowest, taken, bindings);
945
+ return;
946
+ }
947
+ for (let index = 0; index < body.length; index++) {
948
+ if (!(taken & 1 << index) && !body[index].negated) {
949
+ walk2(index, db.facts(body[index].relation), taken, bindings);
950
+ return;
951
+ }
952
+ }
953
+ for (let index = 0; index < body.length; index++) {
954
+ if (!(taken & 1 << index)) {
955
+ groundNegated(body[index], bindings);
956
+ }
957
+ }
958
+ };
959
+ if (deltaAt === -1) {
960
+ step(0, null);
961
+ } else {
962
+ walk2(deltaAt, deltas.get(body[deltaAt].relation) ?? [], 0, null);
963
+ }
944
964
  return results;
945
965
  }
946
- function boundSource(db, literal, bindings) {
966
+ var boundSource = (db, literal, bindings) => narrowedSource(db, literal, bindings) ?? db.facts(literal.relation);
967
+ function narrowedSource(db, literal, bindings) {
947
968
  let narrowest = null;
948
969
  for (let column = 0; column < literal.terms.length; column++) {
949
970
  const term = literal.terms[column];
@@ -959,7 +980,7 @@ function boundSource(db, literal, bindings) {
959
980
  narrowest = bucket;
960
981
  }
961
982
  }
962
- return narrowest ?? db.facts(literal.relation);
983
+ return narrowest;
963
984
  }
964
985
  var EMPTY_TAGS = [];
965
986
  function evaluateRuleTagged(db, deltas, r, deltaIndex, algebra, budget) {
package/dist/index.js CHANGED
@@ -27,6 +27,7 @@ function deriveOnDemand(rules, complete) {
27
27
  }
28
28
  }
29
29
  const derives = (relation) => rulesByHead.has(relation);
30
+ const labels = labelColumns(rules, derives);
30
31
  const asked = /* @__PURE__ */ new Map();
31
32
  const pending = [];
32
33
  const want = (adorned) => {
@@ -48,7 +49,7 @@ function deriveOnDemand(rules, complete) {
48
49
  while (pending.length > 0) {
49
50
  const adorned = pending.pop();
50
51
  for (const written of rulesByHead.get(adorned.relation) ?? []) {
51
- const r = boundFirst(written, adorned.adornment);
52
+ const r = boundFirst(written, adorned.adornment, labels);
52
53
  const adornments = bodyAdornments(r, adorned.adornment, derives);
53
54
  r.body.forEach((literal, at) => {
54
55
  const adornment = adornments[at];
@@ -91,7 +92,7 @@ function deriveOnDemand(rules, complete) {
91
92
  });
92
93
  }
93
94
  for (const written of rulesByHead.get(adorned.relation) ?? []) {
94
- const r = boundFirst(written, adorned.adornment);
95
+ const r = boundFirst(written, adorned.adornment, labels);
95
96
  const guard = anyBound(adorned.adornment) ? [
96
97
  positiveLiteral(
97
98
  demandOn(adorned.relation, adorned.adornment),
@@ -132,7 +133,30 @@ function deriveOnDemand(rules, complete) {
132
133
  return { rules: rewritten, demandDriven: [...demandDriven], demands };
133
134
  }
134
135
  var sameLiteral = (premises, conclusion) => premises.length === 1 && JSON.stringify(premises[0]) === JSON.stringify(conclusion);
135
- function boundFirst(r, headAdornment) {
136
+ function labelColumns(rules, derives) {
137
+ const labels = /* @__PURE__ */ new Map();
138
+ const note = ({
139
+ relation,
140
+ terms
141
+ }) => {
142
+ if (!derives(relation)) {
143
+ return;
144
+ }
145
+ terms.forEach((term, column) => {
146
+ if (term.type === "constant") {
147
+ const columns = labels.get(relation) ?? /* @__PURE__ */ new Set();
148
+ columns.add(column);
149
+ labels.set(relation, columns);
150
+ }
151
+ });
152
+ };
153
+ for (const r of rules) {
154
+ note(r.head);
155
+ r.body.forEach(note);
156
+ }
157
+ return labels;
158
+ }
159
+ function boundFirst(r, headAdornment, labels) {
136
160
  const bound = /* @__PURE__ */ new Set();
137
161
  r.head.terms.forEach((term, column) => {
138
162
  if (headAdornment[column] && term.type === "variable") {
@@ -140,7 +164,7 @@ function boundFirst(r, headAdornment) {
140
164
  }
141
165
  });
142
166
  const boundColumns = (literal) => literal.terms.filter(
143
- (term) => term.type === "constant" || bound.has(term.name)
167
+ (term, column) => !labels.get(literal.relation)?.has(column) && (term.type === "constant" || bound.has(term.name))
144
168
  ).length;
145
169
  const remaining = [...r.body];
146
170
  const body = [];
@@ -788,65 +812,18 @@ function headTuple(head, bindings) {
788
812
  return bound;
789
813
  });
790
814
  }
791
- var variablesOf = (literal) => literal.terms.filter((term) => term.type === "variable").map((term) => term.name);
792
- var plans = /* @__PURE__ */ new WeakMap();
793
- function bodyPlan(r, deltaIndex) {
794
- let byDelta = plans.get(r);
795
- if (byDelta === void 0) {
796
- byDelta = /* @__PURE__ */ new Map();
797
- plans.set(r, byDelta);
798
- }
799
- const known = byDelta.get(deltaIndex);
800
- if (known !== void 0) {
801
- return known;
802
- }
803
- const positives = r.body.map((literal, index) => literal.negated ? -1 : index).filter((index) => index !== -1);
804
- const deltaAt = positives[deltaIndex] ?? -1;
805
- const computed = { deltaAt, order: planBodyOrder(r, deltaAt) };
806
- byDelta.set(deltaIndex, computed);
807
- return computed;
808
- }
809
- function planBodyOrder(r, deltaAt) {
810
- const natural = r.body.map((_, i) => i);
811
- if (deltaAt <= 0) {
812
- return natural;
813
- }
814
- const taken = r.body.map(() => false);
815
- const bound = /* @__PURE__ */ new Set();
816
- const order = [];
817
- const take = (index) => {
818
- taken[index] = true;
819
- order.push(index);
820
- for (const name of variablesOf(r.body[index])) {
821
- bound.add(name);
822
- }
823
- };
824
- const firstWhere = (want) => natural.find((index) => !taken[index] && want(index)) ?? -1;
825
- const nextLiteral = () => {
826
- const ready = firstWhere(
827
- (i) => r.body[i].negated && variablesOf(r.body[i]).every((name) => bound.has(name))
828
- );
829
- if (ready !== -1) {
830
- return ready;
831
- }
832
- const joined = firstWhere(
833
- (i) => !r.body[i].negated && variablesOf(r.body[i]).some((name) => bound.has(name))
834
- );
835
- if (joined !== -1) {
836
- return joined;
837
- }
838
- const disconnected = firstWhere((i) => !r.body[i].negated);
839
- if (disconnected !== -1) {
840
- return disconnected;
841
- }
842
- return firstWhere(() => true);
843
- };
844
- take(deltaAt);
845
- while (order.length < r.body.length) {
846
- take(nextLiteral());
847
- }
848
- return order;
815
+ var positiveLiterals = /* @__PURE__ */ new WeakMap();
816
+ function deltaLiteral(r, deltaIndex) {
817
+ let positives = positiveLiterals.get(r);
818
+ if (positives === void 0) {
819
+ positives = r.body.map((literal, index) => literal.negated ? -1 : index).filter((index) => index !== -1);
820
+ positiveLiterals.set(r, positives);
821
+ }
822
+ return positives[deltaIndex] ?? -1;
849
823
  }
824
+ var allBound = (literal, bindings) => literal.terms.every(
825
+ (term) => term.type === "constant" || boundValue(bindings, term.name) !== void 0
826
+ );
850
827
  var rowBudget = (limit) => ({
851
828
  examined: 0,
852
829
  limit
@@ -864,21 +841,11 @@ var BudgetExhausted = class extends Error {
864
841
  };
865
842
  function evaluateRule(db, deltas, r, deltaIndex, budget) {
866
843
  const results = [];
867
- const { deltaAt, order } = bodyPlan(r, deltaIndex);
868
- const step = (orderIndex, bindings) => {
869
- if (orderIndex === order.length) {
870
- results.push(headTuple(r.head, bindings));
871
- return;
872
- }
873
- const literalIndex = order[orderIndex];
874
- const literal = r.body[literalIndex];
875
- if (literal.negated) {
876
- if (!db.has(literal.relation, groundNegated(literal, bindings))) {
877
- step(orderIndex + 1, bindings);
878
- }
879
- return;
880
- }
881
- const source = literalIndex === deltaAt ? deltas.get(literal.relation) ?? [] : boundSource(db, literal, bindings);
844
+ const body = r.body;
845
+ const deltaAt = deltaLiteral(r, deltaIndex);
846
+ const whole = 2 ** body.length - 1;
847
+ const walk2 = (index, source, taken, bindings) => {
848
+ const literal = body[index];
882
849
  for (const tuple of source) {
883
850
  budget.examined++;
884
851
  if (budget.examined > budget.limit) {
@@ -886,14 +853,68 @@ function evaluateRule(db, deltas, r, deltaIndex, budget) {
886
853
  }
887
854
  const next = unify(literal, tuple, bindings);
888
855
  if (next !== NO_MATCH) {
889
- step(orderIndex + 1, next);
856
+ step(taken | 1 << index, next);
890
857
  }
891
858
  }
892
859
  };
893
- step(0, null);
860
+ const step = (taken, bindings) => {
861
+ if (taken === whole) {
862
+ results.push(headTuple(r.head, bindings));
863
+ return;
864
+ }
865
+ let pick = -1;
866
+ let narrowest = null;
867
+ for (let index = 0; index < body.length; index++) {
868
+ if (taken & 1 << index) {
869
+ continue;
870
+ }
871
+ const literal = body[index];
872
+ if (literal.negated) {
873
+ if (allBound(literal, bindings)) {
874
+ if (!db.has(literal.relation, groundNegated(literal, bindings))) {
875
+ step(taken | 1 << index, bindings);
876
+ }
877
+ return;
878
+ }
879
+ continue;
880
+ }
881
+ const source = narrowedSource(db, literal, bindings);
882
+ if (source === null) {
883
+ continue;
884
+ }
885
+ if (source.length === 0) {
886
+ return;
887
+ }
888
+ if (narrowest === null || source.length < narrowest.length) {
889
+ pick = index;
890
+ narrowest = source;
891
+ }
892
+ }
893
+ if (narrowest !== null) {
894
+ walk2(pick, narrowest, taken, bindings);
895
+ return;
896
+ }
897
+ for (let index = 0; index < body.length; index++) {
898
+ if (!(taken & 1 << index) && !body[index].negated) {
899
+ walk2(index, db.facts(body[index].relation), taken, bindings);
900
+ return;
901
+ }
902
+ }
903
+ for (let index = 0; index < body.length; index++) {
904
+ if (!(taken & 1 << index)) {
905
+ groundNegated(body[index], bindings);
906
+ }
907
+ }
908
+ };
909
+ if (deltaAt === -1) {
910
+ step(0, null);
911
+ } else {
912
+ walk2(deltaAt, deltas.get(body[deltaAt].relation) ?? [], 0, null);
913
+ }
894
914
  return results;
895
915
  }
896
- function boundSource(db, literal, bindings) {
916
+ var boundSource = (db, literal, bindings) => narrowedSource(db, literal, bindings) ?? db.facts(literal.relation);
917
+ function narrowedSource(db, literal, bindings) {
897
918
  let narrowest = null;
898
919
  for (let column = 0; column < literal.terms.length; column++) {
899
920
  const term = literal.terms[column];
@@ -909,7 +930,7 @@ function boundSource(db, literal, bindings) {
909
930
  narrowest = bucket;
910
931
  }
911
932
  }
912
- return narrowest ?? db.facts(literal.relation);
933
+ return narrowest;
913
934
  }
914
935
  var EMPTY_TAGS = [];
915
936
  function evaluateRuleTagged(db, deltas, r, deltaIndex, algebra, budget) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/datalog",
3
- "version": "0.31.1",
3
+ "version": "0.31.2",
4
4
  "type": "module",
5
5
  "description": "A small semi-naive Datalog evaluator with stratified negation, the rules engine suss runs its whole-program analyses on.",
6
6
  "keywords": [