On Fri, 21 Aug 2026 at 09:40, Nitin Jadhav <[email protected]> wrote:
> > These are minor; the code change itself looks reasonable to me. Thank you Nitin, here is v3 version of the patch addressing all nit-picks Regards, -- Alexander Kukushkin
From ed11873c6421ff7290488b6d11ce5cb72f01eeb2 Mon Sep 17 00:00:00 2001 From: Alexander Kukushkin <[email protected]> Date: Thu, 20 Aug 2026 13:34:50 +0200 Subject: [PATCH] pg_dump: sort casts and transforms independent of OIDs DOTypeNameCompare() sorts dumpable objects by (priority, namespace, name, objType) and then an object-type-specific natural-key tiebreaker. Casts and transforms have no namespace of their own, and getCasts() / getTransforms() build their sort "name" from the *unqualified* type (and language) names. Two casts therefore tie whenever their source and target type names match while the types live in different schemas -- for example a cast to pg_catalog.json and a cast to someext.json from the same source type both get the sort name "sourcetype json". Transforms tie the same way ("typname langname"). With no DO_CAST or DO_TRANSFORM tiebreaker, such pairs reach the Assert(false) fall-through added in commit 0decd5e89db (aborting assert-enabled pg_dump), or on non-assert builds sort by OID, reintroducing exactly the schema-diff instability that commit and its follow-ups (b61a5c4bed7, 4921a5972a3, d49936f3028) have been eliminating. Break the tie using the referenced types' full natural keys via the existing pgTypeNameCompare() (nspname, then typname), the same helper already used for function arguments and operator operands. For transforms, comparing trftype alone suffices: a name tie already implies the same unqualified typname and the same language name, so only the type's schema can differ. Add regression coverage to 002_pg_dump.pl: casts that tie on the target type's schema and casts that tie on the source type's schema (exercising both new comparisons), plus two transforms sharing a typname across schemas. These abort an unpatched assert-enabled run and pass with the fix. --- src/bin/pg_dump/pg_dump_sort.c | 32 ++++++++++++++++++++ src/bin/pg_dump/t/002_pg_dump.pl | 55 ++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c index 03e5c1c1116..6ff106d8ac3 100644 --- a/src/bin/pg_dump/pg_dump_sort.c +++ b/src/bin/pg_dump/pg_dump_sort.c @@ -342,6 +342,38 @@ DOTypeNameCompare(const void *p1, const void *p2) if (cmpval != 0) return cmpval; } + else if (obj1->objType == DO_CAST) + { + CastInfo *cobj1 = *(CastInfo *const *) p1; + CastInfo *cobj2 = *(CastInfo *const *) p2; + + /* + * The "name" is only the source and target type names, unqualified, + * so two casts tie whenever their types share typnames across + * different schemas. Break the tie by the source then target types' + * full natural keys. + */ + cmpval = pgTypeNameCompare(cobj1->castsource, cobj2->castsource); + if (cmpval != 0) + return cmpval; + cmpval = pgTypeNameCompare(cobj1->casttarget, cobj2->casttarget); + if (cmpval != 0) + return cmpval; + } + else if (obj1->objType == DO_TRANSFORM) + { + TransformInfo *tobj1 = *(TransformInfo *const *) p1; + TransformInfo *tobj2 = *(TransformInfo *const *) p2; + + /* + * Same unqualified-typname ambiguity as casts. The language name + * was already compared as part of dobj.name, so trftype is the only + * remaining natural-key field that can break the tie. + */ + cmpval = pgTypeNameCompare(tobj1->trftype, tobj2->trftype); + if (cmpval != 0) + return cmpval; + } else if (obj1->objType == DO_ATTRDEF) { AttrDefInfo *adobj1 = *(AttrDefInfo *const *) p1; diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index 9258948b583..ae603bfb4c0 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -2204,6 +2204,42 @@ my %tests = ( like => { %full_runs, section_pre_data => 1, }, }, + 'CREATE CAST to public target type sharing a typname' => { + create_order => 51, + create_sql => ' + CREATE SCHEMA dump_cast_schema; + CREATE TYPE public.dump_cast_src_for_target_test AS ENUM (\'a\'); + CREATE TYPE public.dump_cast_tgt AS ENUM (\'a\'); + CREATE TYPE dump_cast_schema.dump_cast_tgt AS ENUM (\'a\'); + CREATE TYPE public.dump_cast_src_for_source_test AS ENUM (\'a\'); + CREATE TYPE dump_cast_schema.dump_cast_src_for_source_test AS ENUM (\'a\'); + CREATE CAST (public.dump_cast_src_for_target_test AS public.dump_cast_tgt) WITH INOUT; + CREATE CAST (public.dump_cast_src_for_target_test AS dump_cast_schema.dump_cast_tgt) WITH INOUT; + CREATE CAST (public.dump_cast_src_for_source_test AS public.dump_cast_tgt) WITH INOUT; + CREATE CAST (dump_cast_schema.dump_cast_src_for_source_test AS public.dump_cast_tgt) WITH INOUT;', + regexp => + qr/CREATE CAST \(public\.dump_cast_src_for_target_test AS public\.dump_cast_tgt\) WITH INOUT;/m, + like => { %full_runs, section_pre_data => 1, }, + }, + + 'CREATE CAST to schema-qualified target type sharing a typname' => { + regexp => + qr/CREATE CAST \(public\.dump_cast_src_for_target_test AS dump_cast_schema\.dump_cast_tgt\) WITH INOUT;/m, + like => { %full_runs, section_pre_data => 1, }, + }, + + 'CREATE CAST from public source type sharing a typname' => { + regexp => + qr/CREATE CAST \(public\.dump_cast_src_for_source_test AS public\.dump_cast_tgt\) WITH INOUT;/m, + like => { %full_runs, section_pre_data => 1, }, + }, + + 'CREATE CAST from schema-qualified source type sharing a typname' => { + regexp => + qr/CREATE CAST \(dump_cast_schema\.dump_cast_src_for_source_test AS public\.dump_cast_tgt\) WITH INOUT;/m, + like => { %full_runs, section_pre_data => 1, }, + }, + 'CREATE DATABASE postgres' => { regexp => qr/^ \QCREATE DATABASE postgres WITH TEMPLATE = template0 \E @@ -2927,6 +2957,25 @@ my %tests = ( like => { %full_runs, section_pre_data => 1, }, }, + 'CREATE TRANSFORM with typname shared across schemas' => { + create_order => 34, + create_sql => ' + CREATE SCHEMA dump_trf_schema; + CREATE TYPE public.dump_trf_type AS ENUM (\'a\'); + CREATE TYPE dump_trf_schema.dump_trf_type AS ENUM (\'a\'); + CREATE TRANSFORM FOR public.dump_trf_type LANGUAGE sql (FROM SQL WITH FUNCTION prsd_lextype(internal)); + CREATE TRANSFORM FOR dump_trf_schema.dump_trf_type LANGUAGE sql (FROM SQL WITH FUNCTION prsd_lextype(internal));', + regexp => + qr/CREATE TRANSFORM FOR public\.dump_trf_type LANGUAGE sql \(FROM SQL WITH FUNCTION pg_catalog\.prsd_lextype\(internal\)\);/m, + like => { %full_runs, section_pre_data => 1, }, + }, + + 'CREATE TRANSFORM for schema-qualified type sharing a typname' => { + regexp => + qr/CREATE TRANSFORM FOR dump_trf_schema\.dump_trf_type LANGUAGE sql \(FROM SQL WITH FUNCTION pg_catalog\.prsd_lextype\(internal\)\);/m, + like => { %full_runs, section_pre_data => 1, }, + }, + 'CREATE LANGUAGE pltestlang' => { create_order => 18, create_sql => 'CREATE LANGUAGE pltestlang -- 2.34.1
