Skip to content

fix(catalog): skip orphaned catalog rows when loading the snapshot - #847

Merged
psteinroe merged 2 commits into
mainfrom
fix/schema-load-orphan-objects
Oct 9, 2026
Merged

psteinroe merged 2 commits into
mainfrom
fix/schema-load-orphan-objects

Conversation

@psteinroe

@psteinroe psteinroe commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

A catalog row that references an object that no longer exists (e.g. a pg_proc row whose pg_namespace row is gone) made the whole snapshot fail to load, disabling every database-backed feature. Such rows are now skipped.

Root cause

The functions, types and triggers queries left-joined pg_namespace (and pg_language, pg_proc, pg_class) while forcing the joined columns non-null with sqlx ! overrides. An orphaned row produced a null that failed to decode (ColumnDecode { source: UnexpectedNullError }), and because Snapshot::load uses try_join!, that single row discarded the entire snapshot. policies reads pg_policies, which itself left-joins the namespace, so policies on an orphaned table had the same problem.

Fix

Those joins are now inner joins, and policies with a null schema are filtered out, matching the queries that already inner-join (tables, columns, indexes, sequences, operators). I audited the remaining queries: extensions keeps its left joins on purpose, and its schema is already nullable. casts, roles, schemas and versions cannot produce this null. The new test orphans a function, type, table, trigger and policy by deleting their pg_namespace row and checks that the rest of the snapshot still loads.

The functions query also gets an order by f.oid. It had no ordering, so the join change reshuffled the overloads hover lists. Ordering by oid makes that deterministic, and the builtin_max hover snapshot is updated to match.

This is an alternative to #844. It fixes the queries at the source and leaves Snapshot::load unchanged, rather than also loading most snapshot parts leniently.

Fixes #842

The functions, types and triggers queries left-joined pg_namespace (and
pg_language, pg_proc, pg_class) while forcing those columns non-null with
sqlx "!" overrides. A row whose referenced object no longer exists then
produced a null that failed to decode, and since Snapshot::load uses
try_join! the whole snapshot was discarded. pg_policies left-joins the
namespace too, so policies on orphaned tables had the same problem.

Inner-join the referenced objects and filter null policy schemas so
orphaned rows are skipped, matching the queries that already inner-join.

Fixes #842
The functions query had no order by, so its row order depended on the
query plan. Switching to inner joins changed the plan and reordered the
overloads hover lists, breaking the builtin_max snapshot. Order by oid so
the result is deterministic.
@psteinroe
psteinroe merged commit e74c8b8 into main Oct 9, 2026
9 checks passed
@psteinroe
psteinroe deleted the fix/schema-load-orphan-objects branch October 9, 2026 19:04
@gintsgints

Copy link
Copy Markdown
Contributor

@psteinroe Oh I see, you already fixed ordering ok :).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Schema load fails when there is an orphan database objects

2 participants