Files
alkstore/docs/architecture/decisions/003-sqlite-driver.md
T
glm-5.3-flash 8e68b44194 ADR-013: fold the forked substrate into alkstore-sqlite — no fourth crate
Operator review of ADR-012's fork design re-litigated §1's crate
identity. alkstore-substrate misdescribed what the code is (unpublished,
path-dep-only, one consumer, SQLite-only — not a family-wide substrate);
the mechanical-diff hope was gone at fork time regardless (port deltas,
renames, re-derived half); and the alksocks F-1 lesson applies — a
vendored region under a second, weaker instruction set is a defect seam.
The fork folds into alkstore-sqlite as a bounded module subtree
(src/substrate/); ADR-012 §3–§6 retained verbatim, §2 retained with its
enforcement re-sited from the crate graph to diff fence + review +
contract-suite equivalence pins. OQ-11 item (1) dissolved.
2026-10-05 11:56:01 +00:00

120 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADR-003: SQLite engine — rusqlite + published honker-core, bridged at the trait seam
## Status
Accepted
## Context
The SQLite engine's options were never a single "rusqlite vs sqlx"
axis (OQ-ST-03). Three distinct postures existed:
1. **honker-core on our own rusqlite connection** — we own
connection/schema/watcher wiring; honker supplies the SQL-function
machinery (`attach_notify`, `attach_honker_functions`,
`bootstrap_honker_schema`).
2. **honker-rs as the SQLite substrate** (`Database::open` — honker
holds its own connections; sync-only; transactions pin the
connection mutex).
3. **raw SQL over sqlx-sqlite with the honker loadable extension**
(`.so` built from published source, loaded per pool connection).
POC #1 (`docs/research/poc-sqlite-posture-findings.md`, ran 2026-10-04;
POC #2 is its pg twin) measured postures 1
and 3 end-to-end and settled the choice. Constraint facts that outlive
the comparison:
- rusqlite 0.40.x (what honker-core 0.5.0 pins) needs rustc ≥ 1.99
(`cfg_select!`) — a deployment note for any binary linking it.
- sqlx's `libsqlite3-sys` range collides with rusqlite 0.40's — mixed
rusqlite+sqlx binaries need a vendored one-line patch today. The
per-engine-crate split ([ADR-001]) keeps engine binaries
single-driver, dissolving this.
- The async-facing-trait + sync-bridge posture is family-standard
(alktty REQ-TTY-01; alkblobs store-api.md): bridge-at-the-seam is a
supported posture, not a workaround — which removes native-async's
main differentiator before any measurement.
## Decision
The SQLite engine uses **posture 1**: published `honker-core = 0.5`
as a library dependency over the crate's own rusqlite connections
(`bundled-sqlite` for hermetic builds). *(Ownership superseded
2026-10-05 by [ADR-011](011-sqlite-substrate-fork.md): the substrate
is the forked honker-core lineage, carried in-tree as the engine
crate's substrate module subtree per
[ADR-013](013-fold-substrate-into-sqlite.md), not the published
dependency — the driver, seam, and watcher architecture below are
unchanged.)*
- **Driver**: rusqlite, riding honker-core's pinned version. *(The
pin is ours to move deliberately since the fork — [ADR-011], per
[ADR-005]'s annotated rusqlite row.)*
- **Async seam**: bridged. The writer slot (`Writer`) +
reader pool (`Readers`) + every engine call in `spawn_blocking`.
A dedicated std-thread bridge (one thread owning the writer conn,
ops over mpsc) is the recorded optimization path if seam throughput
ever demands it — not the default.
- **Wake**: honker-core's `SharedUpdateWatcher` inherited (p50 ≈ 1.4 ms
at the default 1 ms `PRAGMA data_version` cadence, with
battle-tested failure handling — subscriber senders close on watcher
death, never silent-hang). A self-built thin watcher was benchmarked
only to confirm honker-core's is tighter (p50 1.40 vs 2.15 ms; max
29 vs 172 ms); no hybrid is warranted.
- **Transactions**: the engine opens `BEGIN IMMEDIATE` on the writer
slot and hands back a caller-held tx handle ([ADR-007]). The slot
lease models WAL's single-writer honestly: a long transaction parks
the writer — that is SQLite's shape, not an emulation artifact.
- **No `.so` runtime artifact, no vendored patches** in the engine
crate.
Rejected alternatives, briefly, for the record: posture 3 is *viable*
but pays ~2× p50 seam cost, a runtime dlopen artifact, wider
dependency tree, and sqlx 0.9 ergonomic warts (unsafe `extension()`,
`SqlSafeStr`) for no compensating advantage. Posture 2 (honker-rs)
was never measured because posture 1 dominates it on control with
comparable reuse.
## Consequences
**Positive**
- ~0.35 ms p50 transactional seam (measured), ~1.4 ms wake latency,
and honker-core's watcher failure-handling inherited for free.
- Static linkage: "open a path, get a store" needs no runtime
artifacts.
- The engine's job shrinks to wiring + the SQL surfaces honker doesn't
cover (queue semantics depth, OQ-ST-05's SQLite side rides honker's
machinery directly) + the async bridge. *(Ownership since resolved:
the substrate is forked — [ADR-011](011-sqlite-substrate-fork.md) —
and the queue ops re-derived on contract v1 within it.)*
**Negative**
- honker-core pins rusqlite ^0.40.1; version movement in honker-core
moves our rusqlite. A honker-core quality read is the recorded fork
trigger ([ADR-005], OQ-06) — *(resolved 2026-10-05: the trigger
fired, [ADR-011](011-sqlite-substrate-fork.md) forks the substrate;
its rusqlite pin is then ours to move deliberately.)*
- rustc ≥ 1.99 required by rusqlite 0.40.x — binaries linking this
engine carry that toolchain floor (deployment-matrix row,
[deployment.md](../deployment.md)).
- The sync bridge means engine ops pay a `spawn_blocking` hop — mostly
invisible next to SQLite write costs, but it shapes the tx-handle
design ([ADR-007]).
## References
- `docs/research/poc-sqlite-posture-findings.md` — the measurements.
- OQ-ST-03 (`docs/research/phase-0.md`) — resolution record.
- [ADR-001](001-crate-split.md) — why the engine crate is
single-driver.
- [ADR-004](004-postgres-driver.md) — the Postgres counterpart.
- [ADR-007](007-transactional-seam.md) — the tx-handle shape both
engines implement.
- [engine-sqlite.md](../engine-sqlite.md) — the engine spec this
decision defines.
[ADR-001]: 001-crate-split.md
[ADR-005]: 005-dependency-ownership.md
[ADR-007]: 007-transactional-seam.md