From 48835f2d4f27450950559b5a973108c631a20a64 Mon Sep 17 00:00:00 2001 From: giles Date: Fri, 8 May 2026 11:22:12 +0000 Subject: [PATCH] =?UTF-8?q?mk:=20relational=20database=20queries=20?= =?UTF-8?q?=E2=80=94=20Datalog-style=20demo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tests/rdb.sx shows the library as a small Datalog engine over fact tables. Each table is an SX list of tuples, wrapped by a relation that does (membero (list ...) table). Queries compose selection, projection, and joins entirely in run* / fresh / conde / membero / intarith / nafc. Five queries: dept filter, salary > threshold, employee-project join, intersection (engineers on a specific project), anyone on multiple distinct projects. 5 new tests, 336/336 cumulative. --- lib/minikanren/tests/rdb.sx | 90 +++++++++++++++++++++++++++++++++++++ plans/minikanren-on-sx.md | 6 +++ 2 files changed, 96 insertions(+) create mode 100644 lib/minikanren/tests/rdb.sx diff --git a/lib/minikanren/tests/rdb.sx b/lib/minikanren/tests/rdb.sx new file mode 100644 index 00000000..96514058 --- /dev/null +++ b/lib/minikanren/tests/rdb.sx @@ -0,0 +1,90 @@ +;; lib/minikanren/tests/rdb.sx — relational database queries. +;; +;; Demonstrates how miniKanren can serve as a Datalog-style query engine +;; over fact tables. Tables are SX lists of tuples; the relation just +;; wraps `membero` over the table. + +(define + rdb-employees + (list + (list "alice" "engineering" 100000) + (list "bob" "marketing" 80000) + (list "carol" "engineering" 90000) + (list "dave" "engineering" 85000) + (list "eve" "sales" 75000))) + +(define + rdb-projects + (list + (list "alice" "compiler") + (list "carol" "compiler") + (list "dave" "runtime") + (list "alice" "runtime") + (list "eve" "outreach"))) + +;; Relation views over the tables. + +(define + employees + (fn (name dept salary) (membero (list name dept salary) rdb-employees))) + +(define + on-project + (fn (name project) (membero (list name project) rdb-projects))) + +;; --- queries --- + +(mk-test + "rdb-engineering-staff" + (let + ((res (run* q (fresh (n s) (employees n "engineering" s) (== q n))))) + (and + (= (len res) 3) + (and + (some (fn (n) (= n "alice")) res) + (and + (some (fn (n) (= n "carol")) res) + (some (fn (n) (= n "dave")) res))))) + true) + +(mk-test + "rdb-high-salary" + (let + ((res (run* q (fresh (n d s) (employees n d s) (lto-i 85000 s) (== q (list n s)))))) + (and + (= (len res) 2) + (and + (some (fn (r) (= r (list "alice" 100000))) res) + (some (fn (r) (= r (list "carol" 90000))) res)))) + true) + +(mk-test + "rdb-join-employee-project" + (let + ((res (run* q (fresh (n d s) (employees n d s) (on-project n "compiler") (== q n))))) + (and + (= (len res) 2) + (and + (some (fn (n) (= n "alice")) res) + (some (fn (n) (= n "carol")) res)))) + true) + +(mk-test + "rdb-engineers-on-runtime" + (let + ((res (run* q (fresh (n s) (employees n "engineering" s) (on-project n "runtime") (== q n))))) + (and + (= (len res) 2) + (and + (some (fn (n) (= n "alice")) res) + (some (fn (n) (= n "dave")) res)))) + true) + +(mk-test + "rdb-people-on-multiple-projects" + (let + ((res (run* q (fresh (n p1 p2) (on-project n p1) (on-project n p2) (nafc (== p1 p2)) (== q n))))) + (some (fn (n) (= n "alice")) res)) + true) + +(mk-tests-run!) diff --git a/plans/minikanren-on-sx.md b/plans/minikanren-on-sx.md index ca39f8c5..fd39efa1 100644 --- a/plans/minikanren-on-sx.md +++ b/plans/minikanren-on-sx.md @@ -173,6 +173,12 @@ _(none yet)_ _Newest first._ +- **2026-05-08** — **Datalog-style relational database queries**: tests/rdb.sx + shows miniKanren as a query engine over fact tables. Defines two tables + (employees + project assignments), wraps each with a relation that does + membero over the table, then expresses queries: dept filter, salary > + threshold (intarith), join engineers ↔ runtime project, find anyone on + multiple distinct projects (nafc + ==). 5 new tests, 336/336 cumulative. - **2026-05-08** — **Cyclic graph behaviour test (motivates Phase 7 tabling)**: Demonstrates that naive patho on a 2-cycle generates ever-longer paths. `run 5` truncates to a finite prefix; `run*` would diverge. This is