Liking cljdoc? Tell your friends :D

rsqlite

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}))

How it works

The architecture is posh's, transplanted from Datascript onto SQLite's preupdate hook:

posh / Datascriptrsqlite
tx-report datomspreupdate-hook events, materialized per row
q-analyze reload patternsan instaparse SELECT grammar + EXPLAIN QUERY PLAN
datom-matcherpure table/column intersection (match)
listener re-run + not=post-commit flush + result diff before delivery
ratomplain 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.

Guarantees

  • No uncommitted visibility: recomputes are scheduled only after a statement leaves autocommit; evaluation reads committed state.
  • No rollback publication: a full rollback discards pending events (savepoints checkpoint and restore them under ROLLBACK TO).
  • The writer is never blocked: hooks only append cheap event data; recomputation and watcher notification run off the writing thread (a single background thread on the JVM, a microtask on JS; sync! flushes deterministically when tests need it).
  • Results are delivered only when they change.
  • Failed re-evaluation keeps the last published result and retries on the next flush.

The contract

  • Writes go through the engine (exec!, transact!) — the preupdate hook observes only its own connection.
  • One connection per engine; engines are not connection pools.
  • DDL through exec! re-derives every query's dependencies on the next flush.
  • Queries are SELECTs; multi-statement strings are not split — give transact! a vector.

Known edges

  • Column matching is by bare name (no alias binding): a shared column name over-triggers, never misses.
  • WITHOUT ROWID tables produce events without rowids (matching is table/column-shaped, so they still invalidate correctly).
  • Virtual tables and sqlite_sequence are invisible to the hook.
  • On JS, integers beyond 2^53 lose precision (the JVM keeps full longs).
  • No bundled native for intel macOS yet - GitHub's macos-13 runner pool queues indefinitely, so that build is omitted until the pool returns; use -Djava.library.path or script/native-setup.sh there (everything else - linux x64/aarch64, Apple Silicon, windows - ships bundled, windows still marked experimental).
  • The CTE/global-subtraction edge in sqldeps's docstring.

Development

./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) 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 are tag-driven (v* semver): CI builds the natives matrix, embeds everything into the jar, publishes to Clojars and attaches the jar to a GitHub Release.

License

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

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close