Reactive SQLite for Clojure and ClojureScript: register a query, get an atom; write through the engine; the atom is always the query's fresh result.
(def eng (rsql/connect)) ; in-memory by default
(rsql/exec! eng "create table person (id integer primary key, name text, age int)")
(def adults (rsql/q! eng "select id, name from person where age >= ?" 18))
@adults
;; => []
(rsql/exec! eng "insert into person values (1, 'Ann', 33)")
@adults
;; => [{"id" 1, "name" "Ann"}]
(rsql/unq! eng adults)
(rsql/close! eng)
Result atoms are plain Clojure atoms — deref to read, add-watch
(or Reagent) to react.
On the JVM the dependency is one line and needs no setup: the jar embeds SQLite's official JNI binding and precompiled native libraries for every supported platform, extracted and loaded at runtime. On ClojureScript the engine runs on @sqlite.org/sqlite-wasm; you initialize the module (it's your build toolchain's import) and hand it over:
;; npm install @sqlite.org/sqlite-wasm
(import 'initSqlite3 from '@sqlite.org/sqlite-wasm') ; your bundler's syntax
(def sqlite3 (await initSqlite3()))
(def eng (rsql/connect {:sqlite3 sqlite3}))
The architecture is posh's, transplanted from Datascript onto SQLite's preupdate hook:
| posh / Datascript | rsqlite |
|---|---|
| tx-report datoms | preupdate-hook events, materialized per row |
| q-analyze reload patterns | an instaparse SELECT grammar + EXPLAIN QUERY PLAN |
| datom-matcher | pure table/column intersection (match) |
listener re-run + not= | post-commit flush + result diff before delivery |
| ratom | plain atom per query |
Dependency extraction blends two sources with complementary
failure modes: the planner's EXPLAIN QUERY PLAN is the sound
floor for tables (sqlite cannot plan a read of a table it won't
read), and the grammar contributes column granularity and star
resolution. A statement the grammar cannot parse degrades to
table-granularity invalidation rather than re-running on every
write.
ROLLBACK TO).sync! flushes deterministically when tests need it).exec!, transact!) — the
preupdate hook observes only its own connection.exec! re-derives every query's dependencies on the
next flush.transact! a vector.sqlite_sequence are invisible to the hook.-Djava.library.path or script/native-setup.sh
there. Linux x64/aarch64, Apple Silicon and Windows x64 ship
bundled; the native builds uniformly with CMake + Ninja (MSVC on
Windows) through script/native-setup.sh.sqldeps's docstring../script/native-setup.sh # build pinned org.sqlite.jni (needed for JVM tests)
clojure -T:build test # JVM suite
npm install
npx shadow-cljs compile test && node target/cljs-test.js # CLJS suite
clojure -T:build ci # assemble target/rsqlite-<version>.jar
script/native-setup.sh pins SQLite 3.54.1 (tag version-3.54.1,
sha3-verified), builds the native with CMake + Ninja uniformly
(MSVC on Windows, gcc/clang elsewhere; it bootstraps the single
ninja binary itself when missing) and patches one thing into the
binding: CApi's static System.loadLibrary is replaced by a
NativeShim class so loading is owned by
ebifriedrice.rsqlite.natives (modern JVMs snapshot
java.library.path at startup, making a jar-bundled library
otherwise unreachable). See resources/NOTICE.
Releases run from the release branch, whose tip must carry an
exact semver tag: fast-forward it to the main commit you want out,
git tag vX.Y.Z && git push origin release --tags, and CI builds
the natives matrix, embeds everything into the jar, publishes to
Clojars and attaches the jar to a GitHub Release. Windows ships a
bundled native from that matrix (its test suite is not yet verified
under msys2; the native itself builds and is in the jar).
Eclipse Public License 2.0. The embedded SQLite artifacts are
public domain — see resources/NOTICE.
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 |