datalog: magic pre-saturation is conditional, not unconditional
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s

Previously dl-magic-query always pre-saturated the source db so it
gave correct results for stratified programs (where the rewriter
doesn't propagate magic to aggregate inner-goals or negated rels).
Pure positive programs paid the full bottom-up cost twice.

Add dl-rules-need-presaturation? — checks whether any rule body
contains an aggregate or negation. Only pre-saturate in that case.
Pure positive programs (the common case for magic-sets) keep their
full goal-directed efficiency.

276/276; identical answers on the existing aggregate-of-IDB test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-11 10:15:05 +00:00
parent fadcdbd6a9
commit 0df5e92c46

View File

@@ -373,6 +373,35 @@
rules)
seen))))
;; True iff any rule's body contains a literal kind that the magic
;; rewriter doesn't propagate magic to — i.e. an aggregate or a
;; negation. Used by dl-magic-query to decide whether to pre-saturate
;; the source db (for correctness on stratified programs) or skip
;; that step (preserving full magic-sets efficiency for pure
;; positive programs).
(define
dl-rule-has-nonprop-lit?
(fn
(body i n)
(cond
((>= i n) false)
((let ((lit (nth body i)))
(or (and (dict? lit) (has-key? lit :neg))
(dl-aggregate? lit)))
true)
(else (dl-rule-has-nonprop-lit? body (+ i 1) n)))))
(define
dl-rules-need-presaturation?
(fn
(rules)
(cond
((= (len rules) 0) false)
((let ((body (get (first rules) :body)))
(dl-rule-has-nonprop-lit? body 0 (len body)))
true)
(else (dl-rules-need-presaturation? (rest rules))))))
(define
dl-magic-query
(fn
@@ -393,17 +422,18 @@
(dl-query db query-goal))
(else
(do
;; Pre-saturate the source db. The magic rewriter passes
;; aggregate body lits through unchanged (no magic
;; propagation generated for them) — so if an aggregate's
;; inner-goal relation is IDB, it would be empty in the
;; magic db unless its tuples are already materialised in
;; the source. Same for negation against IDB. Pre-saturating
;; guarantees `dl-magic-query` returns the same answers as
;; `dl-query` for every stratified program; the efficiency
;; benefit then comes from goal-directed re-derivation of
;; the query relation under the magic seed.
(dl-saturate! db)
;; If the rule set has aggregates or negation, pre-saturate
;; the source db before copying facts. The magic rewriter
;; passes aggregate body lits and negated lits through
;; unchanged (no magic propagation generated for them) — so
;; if their inner-goal relation is IDB, it would be empty in
;; the magic db. Pre-saturating ensures equivalence with
;; `dl-query` for every stratified program. Pure positive
;; programs skip this and keep the full magic-sets perf win
;; from goal-directed re-derivation.
(when
(dl-rules-need-presaturation? (dl-rules db))
(dl-saturate! db))
(let
((query-rel (dl-rel-name query-goal))
(query-adn (dl-adorn-goal query-goal)))