Time Travel
Time-travel is a first-class feature, not an add-on: because the engine is built around a publish-then-serve lifecycle and keeps every published snapshot by default, the database is a versioned point-in-time history. Any past state can be queried as if it were current.
That buys real capabilities on a batch-write / read-heavy workload:
- Audit & as-of analysis — answer “what did this derived relation contain two publishes ago?” without a replay or a full rebuild.
- Immutable history — every version is a frozen, reproducible view; later writes can never retroactively change it.
- Diff / evolution — compare the same goal across versions to see exactly how the database evolved between publishes.
- Zero-cost snapshotting — the snapshot is the normal read path (mmap views); keeping history is just not deleting the old version directories.
Versioned snapshots
Each successful publish produces a monotonically increasing version number
starting at 1. Enumerate the available versions ascending with
dl_snapshot_versions, using the two-call idiom:
long total = dl_snapshot_versions(db, NULL, 0); /* size */
uint32_t *vers = malloc((size_t)total * sizeof(*vers));
dl_snapshot_versions(db, vers, (size_t)total); /* fill */
free(vers);
It returns the total number of versions even when the output buffer
is smaller (filling at most cap entries), returns 0 when no
snapshot has been published, and -1 on a NULL db.
As-of queries
Query a relation as of a specific published version with
dl_query_version, or bind leading columns with
dl_query_bound_version:
long n = dl_query_version(db, version, "edge", cb, user);
long m = dl_query_bound_version(db, version, "edge", leading, k, cb, user);
Semantics and guarantees:
- As-of reads use an explicit version and read that version’s manifest
+ mmap view from disk;
db->snap_versionis never mutated, so live/current routing is untouched. - As-of views are immutable: add/delete operations after a
publish never change an earlier version’s view. The live
dl_querystays on the current version until the next publish. - A nonexistent version (including 0), a NULL goal/relation absent from that version, or a NULL callback is a loud -1 — never a silently-empty result.
- Variadic relations are supported (arity-mixed tuples are returned).
Retention
By default every version is kept forever. To bound disk usage, opt in to
prune-to-N with dl_set_snapshot_retain: after each successful
publish, the oldest versions beyond the most-recent n are
pruned. n == 0 (the default) restores keep-all.
dl_set_snapshot_retain(db, 5); /* keep the 5 most-recent versions */
dl_set_snapshot_retain(db, 0); /* back to keep-all */
A pruned version is gone — querying it returns -1 (loud), matching the nonexistent-version contract.
Concurrency model
This fits the engine’s single-writer / multiple-reader model. Readers
hold mmap views and keep reading valid data even after a retention prune
unlinks the underlying snapshot directory. A reader holding an mmap of
snapshots/<V>/<rel>.dafsa keeps reading valid data
after the pruner removes the directory — the unlink does not disturb the
open mapping.