Relations & Tabling — std.db


Overview

std.db is a thin relation layer over std.fact_table and std.logic. It gives Prolog-style assert / retract / call semantics with first-argument indexing and optional SLG-lite tabling (memoisation with cycle detection) — without leaving Eta or introducing a separate database engine.

(import std.db)        ; brings std.fact_table + std.logic with it

A relation is identified by a (name . arity) pair and stored in a dedicated FactTable whose first column is hash-indexed by default.


Defining Relations

FormEffect
(defrel '(name a1 ... aN))Insert a ground fact (or pattern row containing ?vars)
(defrel '(name a1 ... aN) rule-proc)Insert a rule row whose body is the procedure rule-proc (called with the actual call args)
(assert '(name ...))Same as defrel for facts; conventional name for runtime mutation
(retract '(name pattern...))Remove the first row matching pattern
(retract-all '(name pattern...))Remove every row matching pattern
(index-rel! 'name col-spec)Build/rebuild a column index for every arity registered under name

Variable convention. Symbols starting with ? (e.g. ?x, ?y) inside a defrel head act as pattern variables; repeated ?vars must unify consistently across the head.

(defrel '(parent 'tom 'bob))
(defrel '(parent 'bob 'liz))
(defrel '(grandparent ?g ?c)
  (lambda (g c)
    (let ((p (logic-var)))
      (and (not (null? (call-rel 'parent g p)))
           (not (null? (call-rel 'parent p c)))))))

Calling Relations

FormReturns
(call-rel 'name a1 ... aN)A goalset: a list of zero-arg branch thunks suitable for findall, run1, run*
(call-rel? 'name a1 ... aN)#t iff at least one branch succeeds (convenience predicate)
(import std.logic)

(findall (lambda () #t)
         (call-rel 'parent 'tom (logic-var)))
;; => list of branch results, one per matching row

Goalsets compose naturally with the rest of std.logic.


Tabling (SLG-lite)

FormEffect
(tabled 'name arity)Mark relation (name . arity) as tabled. Subsequent call-rel invocations use a variant-keyed answer cache with cycle-aware fixpoint iteration

Semantics:

(tabled 'reaches 2)
(defrel '(reaches ?x ?x))
(defrel '(reaches ?x ?z)
  (lambda (x z)
    (let ((y (logic-var)))
      (and (call-rel? 'edge x y) (call-rel? 'reaches y z)))))

Without tabled, the same definition would loop on cyclic graphs.


Trail & Backtracking

call-rel branch thunks are ordinary logic goals: they bind logic vars via unify, all writes are trailed, and unwind-trail after each failed branch restores the world exactly. defrel / assert mutations are not trailed — they are global side effects on the relation table, mirroring Prolog’s assertz.


Source Locations

ComponentFile
std.db modulestdlib/std/db.eta
FactTable backendstdlib/std/fact_table.eta, eta/core/src/eta/runtime/types/fact_table.h
Logic helpersstdlib/std/logic.eta
Testsstdlib/tests/db.test.eta