Rindle docs and package mapSkip to main content

Module sqlite

Module sqlite 

Source
Expand description

Phase 2 — the SQLite leaf: a zero-copy lending [RowStream] over a real rusqlite cursor. This is the hardest backend for the canonical value model (value.rs) to satisfy, and the reason the model is designed here rather than in the abstract: a SQLite statement has no row — only a prepared statement, a step, and column_*(i) accessors whose text/blob pointers are valid only until the next step/reset. The borrow checker turns a use-after-step into a compile error, and forces an owned copy at exactly the points the JS already copies.

What this proves (the spike’s exit criteria for the SQLite side):

  1. Zero copy on the transient path, including strings. RowRef::col(i) is a rusqlite get_ref — a ValueRef borrowing the step buffer — mapped to a Value<'_> with no allocation (Str/Json are &[u8] straight into SQLite’s buffer). A filtered/pass-through scan that reads, compares, and drops rows allocates nothing per row (proven by the counting allocator in tests/sqlite_zero_copy.rs).
  2. to_owned_row() is the one forced per-row copy, at the Node boundary — the single point where a value must outlive its step. Same trait method the memory backend implements as an Arc bump (btree.rs); here it copies.
  3. Step fallibility resolved (handoff decision 1 / 05 OQ-9): next_row stays infallible (uniform with the never-erroring memory backend); a sqlite3_step error is parked on the stream and re-raised at the first owning boundary via SqliteRowStream::take_error — NEVER mapped to silent end-of-stream.
  4. RAII statement cleanup (Primitive #2): dropping the stream drops the Rows cursor (rusqlite resets the statement), so the next write does not hit “database is busy”. StmtGuard makes that release observable — and is where a real prepared-statement pool returns its PooledStmt.

Structs§

SqliteRow
One borrowed SQLite row. col(i) reads column i lazily and zero-copy: a Str/Json cell is a &[u8] pointing straight into SQLite’s step buffer (no allocation, no UTF-8 validation — bytewise BINARY compare on the hot path; validate at the to_owned escape).
SqliteRowStream
The leaf row stream: a live rusqlite cursor (Rows) plus the per-column type tags. Implements the foundations lending [RowStream]; each next_row reborrows self, so the returned SqliteRow is invalidated by the next call — the SQLite cursor contract, now a compile-time invariant.
StmtGuard
RAII cursor accounting (Primitive #2). Mirrors the JS finally/.return() that resets+returns the prepared statement so the next write doesn’t hit “database is busy”. rusqlite::Rows already resets the statement on Drop; this guard makes the release observable (and is where a real pool would return its PooledStmt). Decrements its counter on Drop — on normal end, early break, ?, or early return — for free, no finally needed. This is a sqlite-only (native server) path: run the server with the release-server profile (panic = "unwind"), where Drop also runs on panic; under a plain release build (panic = "abort") a panic aborts the process and no destructor runs, so panic balance holds only in unwinding builds (release-server, cargo test). See WS02.

Enums§

ColType
Per-column type tag, resolved once from the schema at build time (foundations §4). Makes value conversion ty-directed: an INTEGER storage class becomes Int or Bool depending on the column, TEXT becomes Str or (unparsed) Json. The hot path never inspects a column name.
SqliteError
A parked error, surfaced at the first owning/dyn boundary. Carries either a sqlite3_step failure or a value-conversion failure; the production RindleError (foundations §10) wraps the same set.

Functions§

is_exact_f64_integer
True when widening i to f64 and narrowing it back preserves the exact integer. This deliberately accepts sparse, exactly representable integers above 2^53 (such as 2^54) while rejecting adjacent values that would lose precision.
select_sql
Build SELECT <c0>, <c1>, … FROM <table> projecting the declared columns in ColId order. Result-row order == columns order == ColId, which is what makes RowRef::col(i) an O(1) array index (05 §8.1). (The full constraint/start/filter WHERE lowering is 05 §4.4; out of scope here — the spike threads its own predicates.)