diff --git a/lib/datalog/aggregates.sx b/lib/datalog/aggregates.sx index 153879f7..27117fd6 100644 --- a/lib/datalog/aggregates.sx +++ b/lib/datalog/aggregates.sx @@ -19,7 +19,7 @@ ;; ;; Empty input: count → 0, sum → 0, min/max → no binding (rule fails). -(define dl-aggregate-rels (list "count" "sum" "min" "max")) +(define dl-aggregate-rels (list "count" "sum" "min" "max" "findall")) (define dl-aggregate? @@ -43,6 +43,7 @@ (cond ((= op "count") (len vals)) ((= op "sum") (dl-sum-vals vals 0)) + ((= op "findall") vals) ((= op "min") (cond ((= (len vals) 0) :empty) diff --git a/lib/datalog/scoreboard.json b/lib/datalog/scoreboard.json index a855f4e0..f184d68b 100644 --- a/lib/datalog/scoreboard.json +++ b/lib/datalog/scoreboard.json @@ -1,8 +1,8 @@ { "lang": "datalog", - "total_passed": 156, + "total_passed": 159, "total_failed": 0, - "total": 156, + "total": 159, "suites": [ {"name":"tokenize","passed":26,"failed":0,"total":26}, {"name":"parse","passed":18,"failed":0,"total":18}, @@ -11,9 +11,9 @@ {"name":"builtins","passed":19,"failed":0,"total":19}, {"name":"semi_naive","passed":8,"failed":0,"total":8}, {"name":"negation","passed":10,"failed":0,"total":10}, - {"name":"aggregates","passed":10,"failed":0,"total":10}, + {"name":"aggregates","passed":13,"failed":0,"total":13}, {"name":"api","passed":9,"failed":0,"total":9}, {"name":"demo","passed":10,"failed":0,"total":10} ], - "generated": "2026-05-08T08:57:57+00:00" + "generated": "2026-05-08T09:02:31+00:00" } diff --git a/lib/datalog/scoreboard.md b/lib/datalog/scoreboard.md index 6977408c..7707e42a 100644 --- a/lib/datalog/scoreboard.md +++ b/lib/datalog/scoreboard.md @@ -1,6 +1,6 @@ # datalog scoreboard -**156 / 156 passing** (0 failure(s)). +**159 / 159 passing** (0 failure(s)). | Suite | Passed | Total | Status | |-------|--------|-------|--------| @@ -11,6 +11,6 @@ | builtins | 19 | 19 | ok | | semi_naive | 8 | 8 | ok | | negation | 10 | 10 | ok | -| aggregates | 10 | 10 | ok | +| aggregates | 13 | 13 | ok | | api | 9 | 9 | ok | | demo | 10 | 10 | ok | diff --git a/lib/datalog/tests/aggregates.sx b/lib/datalog/tests/aggregates.sx index d80276d8..a582ed8a 100644 --- a/lib/datalog/tests/aggregates.sx +++ b/lib/datalog/tests/aggregates.sx @@ -199,6 +199,33 @@ (list (quote popular) (quote P))) (list {:P (quote p1)})) + ;; findall: collect distinct values into a list. + (dl-at-test-set! "findall over EDB" + (dl-query + (dl-program + "p(a). p(b). p(c). + all_p(L) :- findall(L, X, p(X)).") + (list (quote all_p) (quote L))) + (list {:L (list (quote a) (quote b) (quote c))})) + + (dl-at-test-set! "findall over derived" + (dl-query + (dl-program + "parent(a, b). parent(b, c). parent(c, d). + ancestor(X, Y) :- parent(X, Y). + ancestor(X, Z) :- parent(X, Y), ancestor(Y, Z). + desc(L) :- findall(L, X, ancestor(a, X)).") + (list (quote desc) (quote L))) + (list {:L (list (quote b) (quote c) (quote d))})) + + (dl-at-test-set! "findall empty" + (dl-query + (dl-program + "p(1). + all_q(L) :- findall(L, X, q(X)).") + (list (quote all_q) (quote L))) + (list {:L (list)})) + ;; Aggregate vs single distinct. (dl-at-test-set! "distinct counted once" (dl-query diff --git a/plans/datalog-on-sx.md b/plans/datalog-on-sx.md index 21f6733a..28f6495e 100644 --- a/plans/datalog-on-sx.md +++ b/plans/datalog-on-sx.md @@ -186,9 +186,10 @@ large graphs. ### Phase 8 — aggregation (Datalog+) - [x] `(count R V Goal)`, `(sum R V Goal)`, `(min R V Goal)`, - `(max R V Goal)` — first arg is the result variable, second is the - aggregated variable, third is the goal literal. Live in - `lib/datalog/aggregates.sx`. + `(max R V Goal)`, `(findall L V Goal)` — first arg is the result + variable, second is the aggregated variable, third is the goal + literal. `findall` returns the distinct-value list itself; the + others reduce. Live in `lib/datalog/aggregates.sx`. - [x] `dl-eval-aggregate`: runs `dl-find-bindings` on the goal under the current subst (which provides outer-context bindings), collects distinct values of the aggregated var, applies the aggregate. @@ -269,6 +270,13 @@ large graphs. _Newest first._ +- 2026-05-08 — Phase 8 extension: `findall L V Goal` aggregate. Bind + L to the list of distinct V values for which Goal holds (or the + empty list when no matches). Implemented as a one-line case in + `dl-do-aggregate`. 3 new tests: EDB, derived relation, empty. + Useful for "give me all the X such that …" queries without + scalar reduction. + - 2026-05-08 — Phase 5d semantic fix: anonymous `_` variables are renamed per occurrence at `dl-add-rule!` and `dl-query` time so `(p X _) (p _ Y)` no longer unifies the two `_`s. New helpers