From 3e734ca8eab6083f6a27ea560e2fefd834f8ee73 Mon Sep 17 00:00:00 2001 From: Vignesh C 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