SQLite targets for vaelii's storage seams —
an Apache-2.0 adapter (com.vaelii/sqlite) on the SSPL engine. It depends on
core; core never depends on it.
Two SQLite lanes over the engine's storage seams — a snapshot sink (a KB image in a file) and a record store (a live durable backend). Pick by whether you want a frozen image you re-export or an always-current store the KB reads and writes.
vaelii.sqlite.snapshotA SnapshotSink / SnapshotSource over the engine's snapshot seam
(vaelii.impl.io.snapshot). It puts a KB image in a single file: the index
projection today, and any of the seam's named sections as they land.
A snapshot is O(sections) bulk blob transfers, not O(records) tiny probes, so
it is cheap on any store and cheapest of all on SQLite — no server to stand up,
just a file. "Put my KB in a file" — for backup, for shipping a corpus to another
host, for embedding a KB beside an app — is answered here, and the file is the
artifact you copy.
Two properties the seam gives, and a database sharpens:
manifest.edn last" rule, enforced
by the database rather than by ordering.snapshot/decision.A section written through this sink reads back frame-identical through any source — file, memory, Postgres, or SQLite.
vaelii.sqlite.record-storeA durable RecordStore (the engine's vaelii.impl.protocols/RecordStore) over a
single file — a live third records backend beside core's :memory and :disk,
where the disk backend is a bespoke mmap/idx pair and the snapshot is a frozen
image. This one is the running store, in a file any SQLite tool can open.
The "two orders of magnitude off local" that keeps a networked records store
off the query path does not apply here: embedded SQLite runs in-process, and
a warm point read measured ~6µs against the disk store's ~3µs — a constant factor,
not a round trip. Records are whole nippy-frozen frames keyed by handle, so a
fetch thaws back type-identical; a sentex's strength rides a column read back onto
the record, so mark-premise is a column update, never a frame rewrite; handles
are never reissued across a reopen. One connection under WAL + synchronous=NORMAL
(the disk store's durability shape), serialized on a lock, an LRU in front.
Core reaches it as the :sqlite backend, resolved lazily so the SSPL engine never
loads a JDBC driver unless a KB asks for it:
(require '[vaelii.core :as v])
(def kb (v/open-kb {:backend :sqlite :dir "/path/to/kb"})) ; records.sqlite lands there
(v/assert kb '(likes Felix Tuna) 'CxTest)
(v/close! kb) ; releases the connection; the file is the KB
;; a later open over the same :dir recovers the whole KB from the file alone
(def kb' (v/open-kb {:backend :sqlite :dir "/path/to/kb"}))
(require '[vaelii.sqlite.snapshot :as sqlite]
'[vaelii.impl.io.snapshot :as snap])
(def ds {:dbtype "sqlite" :dbname "kb.db"}) ; a file — see the note below
;; write a KB's index projection as an image
(with-open [sink (sqlite/sqlite-sink ds "my-kb")]
(snap/save-index! sink (:index kb) records-fingerprint))
;; read it back into an (empty) index, validated against the current records
(snap/load-index! (sqlite/sqlite-source ds "my-kb") (:index other-kb) records-fingerprint)
;; => {:index :replayed :entries n} — or {:index :rebuild :reason …}
sqlite-sink is java.io.Closeable; commit! commits, and a close without a
commit rolls the whole image back. Use with-open.
Point it at a file. The sink, the source, and ensure-schema! each open
their own connection, and only a file-backed ds shares one database across
them. An in-memory :memory: spec gives each connection its own database, so
the schema one opens is invisible to the next — use a file, or a shared-cache
datasource, if you need memory.
checkouts/vaelii -> ../vaelii shadows the core dependency with dev-core source,
so the seam is the checkout's, not a jar's. Run scripts/link-checkouts.sh after
a fresh clone (checkouts/ is gitignored).
lein test # always runs — SQLite needs no server, only a temp file
lein lint # kondo + cljfmt + shellcheck + the reflection ratchet
Every test stands up a fresh temp-file database, runs, and deletes it. There is
nothing to gate on and no VAELII_* variable to set — that is the difference
from the Postgres sibling.
Apache-2.0. See LICENSE.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |