Read DuckDB database files written in a newer storage format than this DuckDB supports
Installing and Loading
INSTALL storage_compat FROM community;
LOAD storage_compat;
Example
-- A database written by a newer DuckDB cannot be opened directly:
ATTACH 'file200.db' AS db;
-- IO Error: Trying to read a database file with version number 999,
-- but we can only read versions between 64 and 68.
-- With storage_compat, attach it through the prefix instead:
LOAD storage_compat;
ATTACH 'storage_compat:file200.db' AS db;
SHOW ALL TABLES;
SELECT * FROM db.events;
-- Columns whose type this DuckDB cannot represent are shown, never invented:
-- ts UNSUPPORTED(TIMESTAMPTZ_NS)
-- Reading one is refused rather than guessed; the other columns read normally.
SELECT id, label FROM db.future;
-- Full inventory, including what is not readable and why:
SELECT * FROM db.storage_compat_tables();
About storage_compat
In a deployment with more than one DuckDB, the versions drift. One service writes with a newer DuckDB while another still reads with an older one, and the older one refuses the file outright — there is no partial read, because the catalog layer changes shape between storage versions and DuckDB's binary serialization has no forward compatibility by design.
The producer-side fix (ATTACH ... (STORAGE_VERSION '...')) only helps if you
control every writer. storage_compat is the consumer-side fix: it can be deployed
unilaterally by the service doing the reading, so producers and consumers can
version independently.
It works by embedding the newer DuckDB inside the extension and reaching it only through its C API. The newer engine reads its own format — so results are correct by construction, not by reimplementing a reader — and the extension translates the chunks it returns into the host engine's vectors. Nothing is pinned to a specific storage version: rebuild with a newer guest and the same extension covers the next format too.
ATTACH through the storage_compat: prefix and the file appears as an ordinary
read-only attached database: tables and views, native types (LIST, STRUCT, MAP,
ENUM, DECIMAL, UUID, INTERVAL), projection pushdown, joins against local tables.
It never guesses. When you do not control the producers, a writer can start
using a type your engine has never heard of without warning, so degrading
predictably is the whole point. A column whose type the host cannot represent keeps
its place in the catalog as UNSUPPORTED(<the newer engine's type name>), the
table's other columns read normally, and projecting the unrepresentable one raises
an error naming the column, the storage format of the file and the DuckDB you are
running. db.storage_compat_tables() lists every column with a readable flag and
a reason. Tables are never silently hidden and values are never invented.
All write paths are refused and the file is left byte-identical; the embedded engine is additionally opened read-only.
Size. This extension contains a complete second DuckDB, so the binary is large (tens of MB). That is inherent to the approach: correctness comes from shipping the engine that owns the format rather than reimplementing its reader.
Added Functions
This extension does not add any functions.
Overloaded Functions
This extension does not add any function overloads.
Added Types
This extension does not add any types.
Added Settings
This extension does not add any settings.