Introduce dbval.store: pluggable tuple storage behind a protocol - #7
Merged
Conversation
The engine only ever needed an ordered set of byte-array keys with range scans and atomic batch commits — the contract of a transactional ordered key-value store. `dbval.store/ITupleStore` makes that explicit: -scan committed keys in [begin, end), unsigned byte order -commit! atomically add a batch of keys -close! release resources Read-your-writes inside a running transaction moves into the engine: the transaction stages its keys in a pending TreeSet overlay carried by the db value, and `slice` lazily merges the overlay over the store scan. Stores therefore only see committed state, and nothing touches the store until the single atomic commit — a failing transaction just discards the overlay (the JDBC rollback machinery is gone). Two adapters: - `dbval.store.sqlite` (default): the previous storage code. Reads now run with autocommit, so a scan always sees the latest committed state — this also fixes the WAL snapshot pinning that made a second connection to the same file blind to other connections' commits (regression test re-added). Read consistency for snapshots comes from :max-tx filtering, not storage-level read transactions. - `dbval.store.memory`: a ConcurrentSkipListSet, for tests and for running the engine without any storage backend. `empty-db` accepts a :store option; without it the SQLite store is built from :db-file as before. `dbval.test.store` runs representative engine flows (transact, upsert, retract history, query, pull, rseek, index-range, snapshot isolation, failed-transaction atomicity) on the memory store. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Like next.jdbc, dbval no longer declares a JDBC driver in :deps — a consumer using the SlateDB or memory backend should not download the SQLite driver's bundled native libraries. The SQLite adapter stays in the artifact (it only references java.sql and loads org.sqlite.JDBC reflectively), `empty-db` resolves it lazily via requiring-resolve, and a missing driver now produces an instructive error instead of a bare ClassNotFoundException. The :dev and :bench aliases provide the driver for the test suite and benchmarks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jul 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Extracts the storage layer behind
dbval.store/ITupleStore, so the engine (transact, indexes, query, pull) is storage-agnostic. This is step 1+2 of the plan to unify dbval and slateval: the contract is exactly what both backends provide — a transactional ordered key-value store.The protocol (deliberately minimal)
The key design decision: stores never see uncommitted state. dbval's transact engine reads mid-transaction (upsert resolution, retract lookups), which SQLite used to provide via read-your-writes on the shared connection — SlateDB's WriteBatch can't. So that concern moves into the engine: a transaction stages its keys in a pending
TreeSetoverlay carried by the db value, andslicelazily merges the overlay over the store scan. Consequences:-commit!— a failing transaction just discards the overlay, and the JDBC rollback machinery is deletedAdapters
dbval.store.sqlite(default, used whenempty-dbgets:db-file/no:store): the previous storage code moved. Reads now run with autocommit, so scans always see the latest committed state — this fixes the WAL snapshot pinning where a second connection to the same file never saw other connections' commits. Snapshot consistency comes from:max-txfiltering (the store is append-only), not storage-level read transactions. The previously-removed two-connections test is re-added and passes.dbval.store.memory: aConcurrentSkipListSet— engine tests without any storage backend, and the natural third implementation proving the seam.No bundled storage driver (
da2437f)org.xerial/sqlite-jdbcmoved out of:depsinto the:dev/:benchaliases — consumers on the SlateDB or memory backend no longer download the SQLite driver's ~13 MB of bundled natives. The adapter itself stays in the artifact (it only referencesjava.sqland loads the driver reflectively);empty-dbresolves it lazily viarequiring-resolve, and a missing driver produces an instructive error pointing atorg.xerial/sqlite-jdbc/:store, verified on a driver-free classpath. Same model as next.jdbc: you bring the driver for the store you pick. dbval's only remaining hard deps:fdb-java(the tuple codec) andcolossal-squuid.Tests
dbval.test.store: transact/upsert/retract-history/query/pull/rseek/index-range/snapshot-isolation and failed-transaction atomicity on the memory store.test-deref-sees-other-connections: the cross-connection freshness win, previously impossible.Follow-ups (not in this PR)
dbval.store.slatedbadapter + porting slateval'sas-of/since/historyinto the shared engine, then archiving slateval.BEGIN IMMEDIATE-style cross-process write locking as an adapter concern.🤖 Generated with Claude Code
https://claude.ai/code/session_014dX8tTR4yFh5SyBGw3atpo