0001-topup_pg_dump-dump-conflict-log-table-configuration-for-su.patch
application/octet-stream
Filename: 0001-topup_pg_dump-dump-conflict-log-table-configuration-for-su.patch
Type: application/octet-stream
Part: 0
From 3e734ca8eab6083f6a27ea560e2fefd834f8ee73 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 16 Dec 2025 09:16:26 +0530
Subject: [PATCH] pg_dump: dump conflict log table configuration for
subscriptions
Allow pg_dump to preserve the conflict_log_table setting of logical
replication subscriptions.
---
src/backend/commands/subscriptioncmds.c | 33 +++++++++++++-
src/bin/pg_dump/pg_dump.c | 52 +++++++++++++++++++++-
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/pg_dump/t/002_pg_dump.pl | 5 ++-
src/test/regress/expected/subscription.out | 5 ++-
5 files changed, 89 insertions(+), 7 deletions(-)
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index eb3fe068ddb..e30078c351c 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1765,7 +1765,38 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
* provided.
*/
if (relname != NULL)
- create_conflict_log_table(nspid, relname, subid);
+ {
+ Oid conflictlogrelid = get_relname_relid(relname, nspid);
+ if (OidIsValid(conflictlogrelid))
+ {
+ Relation conflictlogrel;
+
+ conflictlogrel = table_open(conflictlogrelid,
+ RowExclusiveLock);
+ if (IsConflictLogTable(conflictlogrelid))
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("conflict log table \"%s.%s\" cannot be used",
+ get_namespace_name(RelationGetNamespace(conflictlogrel)),
+ RelationGetRelationName(conflictlogrel)),
+ errdetail("The specified table is already registered for a different subscription."),
+ errhint("Specify a different conflict log table."));
+
+ if (!ValidateConflictLogTable(conflictlogrel))
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("conflict log table \"%s.%s\" has an incompatible definition",
+ get_namespace_name(RelationGetNamespace(conflictlogrel)),
+ RelationGetRelationName(conflictlogrel)),
+ errdetail("The table does not match the required conflict log table structure."),
+ errhint("Create the conflict log table with the expected definition or specify a different table."));
+
+ table_close(conflictlogrel, NoLock);
+
+ }
+ else
+ create_conflict_log_table(nspid, relname, subid);
+ }
values[Anum_pg_subscription_subconflictlognspid - 1] =
ObjectIdGetDatum(nspid);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 24ad201af2f..1e286e531b2 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5130,6 +5130,7 @@ getSubscriptions(Archive *fout)
int i_subfailover;
int i_subretaindeadtuples;
int i_submaxretention;
+ int i_subconflictlogrelid;
int i,
ntups;
@@ -5216,10 +5217,17 @@ getSubscriptions(Archive *fout)
if (fout->remoteVersion >= 190000)
appendPQExpBufferStr(query,
- " s.submaxretention\n");
+ " s.submaxretention,\n");
else
appendPQExpBuffer(query,
- " 0 AS submaxretention\n");
+ " 0 AS submaxretention,\n");
+
+ if (fout->remoteVersion >= 190000)
+ appendPQExpBufferStr(query,
+ " c.oid AS subconflictlogrelid\n");
+ else
+ appendPQExpBufferStr(query,
+ " 0::oid AS subconflictlogrelid\n");
appendPQExpBufferStr(query,
"FROM pg_subscription s\n");
@@ -5229,6 +5237,12 @@ getSubscriptions(Archive *fout)
"LEFT JOIN pg_catalog.pg_replication_origin_status o \n"
" ON o.external_id = 'pg_' || s.oid::text \n");
+ if (fout->remoteVersion >= 190000)
+ appendPQExpBufferStr(query,
+ "LEFT JOIN pg_class c ON c.relname = s.subconflictlogtable\n"
+ "LEFT JOIN pg_namespace n \n"
+ " ON n.oid = c.relnamespace AND n.oid = s.subconflictlognspid\n");
+
appendPQExpBufferStr(query,
"WHERE s.subdbid = (SELECT oid FROM pg_database\n"
" WHERE datname = current_database())");
@@ -5255,6 +5269,7 @@ getSubscriptions(Archive *fout)
i_subfailover = PQfnumber(res, "subfailover");
i_subretaindeadtuples = PQfnumber(res, "subretaindeadtuples");
i_submaxretention = PQfnumber(res, "submaxretention");
+ i_subconflictlogrelid = PQfnumber(res, "subconflictlogrelid");
i_subconninfo = PQfnumber(res, "subconninfo");
i_subslotname = PQfnumber(res, "subslotname");
i_subsynccommit = PQfnumber(res, "subsynccommit");
@@ -5292,6 +5307,22 @@ getSubscriptions(Archive *fout)
(strcmp(PQgetvalue(res, i, i_subretaindeadtuples), "t") == 0);
subinfo[i].submaxretention =
atoi(PQgetvalue(res, i, i_submaxretention));
+ subinfo[i].subconflictlogrelid =
+ atooid(PQgetvalue(res, i, i_subconflictlogrelid));
+
+ if (subinfo[i].subconflictlogrelid != InvalidOid)
+ {
+ TableInfo *tableInfo = findTableByOid(subinfo[i].subconflictlogrelid);
+
+ if (!tableInfo)
+ pg_fatal("could not find conflict log table with OID %u",
+ subinfo[i].subconflictlogrelid);
+
+ /* Ensure the table is marked to be dumped */
+ tableInfo->dobj.dump |= DUMP_COMPONENT_DEFINITION;
+
+ addObjectDependency(&subinfo[i].dobj, tableInfo->dobj.dumpId);
+ }
subinfo[i].subconninfo =
pg_strdup(PQgetvalue(res, i, i_subconninfo));
if (PQgetisnull(res, i, i_subslotname))
@@ -5564,6 +5595,23 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
appendPQExpBufferStr(query, ");\n");
+ if (subinfo->subconflictlogrelid != InvalidOid)
+ {
+ PQExpBuffer conflictlogbuf = createPQExpBuffer();
+ TableInfo *tbinfo = findTableByOid(subinfo->subconflictlogrelid);
+
+ appendStringLiteralAH(conflictlogbuf,
+ fmtQualifiedDumpable(tbinfo),
+ fout);
+
+ appendPQExpBuffer(query,
+ "\n\nALTER SUBSCRIPTION %s SET (conflict_log_table = %s);\n",
+ qsubname,
+ conflictlogbuf->data);
+
+ destroyPQExpBuffer(conflictlogbuf);
+ }
+
/*
* In binary-upgrade mode, we allow the replication to continue after the
* upgrade.
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 72a00e1bc20..20ffae491eb 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -719,6 +719,7 @@ typedef struct _SubscriptionInfo
bool subfailover;
bool subretaindeadtuples;
int submaxretention;
+ Oid subconflictlogrelid;
char *subconninfo;
char *subslotname;
char *subsynccommit;
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index e33aa95f6ff..ef11db6b8ee 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3204,9 +3204,10 @@ my %tests = (
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub3
CONNECTION \'dbname=doesnotexist\' PUBLICATION pub1
- WITH (connect = false, origin = any, streaming = on);',
+ WITH (connect = false, origin = any, streaming = on, conflict_log_table = \'conflict\');',
regexp => qr/^
- \QCREATE SUBSCRIPTION sub3 CONNECTION 'dbname=doesnotexist' PUBLICATION pub1 WITH (connect = false, slot_name = 'sub3', streaming = on);\E
+ \QCREATE SUBSCRIPTION sub3 CONNECTION 'dbname=doesnotexist' PUBLICATION pub1 WITH (connect = false, slot_name = 'sub3', streaming = on);\E\n\n\n
+ \QALTER SUBSCRIPTION sub3 SET (conflict_log_table = 'public.conflict');\E
/xm,
like => { %full_runs, section_post_data => 1, },
unlike => {
diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out
index bab2d0ea954..64ee5b9d43e 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -630,8 +630,9 @@ SELECT * FROM pg_publication_tables WHERE pubname = 'pub';
DROP PUBLICATION pub;
-- fail - set conflict_log_table to one already used by a different subscription
ALTER SUBSCRIPTION regress_conflict_test2 SET (conflict_log_table = 'public.regress_conflict_log1');
-ERROR: cannot create conflict log table "public.regress_conflict_log1" because a table with that name already exists
-HINT: Use a different name for the conflict log table or drop the existing table.
+ERROR: conflict log table "public.regress_conflict_log1" cannot be used
+DETAIL: The specified table is already registered for a different subscription.
+HINT: Specify a different conflict log table.
-- ok - dropping subscription also drops the log table
ALTER SUBSCRIPTION regress_conflict_test1 DISABLE;
ALTER SUBSCRIPTION regress_conflict_test1 SET (slot_name = NONE);
--
2.43.0