From 9f1bdd1b04621c624958fc0af36fcbc5eff18f9b Mon Sep 17 00:00:00 2001 From: Teletele Date: Sat, 10 Oct 2026 10:01:46 +0800 Subject: [PATCH v1] Fix pg_dump --clean with inherited partition constraints. pg_dump emits DROP CONSTRAINT for inherited constraints on partitions, which the backend does not allow. This causes pg_restore --clean to report errors, or abort with --exit-on-error, --single-transaction, or --transaction-size. Avoid generating a DROP when the constraint's index is attached to a parent partitioned index. The constraint will go away when the parent constraint or partition's table is dropped. Local constraints on partitions still get their own DROP commands. This extends the fix in 1fc3403626d4 to dumpConstraint(). --- src/bin/pg_dump/meson.build | 1 + src/bin/pg_dump/pg_dump.c | 17 +- .../pg_dump/t/007_pg_dump_partition_clean.pl | 146 ++++++++++++++++++ 3 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 src/bin/pg_dump/t/007_pg_dump_partition_clean.pl diff --git a/src/bin/pg_dump/meson.build b/src/bin/pg_dump/meson.build index 79bd5036841..53fed8e4492 100644 --- a/src/bin/pg_dump/meson.build +++ b/src/bin/pg_dump/meson.build @@ -103,6 +103,7 @@ tests += { 't/004_pg_dump_parallel.pl', 't/005_pg_dump_filterfile.pl', 't/006_pg_dump_compress.pl', + 't/007_pg_dump_partition_clean.pl', 't/010_dump_connstr.pl', ], }, diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 90971fbdac6..b72abc9bf25 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -18883,10 +18883,19 @@ dumpConstraint(Archive *fout, const ConstraintInfo *coninfo) "pg_catalog.pg_class", "INDEX", fmtQualifiedDumpable(indxinfo)); - appendPQExpBuffer(delq, "ALTER %sTABLE ONLY %s ", foreign, - fmtQualifiedDumpable(tbinfo)); - appendPQExpBuffer(delq, "DROP CONSTRAINT %s;\n", - fmtId(coninfo->dobj.name)); + /* + * If the index is attached to a partitioned index, the backend will + * not allow us to drop the constraint separately. It will go away + * when we drop the parent constraint or the partition's table. See + * dumpIndex() for the implications for selective restores. + */ + if (indxinfo->parentidx == 0) + { + appendPQExpBuffer(delq, "ALTER %sTABLE ONLY %s ", foreign, + fmtQualifiedDumpable(tbinfo)); + appendPQExpBuffer(delq, "DROP CONSTRAINT %s;\n", + fmtId(coninfo->dobj.name)); + } tag = psprintf("%s %s", tbinfo->dobj.name, coninfo->dobj.name); diff --git a/src/bin/pg_dump/t/007_pg_dump_partition_clean.pl b/src/bin/pg_dump/t/007_pg_dump_partition_clean.pl new file mode 100644 index 00000000000..6f135899d2d --- /dev/null +++ b/src/bin/pg_dump/t/007_pg_dump_partition_clean.pl @@ -0,0 +1,146 @@ +# Copyright (c) 2026, PostgreSQL Global Development Group + +use strict; +use warnings FATAL => 'all'; + +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +my $tempdir = PostgreSQL::Test::Utils::tempdir; +my $node = PostgreSQL::Test::Cluster->new('main'); +$node->init; +$node->start; + +$node->safe_psql('postgres', 'CREATE DATABASE dump_src'); +$node->safe_psql('postgres', 'CREATE DATABASE dump_dst'); +$node->safe_psql( + 'dump_src', + q{ +CREATE TABLE p ( + id integer, + k integer, + payload text, + CONSTRAINT p_pkey PRIMARY KEY (id, k), + CONSTRAINT p_unique UNIQUE (payload, k) +) PARTITION BY LIST (k); +CREATE TABLE p1 PARTITION OF p FOR VALUES IN (1); +CREATE TABLE p2 PARTITION OF p FOR VALUES IN (2); +ALTER TABLE p1 ADD CONSTRAINT p1_local UNIQUE (id); +INSERT INTO p VALUES (1, 1, 'one'), (2, 2, 'two'); +}); + +# A clean dump must drop parent and local constraints, but not inherited ones. +$node->command_ok( + [ + 'pg_dump', '--clean', '--no-sync', + '--file' => "$tempdir/clean.sql", + '--dbname' => $node->connstr('dump_src'), + ], + 'plain dump with clean'); +my $plain = slurp_file("$tempdir/clean.sql"); +like( + $plain, + qr/ALTER TABLE ONLY public\.p DROP CONSTRAINT p_pkey;/, + 'plain dump drops parent primary key'); +like( + $plain, + qr/ALTER TABLE ONLY public\.p DROP CONSTRAINT p_unique;/, + 'plain dump drops parent unique constraint'); +unlike( + $plain, + qr/DROP CONSTRAINT p[12]_pkey;/, + 'plain dump does not drop inherited primary keys'); +unlike( + $plain, + qr/DROP CONSTRAINT p[12]_payload_k_key;/, + 'plain dump does not drop inherited unique constraints'); +like( + $plain, + qr/ALTER TABLE ONLY public\.p1 DROP CONSTRAINT p1_local;/, + 'plain dump drops local constraint on partition'); + +$node->command_ok( + [ + 'pg_dump', '--format=custom', '--no-sync', + '--file' => "$tempdir/partition.dump", + '--dbname' => $node->connstr('dump_src'), + ], + 'custom dump of partition constraints'); +$node->command_ok( + [ + 'pg_restore', '--exit-on-error', + '--dbname' => $node->connstr('dump_dst'), + "$tempdir/partition.dump", + ], + 'initial restore of partition constraints'); + +for my $option ('--exit-on-error', '--single-transaction', + '--transaction-size=2') +{ + $node->command_ok( + [ + 'pg_restore', '--clean', '--if-exists', $option, + '--dbname' => $node->connstr('dump_dst'), + "$tempdir/partition.dump", + ], + "clean restore of partition constraints with $option"); +} + +is($node->safe_psql('dump_dst', 'SELECT * FROM p ORDER BY id'), + "1|1|one\n2|2|two", 'data preserved after clean restores'); + +# Each partition should still inherit both constraints from the parent. +is( $node->safe_psql( + 'dump_dst', + q{SELECT count(*) FROM pg_constraint + WHERE conrelid IN ('p'::regclass, 'p1'::regclass, 'p2'::regclass) + AND contype IN ('p', 'u') AND conparentid <> 0}), + '4', + 'partition primary keys and unique constraints remain attached'); + +# Selective clean restore must also drop and recreate a local constraint. +$node->command_ok( + [ + 'pg_restore', '--list', + '--file' => "$tempdir/toc.list", + "$tempdir/partition.dump", + ], + 'list partition constraint archive'); +my @toc = split /\n/, slurp_file("$tempdir/toc.list"); +my @local = grep { / CONSTRAINT public p1 p1_local / } @toc; +is(scalar @local, 1, 'local constraint has an archive entry'); +append_to_file("$tempdir/local.list", join("\n", @local) . "\n"); +$node->command_ok( + [ + 'pg_restore', '--clean', '--exit-on-error', + '--use-list' => "$tempdir/local.list", + '--dbname' => $node->connstr('dump_dst'), + "$tempdir/partition.dump", + ], + 'selective clean restore of local partition constraint'); + +# Inherited constraints cannot be dropped if their parent is not selected. +# Check the SQL, since recreating one on an existing table would fail. +my @inherited = grep { / CONSTRAINT public p2 p2_pkey / } @toc; +is(scalar @inherited, 1, 'inherited constraint has an archive entry'); +append_to_file("$tempdir/inherited.list", join("\n", @inherited) . "\n"); +$node->command_ok( + [ + 'pg_restore', '--clean', + '--use-list' => "$tempdir/inherited.list", + '--file' => "$tempdir/inherited.sql", + "$tempdir/partition.dump", + ], + 'generate selective clean restore of inherited partition constraint'); +my $selective = slurp_file("$tempdir/inherited.sql"); +unlike( + $selective, + qr/DROP CONSTRAINT/, + 'selective restore omits inherited DROP'); +like( + $selective, + qr/ADD CONSTRAINT p2_pkey PRIMARY KEY/, + 'selective restore retains inherited constraint creation'); + +done_testing(); -- 2.43.0