Commit Graph

8 Commits

Author SHA1 Message Date
9e380fd96e datalog: aggregate validates that agg-var appears in goal
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 37s
`count(N, Y, p(X))` silently returned `N = 1` because `Y` was never
bound by the goal — every match contributed the same unbound symbol
which dl-val-member? deduped to a single entry. Similarly:

  sum(S, Y, p(X))    => raises "expected number, got symbol"
  findall(L, Y, p(X)) => L = (Y)  (a list containing the unbound symbol)
  count(N, Y, p(X))   => N = 1    (silent garbage)

Added a third validator in dl-eval-aggregate: the agg-var must
syntactically appear among the goal's variables. Error names the
variable and the goal and explains why the result would be
meaningless.

1 new test; conformance 263/263.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 07:57:01 +00:00
dcae125955 datalog: aggregate arg validators (259/259)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 28s
Bug: dl-eval-aggregate accepted non-variable agg-vars and non-
literal goals silently, producing weird/incorrect counts:
- `count(N, 5, p(X))` would compute count over the single
  constant 5 (always 1), ignoring p entirely.
- `count(N, X, 42)` would crash with "unknown body-literal
  shape" at saturation time rather than at rule-add time.

Fix: dl-eval-aggregate now validates up front that the second
arg is a variable (the value to aggregate) and the third arg is
a positive literal (the goal). Errors are descriptive and
include the offending argument.

2 new aggregate tests.
2026-05-11 07:26:48 +00:00
f12edc8fd9 datalog: indirect aggregate cycle rejected (230/230)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 57s
2026-05-08 14:08:02 +00:00
de734b27b8 datalog: group-by-via-aggregate-in-rule test (216/216)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
2026-05-08 10:22:03 +00:00
d66ddc614b datalog: aggregates work as top-level query goals (183/183)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 40s
Bug: dl-match-lit (the naive matcher used by dl-find-bindings)
was missing dl-aggregate? dispatch — it was only present in
dl-fbs-aux (semi-naive). Symptom:
  (dl-query db '(count N X (p X)))
silently returned ().

Two fixes:
- Add aggregate branch to dl-match-lit before the positive case.
- dl-query-user-vars now projects only the result var (first arg)
  of an aggregate goal — the aggregated var and inner-goal vars
  are existentials and should not leak into substitutions.

2 new aggregate tests cover count and findall as direct query goals.
2026-05-08 09:45:15 +00:00
b95d8c5a63 datalog: stratifier rejects recursion through aggregation (165/165)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 53s
Bug: dl-check-stratifiable iterated body literals looking only for
explicit :neg literals, missing aggregate cycles. Now also walks
aggregates via dl-aggregate-dep-edge — q(N) :- count(N, X, q(X))
correctly errors out at saturation time.

3 new tests cover:
- recursion-through-aggregation rejected
- negation + aggregation coexist when in different strata
- min over empty derived relation produces no result
2026-05-08 09:13:10 +00:00
55286cc5bc datalog: findall aggregate (159/159)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 53s
(findall L V Goal) — bind L to the distinct V values for which Goal
holds, or the empty list when none. One-line addition to
dl-do-aggregate that returns the unreduced list. Tests cover EDB,
derived relation, and empty cases.

Useful for "give me all the X such that ..." queries without
scalar reduction.
2026-05-08 09:02:43 +00:00
6d04cf7bf2 datalog: aggregation count/sum/min/max (Phase 8, 134/134)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 50s
New lib/datalog/aggregates.sx: (count R V Goal), (sum R V Goal),
(min R V Goal), (max R V Goal). dl-eval-aggregate runs
dl-find-bindings on the goal under the outer subst, collects
distinct values of V, applies the operator, binds R. Empty input:
count/sum return 0; min/max produce no binding (rule fails).

Group-by emerges naturally from outer-subst substitution into the
goal — `popular(P) :- post(P), count(N, U, liked(U, P)), >=(N, 3).`
counts per-post.

Stratifier extended: dl-aggregate-dep-edge contributes a
negation-like edge so the aggregate's goal relation is fully
derived before the aggregate fires (non-monotonicity respected).
Safety relaxed for aggregates: goal-internal vars are existentials,
only the result var becomes bound.
2026-05-08 08:28:45 +00:00