Hi, On Thu, 20 Aug 2026 at 13:04, Nitin Jadhav <[email protected]> wrote:
> > One small test-coverage suggestion - the added cast test exercises the > casttarget tie-breaker, because both casts use the same source type > and only the target type differs by schema. Since the patch also adds > a castsource tie-breaker, it may be worth adding a symmetric case > where two source types share the same typname across schemas and cast > to the same target type. That would cover both new comparisons > explicitly. Thank you for a review Nitin, here is a v2 version of the patch with extended test coverage. -- 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 | 28 ++++++++++++++++++ src/bin/pg_dump/t/002_pg_dump.pl | 49 ++++++++++++++++++++++++++++++++ 2 files changed, 77 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,34 @@ 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; break by type. */ + 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,36 @@ my %tests = ( like => { %full_runs, section_pre_data => 1, }, }, + 'CREATE CAST with typname shared across schemas' => { + create_order => 51, + create_sql => ' + CREATE SCHEMA dump_cast_schema; + CREATE TYPE public.dump_cast_src 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_src2 AS ENUM (\'a\'); + CREATE TYPE dump_cast_schema.dump_cast_src2 AS ENUM (\'a\'); + CREATE CAST (public.dump_cast_src AS public.dump_cast_tgt) WITH INOUT; + CREATE CAST (public.dump_cast_src AS dump_cast_schema.dump_cast_tgt) WITH INOUT; + CREATE CAST (public.dump_cast_src2 AS public.dump_cast_tgt) WITH INOUT; + CREATE CAST (dump_cast_schema.dump_cast_src2 AS public.dump_cast_tgt) WITH INOUT;', + regexp => + qr/CREATE CAST \(public\.dump_cast_src AS public\.dump_cast_tgt\) WITH INOUT;/m, + like => { %full_runs, section_pre_data => 1, }, + }, + + 'CREATE CAST to schema-qualified type sharing a typname' => { + regexp => + qr/CREATE CAST \(public\.dump_cast_src AS dump_cast_schema\.dump_cast_tgt\) WITH INOUT;/m, + like => { %full_runs, section_pre_data => 1, }, + }, + + 'CREATE CAST from schema-qualified type sharing a typname' => { + regexp => + qr/CREATE CAST \(dump_cast_schema\.dump_cast_src2 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
