Skip to content

Port as-of, since, history, and with-dry-run from slateval - #9

Merged
maxweber merged 1 commit into
mainfrom
temporal
Jul 15, 2026
Merged

Port as-of, since, history, and with-dry-run from slateval#9
maxweber merged 1 commit into
mainfrom
temporal

Conversation

@maxweber

Copy link
Copy Markdown
Owner

Summary

Ports slateval's temporal features into dbval — the last functional delta between the two engines. After this merges, slateval's only remaining uniqueness is its storage adapter, which #8 already ports.

Stacked on #8 (slatedb-store); retarget after it merges. Stack: #6#7#8 ← this.

What's ported

  • as-of — the value of the database at transaction t (squuid or instant). Implemented as a basis bound: same value, max-tx clamped to t.
  • since — only datoms asserted after t (exclusive).
  • history — all datom versions including retractions; :added distinguishes assert/retract.
  • with-dry-run — speculative transactions. A perfect fit for the pending-overlay engine from Introduce dbval.store: pluggable tuple storage behind a protocol #7: the transaction runs normally, nothing commits, and the overlay stays on :db-after so reads see the speculative datoms. Chained dry-runs accumulate; a real transact against a speculative value throws.
  • 5-element datom patterns ([?e :age ?a _ ?added]) binding the assert/retract flag, Datomic-style — history views need this to be useful.
  • Accessors as-of-t/since-t, temporal-view?; temporal views are read-only (guarded in conn/with and transact-tx-data).

Adaptation to dbval's architecture

slateval marks views by associng namespaced keys onto its record DB; dbval's DB is an opaque deftype, so the markers are explicit fields (as-of-tx, since-tx, history?). The five inline per-index filter chains consolidate into tx-visibility-xform — plus a reverse variant slateval doesn't have, so rseek-datoms works correctly on temporal views too.

Bugfix found while porting (latent in slateval as well)

A since lower bound can orphan a retract by filtering out its add. datoms-filter's pairing logic assumed every retract directly follows its add, so an orphaned tombstone was emitted as a datom (since views showed retracted pre-window values) and could even swallow an unrelated preceding add. datoms-filter now never emits non-adds and never drops an add against an unrelated retract — with regression tests for both scenarios. slateval needs the same one-line-shaped fix.

Tests

  • dbval.test.time-travel ported from slateval (as-of by squuid and by instant, since, history + 5-element patterns, dry-run visibility/chaining/guards) plus the two new since-with-retractions regressions. One portability fix: the instant-based as-of test needed a 5ms gap between transactions — SQLite commits fast enough for both txs to share a millisecond, which SlateDB's ~100ms commits masked.
  • Full suite: 167 tests, 1068 assertions, 0 failures.
  • Temporal features smoke-verified against the memory store as well (storage-agnostic).

🤖 Generated with Claude Code

https://claude.ai/code/session_014dX8tTR4yFh5SyBGw3atpo

Temporal views become explicit fields on the DB handle (as-of-tx,
since-tx, history?), and the per-index filter chains consolidate into
`tx-visibility-xform` (plus a reverse variant for rseek, which slateval
did not have): upper bound (basis / as-of), optional lower bound
(since), and datoms-filter unless history. Temporal views are
read-only — guarded in both conn/with and transact-tx-data.

`with-dry-run` is a natural fit for the pending-overlay engine: the
transaction runs normally but nothing is committed and the overlay stays
on :db-after, so reads see the speculative datoms; chained dry-runs
accumulate, and a real transact against a speculative value throws.

Also ports Datomic's 5-element datom patterns binding the assert/retract
flag ([?e :age ?a _ ?added]), which history views need to be useful.

Bugfix found while porting (latent in slateval too): a `since` lower
bound can orphan a retract by filtering out its add; `datoms-filter`'s
pairing logic then emitted the tombstone as a datom and could even
swallow an unrelated preceding add. It now never emits non-adds and
never drops an add against an unrelated retract. With regression tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Base automatically changed from slatedb-store to main July 15, 2026 19:59
@maxweber
maxweber merged commit a8d7c59 into main Jul 15, 2026
1 check passed
@maxweber
maxweber deleted the temporal branch July 15, 2026 20:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant