Which backend answers is the URI's business and nothing else's:
sqlite:///catalog.db opens a file, postgresql://user@host/db opens a
server, and every method below is the same either way. See _sqldriver for
what that costs (placeholder rewriting, and one predicate over SQLSTATEs).
Over sqlite this is a development catalog: local, single-process, and test
parity with PyIceberg, whose quickstart default is exactly this — a
SQLite-backed SqlCatalog — and which is what iceberg-rs.mojo's own fixtures
already use as an oracle. Over PostgreSQL the same catalog is deployable:
iceberg_tables and iceberg_namespace_properties are the tables PyIceberg's
SqlCatalog and the Java JdbcCatalog both use, the guarded UPDATE is a
real transaction against a real server, and many writers may share it. A
database this catalog creates opens cleanly in PyIceberg's SqlCatalog and
vice versa, on either engine: same two tables, same column names, same
optimistic-concurrency guard.
iceberg_tables (
catalog_name, table_namespace, table_name, -- primary key
metadata_location, previous_metadata_location
)
iceberg_namespace_properties (
catalog_name, namespace, property_key, -- primary key
property_value
)
Namespaces are dot-joined strings ('db.sub'); a nested namespace is a prefix
of a longer one, matched with a LIKE ... ESCAPE '!' pattern the same way
PyIceberg escapes %, _ and the escape character itself.
The commit path is the one part of this file that is not shared with the
sibling catalogs: unlike FilesystemCatalog (whose optimistic concurrency
compares the newest file a directory listing turns up) and RestCatalog
(whose server enforces assert-ref-snapshot-id), a SQL catalog's source of
truth is one row, and the swap is a guarded
UPDATE iceberg_tables SET metadata_location = <new>
WHERE ... AND metadata_location = <the value this attempt read>
exactly PyIceberg's guard. Zero rows affected means somebody else's commit
already moved the pointer; this reloads and retries like the REST catalog's
409 handling, and raises a commit-failed error when retries run out. Manifest
writing, data-file writing and metadata JSON serialization are the same
prepare_append / prepare_delete / prepare_overwrite /
prepare_dynamic_partition_overwrite machinery RestCatalog uses — this
file only adds the row that names the current metadata file.