From e809c9c3aa39102b64a3cf2af54d2f0742f8c570 Mon Sep 17 00:00:00 2001 From: David Boone Date: Thu, 3 Sep 2026 14:47:08 -0700 Subject: [PATCH] fix: constrain pg_depend lookups to the right catalog pg_depend.objid is only unique within the catalog named by classid, but the PostgreSQL reader matched on objid alone. Any extension member from another catalog whose oid collides with a relation's pg_class oid made the NOT EXISTS fire and silently dropped that object from the model. Observed on a PostGIS database where four of 57 requested tables went missing. Each was excluded by a deptype 'e' row that belonged to a pg_proc or pg_operator object rather than to the table, the two oids having collided: SELECT ns.nspname, cls.relname, cls.oid, dep.classid::regclass, dep.deptype FROM pg_class cls JOIN pg_namespace ns ON ns.oid = cls.relnamespace JOIN pg_depend dep ON dep.objid = cls.oid AND dep.deptype IN ('e', 'x'); Extensions installed or upgraded after the tables were created leave their member oids interleaved with relation oids, so the collision is ordinary rather than exotic. Tables and views now require classid = 'pg_class' and objsubid = 0 (extension membership is recorded on the whole relation); routines require classid = 'pg_proc'. The sequence reader's identity/serial ownership probe had the same defect and is fixed alongside. No fixture reproduces this: the collision depends on oid allocation order, which a freshly created test database cannot arrange. Co-Authored-By: Claude Opus 5 (1M context) --- .../PostgreSqlSchemaReader.Routines.cs | 4 ++-- .../PostgreSqlSchemaReader.Sequences.cs | 4 +++- src/SchemaSaurus.PostgreSql/PostgreSqlSchemaReader.Tables.cs | 5 ++++- src/SchemaSaurus.PostgreSql/PostgreSqlSchemaReader.Views.cs | 5 ++++- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/SchemaSaurus.PostgreSql/PostgreSqlSchemaReader.Routines.cs b/src/SchemaSaurus.PostgreSql/PostgreSqlSchemaReader.Routines.cs index 5fd459e..1acbe36 100644 --- a/src/SchemaSaurus.PostgreSql/PostgreSqlSchemaReader.Routines.cs +++ b/src/SchemaSaurus.PostgreSql/PostgreSqlSchemaReader.Routines.cs @@ -72,7 +72,7 @@ FROM pg_proc AS proc LEFT JOIN pg_description AS des ON des.objoid = proc.oid AND des.objsubid = 0 WHERE {routineWhere} AND ns.nspname NOT IN ('pg_catalog', 'information_schema'){schemaWhere} - AND NOT EXISTS (SELECT 1 FROM pg_depend dep WHERE dep.objid = proc.oid AND dep.deptype IN ('e', 'x')) + AND NOT EXISTS (SELECT 1 FROM pg_depend dep WHERE dep.classid = 'pg_proc'::regclass AND dep.objid = proc.oid AND dep.deptype IN ('e', 'x')) ORDER BY ns.nspname, proc.proname """; @@ -205,7 +205,7 @@ CROSS JOIN LATERAL unnest( LEFT JOIN pg_type AS base_typ ON base_typ.oid = typ.typbasetype WHERE {routineWhere} AND ns.nspname NOT IN ('pg_catalog', 'information_schema'){schemaWhere} - AND NOT EXISTS (SELECT 1 FROM pg_depend dep WHERE dep.objid = proc.oid AND dep.deptype IN ('e', 'x')) + AND NOT EXISTS (SELECT 1 FROM pg_depend dep WHERE dep.classid = 'pg_proc'::regclass AND dep.objid = proc.oid AND dep.deptype IN ('e', 'x')) AND param.parameter_mode <> 't' ORDER BY proc.oid, param.ordinal_position """; diff --git a/src/SchemaSaurus.PostgreSql/PostgreSqlSchemaReader.Sequences.cs b/src/SchemaSaurus.PostgreSql/PostgreSqlSchemaReader.Sequences.cs index 879d84d..4563a58 100644 --- a/src/SchemaSaurus.PostgreSql/PostgreSqlSchemaReader.Sequences.cs +++ b/src/SchemaSaurus.PostgreSql/PostgreSqlSchemaReader.Sequences.cs @@ -45,7 +45,9 @@ FROM pg_sequence AS seq WHERE NOT EXISTS ( SELECT 1 FROM pg_depend AS dep - WHERE dep.objid = cls.oid AND dep.deptype IN ('i', 'I', 'a') + WHERE dep.classid = 'pg_class'::regclass + AND dep.objid = cls.oid + AND dep.deptype IN ('i', 'I', 'a') ){schemaWhere} ORDER BY ns.nspname, cls.relname """; diff --git a/src/SchemaSaurus.PostgreSql/PostgreSqlSchemaReader.Tables.cs b/src/SchemaSaurus.PostgreSql/PostgreSqlSchemaReader.Tables.cs index 8f3a143..7fe2218 100644 --- a/src/SchemaSaurus.PostgreSql/PostgreSqlSchemaReader.Tables.cs +++ b/src/SchemaSaurus.PostgreSql/PostgreSqlSchemaReader.Tables.cs @@ -63,7 +63,10 @@ WHERE cls.relkind IN ('r', 'p', 'f') AND NOT EXISTS ( SELECT 1 FROM pg_depend dep - WHERE dep.objid = cls.oid AND dep.deptype IN ('e', 'x') + WHERE dep.classid = 'pg_class'::regclass + AND dep.objid = cls.oid + AND dep.objsubid = 0 + AND dep.deptype IN ('e', 'x') ) ORDER BY ns.nspname, cls.relname """; diff --git a/src/SchemaSaurus.PostgreSql/PostgreSqlSchemaReader.Views.cs b/src/SchemaSaurus.PostgreSql/PostgreSqlSchemaReader.Views.cs index 6a7450a..3b64b6e 100644 --- a/src/SchemaSaurus.PostgreSql/PostgreSqlSchemaReader.Views.cs +++ b/src/SchemaSaurus.PostgreSql/PostgreSqlSchemaReader.Views.cs @@ -44,7 +44,10 @@ WHERE cls.relkind IN ('v', 'm') AND NOT EXISTS ( SELECT 1 FROM pg_depend dep - WHERE dep.objid = cls.oid AND dep.deptype IN ('e', 'x') + WHERE dep.classid = 'pg_class'::regclass + AND dep.objid = cls.oid + AND dep.objsubid = 0 + AND dep.deptype IN ('e', 'x') ) ORDER BY ns.nspname, cls.relname """;