datalog: dl-query accepts conjunctive goal lists (167/167)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 54s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 54s
dl-query now auto-dispatches on the first element's shape:
- positive literal (head is a symbol) or {:neg ...} dict → wrap
- list of literals → conjunctive query
dl-query-coerce normalizes; dl-query-user-vars collects the union
of user-named vars (deduped, '_' filtered) for projection. Old
single-literal callers unchanged.
(dl-query db '(p X)) ; single
(dl-query db '((p X) (q X))) ; conjunction
(dl-query db (list '(n X) '(> X 2))) ; with comparison
2 new api tests cover multi-goal AND and conjunction with comparison.
This commit is contained in:
@@ -362,32 +362,72 @@
|
||||
|
||||
;; ── Querying ─────────────────────────────────────────────────────
|
||||
|
||||
;; Coerce a query argument to a list of body literals. A single literal
|
||||
;; like `(p X)` (positive — head is a symbol) or `{:neg ...}` becomes
|
||||
;; `((p X))`. A list of literals like `((p X) (q X))` is returned as-is.
|
||||
(define
|
||||
dl-query-coerce
|
||||
(fn
|
||||
(goal)
|
||||
(cond
|
||||
((and (dict? goal) (has-key? goal :neg)) (list goal))
|
||||
((and (list? goal) (> (len goal) 0) (symbol? (first goal)))
|
||||
(list goal))
|
||||
((list? goal) goal)
|
||||
(else (error (str "dl-query: unrecognised goal shape: " goal))))))
|
||||
|
||||
(define
|
||||
dl-query
|
||||
(fn
|
||||
(db goal)
|
||||
(do
|
||||
(dl-saturate! db)
|
||||
;; Rename anonymous '_' vars in the goal so multiple occurrences
|
||||
;; do not unify together. Keep the user-facing var list (taken
|
||||
;; before renaming) so projected results retain user names.
|
||||
;; Rename anonymous '_' vars in each goal literal so multiple
|
||||
;; occurrences do not unify together. Keep the user-facing var
|
||||
;; list (taken before renaming) so projected results retain user
|
||||
;; names.
|
||||
(let
|
||||
((user-vars (filter (fn (n) (not (= n "_"))) (dl-vars-of goal)))
|
||||
(renamed (dl-rename-anon-lit goal (dl-make-anon-renamer))))
|
||||
((goals (dl-query-coerce goal))
|
||||
(renamer (dl-make-anon-renamer)))
|
||||
(let
|
||||
((substs (dl-find-bindings (list renamed) db (dl-empty-subst)))
|
||||
(results (list)))
|
||||
(do
|
||||
(for-each
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((proj (dl-project-subst s user-vars)))
|
||||
((user-vars (dl-query-user-vars goals))
|
||||
(renamed (map (fn (g) (dl-rename-anon-lit g renamer)) goals)))
|
||||
(let
|
||||
((substs (dl-find-bindings renamed db (dl-empty-subst)))
|
||||
(results (list)))
|
||||
(do
|
||||
(for-each
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((proj (dl-project-subst s user-vars)))
|
||||
(when
|
||||
(not (dl-tuple-member? proj results))
|
||||
(append! results proj))))
|
||||
substs)
|
||||
results)))))))
|
||||
|
||||
(define
|
||||
dl-query-user-vars
|
||||
(fn
|
||||
(goals)
|
||||
(let ((seen (list)))
|
||||
(do
|
||||
(for-each
|
||||
(fn
|
||||
(g)
|
||||
(let
|
||||
((tgt
|
||||
(if (and (dict? g) (has-key? g :neg)) (get g :neg) g)))
|
||||
(for-each
|
||||
(fn
|
||||
(v)
|
||||
(when
|
||||
(not (dl-tuple-member? proj results))
|
||||
(append! results proj))))
|
||||
substs)
|
||||
results))))))
|
||||
(and (not (= v "_")) (not (dl-member-string? v seen)))
|
||||
(append! seen v)))
|
||||
(dl-vars-of tgt))))
|
||||
goals)
|
||||
seen))))
|
||||
|
||||
(define
|
||||
dl-project-subst
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"lang": "datalog",
|
||||
"total_passed": 165,
|
||||
"total_passed": 167,
|
||||
"total_failed": 0,
|
||||
"total": 165,
|
||||
"total": 167,
|
||||
"suites": [
|
||||
{"name":"tokenize","passed":26,"failed":0,"total":26},
|
||||
{"name":"parse","passed":18,"failed":0,"total":18},
|
||||
@@ -12,8 +12,8 @@
|
||||
{"name":"semi_naive","passed":8,"failed":0,"total":8},
|
||||
{"name":"negation","passed":10,"failed":0,"total":10},
|
||||
{"name":"aggregates","passed":16,"failed":0,"total":16},
|
||||
{"name":"api","passed":9,"failed":0,"total":9},
|
||||
{"name":"api","passed":11,"failed":0,"total":11},
|
||||
{"name":"demo","passed":13,"failed":0,"total":13}
|
||||
],
|
||||
"generated": "2026-05-08T09:12:57+00:00"
|
||||
"generated": "2026-05-08T09:16:59+00:00"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# datalog scoreboard
|
||||
|
||||
**165 / 165 passing** (0 failure(s)).
|
||||
**167 / 167 passing** (0 failure(s)).
|
||||
|
||||
| Suite | Passed | Total | Status |
|
||||
|-------|--------|-------|--------|
|
||||
@@ -12,5 +12,5 @@
|
||||
| semi_naive | 8 | 8 | ok |
|
||||
| negation | 10 | 10 | ok |
|
||||
| aggregates | 16 | 16 | ok |
|
||||
| api | 9 | 9 | ok |
|
||||
| api | 11 | 11 | ok |
|
||||
| demo | 13 | 13 | ok |
|
||||
|
||||
@@ -180,6 +180,24 @@
|
||||
(dl-query db (quote (p X)))))
|
||||
(list {:X 1} {:X 2}))
|
||||
|
||||
;; Multi-goal query: pass list of literals.
|
||||
(dl-api-test-set! "multi-goal query"
|
||||
(dl-query
|
||||
(dl-program-data
|
||||
(quote ((p 1) (p 2) (p 3) (q 2) (q 3)))
|
||||
(list))
|
||||
(list (quote (p X)) (quote (q X))))
|
||||
(list {:X 2} {:X 3}))
|
||||
|
||||
;; Multi-goal with comparison.
|
||||
(dl-api-test-set! "multi-goal with comparison"
|
||||
(dl-query
|
||||
(dl-program-data
|
||||
(quote ((n 1) (n 2) (n 3) (n 4) (n 5)))
|
||||
(list))
|
||||
(list (quote (n X)) (list (string->symbol ">") (quote X) 2)))
|
||||
(list {:X 3} {:X 4} {:X 5}))
|
||||
|
||||
;; dl-rule-from-list with no arrow → fact-style.
|
||||
(dl-api-test-set! "no arrow → fact-like rule"
|
||||
(let
|
||||
|
||||
@@ -222,7 +222,11 @@ large graphs.
|
||||
```
|
||||
- [x] `(dl-rule head body)` constructor for the dict form.
|
||||
- [x] `(dl-query db '(ancestor tom X))` already worked — same query API
|
||||
consumes the SX-data goal.
|
||||
consumes the SX-data goal. Now also accepts a *list* of body
|
||||
literals for conjunctive queries:
|
||||
`(dl-query db '((p X) (q X)))`,
|
||||
`(dl-query db (list '(n X) '(> X 2)))`. Auto-dispatched via
|
||||
`dl-query-coerce` on first-element shape.
|
||||
- [x] `(dl-assert! db '(parent ann pat))` → adds the fact and re-saturates.
|
||||
- [x] `(dl-retract! db '(parent bob ann))` → drops matching tuples from
|
||||
the EDB list, wipes every relation that has a rule (those are IDB),
|
||||
@@ -273,6 +277,14 @@ large graphs.
|
||||
|
||||
_Newest first._
|
||||
|
||||
- 2026-05-08 — `dl-query` accepts a list of body literals for
|
||||
conjunctive queries, in addition to a single positive literal.
|
||||
`dl-query-coerce` dispatches based on the first element's shape:
|
||||
positive lit (head is a symbol) or `:neg` dict → wrap as singleton;
|
||||
list of lits → use as-is. `dl-query-user-vars` collects the union
|
||||
of vars across all goals (deduped, `_` filtered) for projection.
|
||||
2 new api tests: multi-goal AND, and conjunction with comparison.
|
||||
|
||||
- 2026-05-08 — Bug fix: `dl-check-stratifiable` now rejects recursion
|
||||
through aggregation (e.g., `q(N) :- count(N, X, q(X))`). The
|
||||
stratifier was already adding negation-like edges for aggregates,
|
||||
|
||||
Reference in New Issue
Block a user