0016. Tests use SQLite in-memory mode, not a hand-written in-memory adapter

0016. Tests use SQLite in-memory mode, not a hand-written in-memory adapter

Status

Accepted

Date

2026-10-04

Deciders

roadmap author, at the lead’s request; the lead or the operator may overrule

Context

Tests and prototypes need a data layer with no database server. Ash, the Elixir framework Mesh is modelled on, ships in-memory data layers for this and treats them as part of its testing story (research synthesis, section 4, “Testing”). The synthesis listed “in-memory” among the first data adapters (research synthesis, section 10, “Data layer” row). Plan revision 1 (not published) had a hand-written one; revision 2 dropped it in favour of SQLite’s :memory: mode, as open question N2 (plan revision 2 (superseded), section 9.2). The lead then asked to reconsider N2 now that an in-memory expression evaluator exists (ADR-0010) and to decide with reasons (rulings of 2026-10-04, section “Lead decisions of the architecture-docs brief”).

Decision

Tests and prototypes use SQLite’s :memory: mode through data-sqlite. No hand-written in-memory data adapter in v1 (roadmap, M3 and section 7).

Reasoning. N2’s original argument was that a second adapter means “a second implementation of expression semantics” to keep identical to SQL by hand (plan revision 2 (superseded), section 9.2, N2). That is weaker now: an in-memory adapter could reuse the evaluator. But the evaluator only evaluates expressions on records already loaded. An adapter must also provide the mandatory set of Ruling 4 (select, insert, update, delete, transactions, filters, sort, pagination), and SQLite already does all of it. Joins and aggregates are optional capabilities (ADR-0013), so a minimal in-memory adapter is smaller than a full one, but from M7 on the tests of relationships and aggregates need a real SQL engine anyway. Ash’s in-memory layer shows the drift risk: ETS reports :transact as false, so it has no transactions (Ash runtime internals, section 3.4), and a filter policy on creates works on one data layer and raises on ETS (Ash runtime internals, section 12.A, item 7).

Options considered

Option A: SQLite :memory: through data-sqlite (chosen)

Dimension Assessment
Complexity Low: same code path as the file-backed adapter
Cost Nearly none; Drizzle documents bun:sqlite (TypeScript foundation candidates, section 3)
Test fidelity Real SQL, real transactions, but SQLite’s
Contract proof Only SQL-shaped implementations until Postgres

Pros: tests exercise the same SQL as production for SQLite; nothing extra to maintain.
Cons: SQLite is not Postgres. Tests on :memory: say nothing about Postgres behaviour, which is the problem ADR-0012 describes. The contract has one implementation until M9, and every implementation is SQL-shaped, so nothing proves the contract is not SQL-specific (roadmap, M3 risks).

Option B: Hand-written in-memory adapter on the evaluator

Dimension Assessment
Complexity Medium: the mandatory set only (transactions, sort, pagination, filters)
Cost A permanent second implementation to keep in step with the suite
Test fidelity Drifts from SQL, as Ash’s ETS does
Contract proof The only non-SQL check on the contract

Pros: no driver; the strongest test that the contract is not tied to SQL.
Cons: transactions and pagination reimplemented; drift against SQL; M7 and later tests still need SQLite.

Option C: In-memory adapter as contract proof only

Build option B but run it only in the conformance suite, never for application tests.

Dimension Assessment
Complexity Medium
Cost Medium: written once, no application dependence
Test fidelity Application tests stay on SQLite
Contract proof As B

Pros: buys B’s proof without making tests depend on it.
Cons: effort spent on something no user runs; no ruling needs the proof in v1.

Trade-off analysis

B’s one real advantage, proof that the contract is not SQL-shaped, is a long-term benefit; its costs are immediate. C isolates that benefit and could be added later without changing A. Postgres in M9 supplies a second dialect, though not a non-SQL one.

Consequences

Easier: M2 and M3 tests; the walking skeleton. Harder: nothing in v1 runs without a SQLite driver, and Postgres behaviour is tested only from M9. Revisit if a non-SQL store is ever wanted: an in-memory adapter then becomes the cheapest proof.

Action items

  • M3: data-sqlite in-memory mode passes the conformance suite (roadmap, M3 test 1).
  • After v1: revisit if a non-SQL adapter is proposed.