Skip to content

Use (catoid, oid) object identity for filter lookups#44

Merged
teknogeek0 merged 1 commit into
mainfrom
fix/filter-oid-collision
Jul 17, 2026
Merged

Use (catoid, oid) object identity for filter lookups#44
teknogeek0 merged 1 commit into
mainfrom
fix/filter-oid-collision

Conversation

@teknogeek0

Copy link
Copy Markdown
Collaborator

Problem

The filter table keyed rows on oid alone, with a UNIQUE index on (oid). PostgreSQL OIDs are unique only within a system catalog, not across them, so a pg_type OID can equal a pg_proc OID. When that happens, a lookup for one object matches the other's filter row and the wrong object is skipped.

Observed in a migration: a CREATE TYPE was silently dropped because its enum OID matched an excluded extension function's pg_proc OID, after which the dependent CREATE TABLE failed type ... does not exist and the clone aborted (exit 12).

Fix

Mirror pg_depend's (classid, objid) identity model:

  • filter gains a catoid column; the unique index becomes (catoid, oid).
  • A catnames table maps the relevant catalog OIDs (pg_class, pg_constraint, pg_attrdef, pg_extension, pg_collation), fetched from Postgres at runtime — nothing hardcoded.
  • Lookups take (catalogOid, objectOid). TOC entries that carry no catalog id (e.g. REFRESH MATERIALIZED VIEW) fall back to an OID-only lookup, which is safe because OIDs are drawn from a single per-database counter.

Ports upstream dimitri/pgcopydb dimitri#972, reconciled with this repo's fork-only filter code:

  • copydb_objectid_is_filtered_out takes a catalogOid (threaded from the archive item) rather than upstream's item struct.
  • The fork-only extension, extension-object, and pg_depend filter inserts carry catoid (classid, or the pg_extension/pg_class catnames entry).
  • The extension-objects_table query-back join is guarded by the pg_class catoid (it was an OID-only match, the same collision class this fix addresses).

Tests

Upstream ships no regression test for this, and a true cross-catalog OID collision can't be reproduced deterministically (OIDs are PG-assigned). Coverage relies on the existing filter/extension suites for no-regression.

Verified locally: full PGVERSION=18 make tests green; exclude-extension, filtering, and extensions explicitly exercise the changed paths.

The filter table keyed rows on oid alone, so OIDs that collide across
Postgres system catalogs (e.g. a pg_type oid equal to a pg_proc oid) could
match the wrong row and filter out the wrong object — dropping a CREATE TYPE
whose oid coincided with an excluded extension function, which then failed
the dependent CREATE TABLE and aborted the clone.

Mirror pg_depend's (classid, objid) identity: every filter row carries a
catoid, the unique index becomes (catoid, oid), and lookups take
(catalogOid, objectOid). A catnames table maps the relevant catalog OIDs,
fetched at runtime so nothing is hardcoded. TOC entries with no catalog id
(e.g. REFRESH MATERIALIZED VIEW) fall back to an OID-only lookup.

Ports upstream dimitri/pgcopydb dimitri#972, reconciled with this repo's fork-only
filter code: copydb_objectid_is_filtered_out takes catalogOid (from the
archive item) instead of upstream's item struct; the fork-only extension,
extension-object, and pg_depend inserts carry catoid (classid, or the
pg_extension/pg_class catnames entry); and the extension-object -> s_table
query-back is guarded by the pg_class catoid.
@teknogeek0
teknogeek0 merged commit efd2506 into main Jul 17, 2026
93 checks passed
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.

1 participant