Repository navigation
fix(db): tolerate a SQLite build without the dbstat virtual table - #8
Merged
Merged
Conversation
added 3 commits
August 3, 2026 17:43
getDatabaseStats() queried `dbstat` once per table with no guard. `dbstat` is compile-time optional (ENABLE_DBSTAT_VTAB) and is absent from sql.js/WASM builds, so on those runtimes the query throws and the error propagates out of getDatabaseStats(). Every caller dies with it. Most visibly, GET and PATCH /api/settings/database return HTTP 500, which makes the entire database settings page unusable — users cannot read or change page size, cache size, or vacuum settings. The function already anticipated missing virtual-table modules: the COUNT(*) lookup a few lines above swallows "no such module:" errors. The dbstat query simply sat outside that guard. Probe dbstat once per call and skip the per-table size lookups when it is unavailable, reporting size 0. Database-level figures (total size, page count, cache size) come from pragmas and stay accurate; only per-table byte sizes are lost, which is the correct trade against a hard 500. Unrelated failures (I/O errors, corruption) still propagate. Both spellings are handled: sql.js reports "no such module: dbstat" while better-sqlite3 can surface "no such table: dbstat".
Review follow-up on the previous commit.
The guard is deliberately unanchored because real drivers stringify errors
with their class name attached ("SqliteError: no such table: dbstat",
"RuntimeError: ..."). Nothing pinned that, so anchoring the regex would have
passed the suite while silently breaking every real driver. Add a case for the
prefixed form; it fails if a caret is introduced.
Also cover three shapes the fake previously could not express:
- a database with no user tables, which is what a fresh install hits first
- SUM(pgsize) returning NULL for a table occupying no pages
- dbstat answering the probe but failing on a later table, which documents
that a mid-iteration fault still propagates rather than being mistaken for
an absent module
Correct the source comment: the two error spellings track the SQLite build,
not the driver package, so the earlier attribution to better-sqlite3 was
wrong.
c317b6c moved this workflow to omniroute-ops, which builds a container image and is not yet the deployment path. The router still runs an unpacked standalone bundle under releases/, so without this workflow there is no way to produce a shippable artifact without building on the live host — which caused a ~30 minute fleet outage on 2026-07-26. Restored verbatim from 2fc1229, the commit that produced the currently running release, with the bundle assertions extended to cover what this deploy actually ships: the dbstat guard and the Claude entrypoint override. Both use grep -F so the regex literal is matched as text. This can be dropped again once the container path is proven.
TechNickAI
force-pushed
the
nick/db-pragma-settings
branch
from
August 3, 2026 22:55
9e8ecbd to
3761736
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
getDatabaseStats()queried thedbstatvirtual table once per table with no guard:dbstatis compile-time optional in SQLite (ENABLE_DBSTAT_VTAB). It is not a schema table, is never created by a migration, and does not appear insqlite_master. Builds without it — sql.js/WASM among them — reject the query outright.openSqliteDatabase()(src/lib/db/core.ts:178) falls back to a sql.js WASM adapter when no sync native driver is available, so this is reachable in normal operation.Impact
The throw propagates out of
getDatabaseStats()and takes every caller with it. Most visibly,GETandPATCH /api/settings/databasereturn HTTP 500:The entire database settings page becomes unusable — page size, cache size, and vacuum settings can be neither read nor changed. Observed in production on a live router.
The fix
The function already anticipated missing virtual-table modules: the
COUNT(*)lookup a few lines above swallows"no such module:"errors. Thedbstatquery simply sat outside that guard.Probe
dbstatonce per call and skip the per-table size lookups when unavailable, reportingsize: 0. Database-level figures (total size, page count, cache size) come from pragmas and stay accurate — only per-table byte sizes are lost, which is the right trade against a hard 500.Unrelated failures (I/O errors, corruption) still propagate.
Both spellings are handled, and the match is deliberately unanchored because drivers prefix their error class onto the message (
SqliteError: no such table: dbstat,RuntimeError: ...). A test pins this so the guard cannot silently regress if anchored later.Tests
tests/unit/db/stats-dbstat-optional.test.ts— 8 cases against aSqliteAdapterdouble:no such module: dbstat→ degrades to 0no such table: dbstat→ degrades to 0SqliteError:/RuntimeError:) → degrades to 0SUM(pgsize)returningNULL→ 0database disk image is malformed→ propagatesFalsified, not assumed: reverting the guard makes exactly 2 of these fail with the pre-fix 500 path. Introducing a
^anchor makes exactly the prefixed-error case fail.Verification
Notes for review
size: 0for every table on a dbstat-less build instead of an exception. No caller was found that depends onsizebeing truthy.getDatabaseStats()call rather than cached on the adapter. Caching capability at connection setup would be tidier; that felt like a larger change than this fix warrants, and I'd happily follow up if preferred.2fc1229fe(v3.8.50), the commit currently deployed in our production environment.