rqlite 8.36.4 preserves SQLite extensions through database replacement
A snapshot restore could reopen SQLite without its configured extensions. rqlite 8.36.4 preserves the driver across that replacement.
A database can recover its tables and still lose the code needed to query them. That is the failure behind rqlite 8.36.4, released on 9 January. The affected node could log that it had loaded an extension, restore its database, and subsequently reject SQL that depended on that extension. The repair concerns how rqlite opens the replacement database during recovery.
I value rqlite for the proportion of machinery it brings to replicated relational state. SQLite behind Raft is an appealing arrangement when the problem needs distributed agreement without an elaborate database deployment. This defect is interesting because the missing state sits outside the tables themselves. Recovery has to preserve the execution environment that makes those tables usable.
A restart exposed the missing module
Issue #2011, opened on 8 January, reports the problem on a single macOS node running 8.36.3. The reporter created a database using the vector extension, shut the server down, and restarted it. Startup recorded that it had loaded the extension library. Subsequent attempts to create a virtual table using the vector module failed with a missing-module error.
The sequence in the logs matters. Extension loading happened before the node restored its database. The successful log entry therefore described an earlier database opening. It did not establish that the database ultimately used to serve requests retained the same extension configuration. Reading only the successful load message would have directed investigation toward the SQL or the library installation.
The reporter narrowed the reproducer further. An empty database without a snapshot did not show the problem. Once the node had created a snapshot, stopping and restarting reproduced it. This tied the missing module to the recovery path. There was no need to posit a multi-node replication disagreement to explain the observation, since a single node was sufficient.
That distinction affects who should care about the patch. A single-node deployment can exercise snapshot recovery even though it has no other node from which to receive state. The reported failure was part of ordinary process restart. Describing the release only as a fix for manually restoring a backup would understate the operational circumstances that exposed it.
The replacement database used a different opening path
The released database wrapper makes the relevant boundary explicit. SwappableDB holds a database and protects access with a read-write mutex. Its swap operation validates the incoming SQLite file, obtains the write lock, closes the current database, removes the existing files, and renames the incoming file into place. It then opens the database that requests will use.
The extension repair changes the information retained across that process. The wrapper now stores its Driver alongside its database. Opening the wrapper resolves a missing driver to the default driver and retains the resulting choice. Replacing the file can then use the same driver that opened the original database.
PR #2014 shows the decisive call changing from Open to OpenWithDriver. The latter receives s.drv, the retained driver. This is a small code change with a concrete effect. Reopening no longer silently discards the driver selection made when the wrapper was constructed.
The preceding PR #2012 adds that driver field and adjusts construction. The store already creates an extended driver when extensions are configured. Passing that driver into the wrapper establishes the relationship that the later swap must preserve. Together, the changes connect extension configuration at startup to extension availability after replacing the underlying database.
The database file and the configured driver have different roles. Moving the file preserves the stored database contents. Reusing the driver preserves how the application opens and operates on those contents. A file replacement cannot by itself reproduce a process-level choice that the old opening path forgot to carry forward.
The test checks SQL after restoration
The regression coverage in PR #2014 uses the ROT13 extension. It first executes an extension-dependent query against the running node, obtains a backup, and then exercises database boot and restore. After each replacement, the test executes the same query again and checks its result. The relevant SQL is small enough to show the capability being preserved.
SELECT rot13("hello")
In this test, the expected returned text is uryyb. The extension must already be supplied through the configured extension path. The query is not a request to install an extension, and a node without that configured library is not expected to implement ROT13 merely because it runs this release.
This is a useful regression assertion because it checks the database that actually serves requests. A test limited to successful backup restoration could miss the bug. The SQLite file could open, ordinary queries could work, and extension-dependent SQL could remain broken. Executing the function after the replacement verifies the lost behavior directly.
The test also distinguishes setup from continued availability. Proving that the extension worked before restoration establishes that the library and initial configuration were sufficient. Failure only after restoration isolates the lifecycle transition. The repair therefore has a stronger explanation than a generic instruction to reload libraries at startup.
What the patch changes for extension users
The versioned changelog recommends this release to all SQLite extension users. Its heading accidentally says January 2024, but the GitHub release is dated January 2025. The recommendation concerns a loss of configured capability during database replacement, and does not claim that the patch adds a new extension or changes a vector algorithm.
My preference is to keep this behavior inside the database wrapper. Every caller that replaces a database should inherit the configured opening behavior. Requiring each restoration caller to reconstruct the extension list would distribute the same invariant across recovery code and make future omissions easier.
The practical distinction is between restoring stored state and restoring a usable database instance. This patch supplies the driver continuity needed for the latter when SQLite extensions are involved. A successful extension-dependent query after recovery now exercises the same configured driver choice that made the query available before recovery.