From 6c74768f8fb3a2caaf06fefe5e6ecfa032ba5f63 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Thu, 7 May 2026 14:48:55 +0530
Subject: [PATCH v31 5/5] Preserve conflict log destination and subscription
 OID for subscriptions

Support pg_dump to dump and restore the conflict_log_destination setting for
subscriptions.

During a normal CREATE SUBSCRIPTION, a conflict log table is created
automatically when required. However, during binary upgrade, the conflict
log table will already exist and must be reused rather than recreated, and
the subscription must retain its original OID to correctly re-establish
catalog relationships.

To ensure correct behavior, pg_dump now emits an ALTER SUBSCRIPTION command
after subscription creation to restore the conflict_log_destination setting.
---
 src/backend/catalog/heap.c                    |   4 +-
 src/backend/commands/subscriptioncmds.c       | 146 +++++++++++++-----
 src/backend/utils/adt/pg_upgrade_support.c    |  10 ++
 src/bin/pg_dump/pg_dump.c                     | 110 ++++++++++++-
 src/bin/pg_dump/pg_dump.h                     |   2 +
 src/bin/pg_dump/pg_dump_sort.c                |  31 ++++
 src/bin/pg_dump/t/002_pg_dump.pl              |   5 +-
 src/bin/pg_upgrade/pg_upgrade.c               |   4 +
 src/bin/pg_upgrade/t/004_subscription.pl      |  14 +-
 src/include/catalog/binary_upgrade.h          |   1 +
 src/include/catalog/pg_proc.dat               |   4 +
 .../expected/spgist_name_ops.out              |   6 +-
 12 files changed, 290 insertions(+), 47 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index d5d3d688178..4804fb9f57e 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -311,11 +311,13 @@ heap_create(const char *relname,
 	 * But allow creating indexes on relations in pg_catalog even if
 	 * allow_system_table_mods = off, upper layers already guarantee it's on a
 	 * user defined relation, not a system one.
+	 *
+	 * Allow creation of conflict table in binary-upgrade mode.
 	 */
 	if (!allow_system_table_mods &&
 		((IsCatalogNamespace(relnamespace) && relkind != RELKIND_INDEX) ||
 		 IsToastNamespace(relnamespace) ||
-		 IsConflictNamespace(relnamespace)) &&
+		 (!IsBinaryUpgrade && IsConflictNamespace(relnamespace))) &&
 		IsNormalProcessingMode())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index a2de57e17b4..3cbf2b5ba4e 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -87,6 +87,12 @@
 /* check if the 'val' has 'bits' set */
 #define IsSet(val, bits)  (((val) & (bits)) == (bits))
 
+/*
+ * This will be set by the pg_upgrade_support function --
+ * binary_upgrade_set_next_pg_subscription_oid().
+ */
+Oid			binary_upgrade_next_pg_subscription_oid = InvalidOid;
+
 /*
  * Structure to hold a bitmap representing the user-provided CREATE/ALTER
  * SUBSCRIPTION command options and the parsed/default values of each of them.
@@ -793,8 +799,21 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 	memset(values, 0, sizeof(values));
 	memset(nulls, false, sizeof(nulls));
 
-	subid = GetNewOidWithIndex(rel, SubscriptionObjectIndexId,
-							   Anum_pg_subscription_oid);
+	/* Use binary-upgrade override for pg_subscription.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_pg_subscription_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_subscription OID value not set when in binary upgrade mode")));
+
+		subid = binary_upgrade_next_pg_subscription_oid;
+		binary_upgrade_next_pg_subscription_oid = InvalidOid;
+	}
+	else
+		subid = GetNewOidWithIndex(rel, SubscriptionObjectIndexId,
+								   Anum_pg_subscription_oid);
+
 	values[Anum_pg_subscription_oid - 1] = ObjectIdGetDatum(subid);
 	values[Anum_pg_subscription_subdbid - 1] = ObjectIdGetDatum(MyDatabaseId);
 	values[Anum_pg_subscription_subskiplsn - 1] = LSNGetDatum(InvalidXLogRecPtr);
@@ -1440,6 +1459,87 @@ CheckAlterSubOption(Subscription *sub, const char *option,
 	}
 }
 
+/*
+ * AlterSubscriptionConflictLogDestination
+ *
+ * Update the conflict log table associated with a subscription when its
+ * conflict log destination is changed.
+ *
+ * If the new destination requires a conflict log table and none was previously
+ * required, this function validates an existing conflict log table identified
+ * by the subscription specific naming convention or creates a new one.
+ *
+ * If the new destination no longer requires a conflict log table, the existing
+ * conflict log table associated with the subscription is removed via internal
+ * dependency cleanup to prevent orphaned relations.
+ *
+ * The function enforces that any conflict log table used is a permanent
+ * relation in a permanent schema, matches the expected structure, and is not
+ * already associated with another subscription.
+ *
+ * On success, *conflicttablerelid is set to the OID of the conflict log table
+ * that was created or validated, or to InvalidOid if no table is required.
+ *
+ * Returns true if the subscription's conflict log table reference must be
+ * updated as a result of the destination change; false otherwise.
+ */
+static bool
+AlterSubscriptionConflictLogDestination(Subscription *sub,
+										ConflictLogDest logdest,
+										Oid *conflicttablerelid)
+{
+	ConflictLogDest old_dest = GetLogDestination(sub->conflictlogdest);
+	bool		want_table;
+	bool		has_oldtable;
+	bool		update_relid = false;
+	Oid			relid = InvalidOid;
+
+	want_table = (logdest == CONFLICT_LOG_DEST_TABLE ||
+				  logdest == CONFLICT_LOG_DEST_ALL);
+	has_oldtable = (old_dest == CONFLICT_LOG_DEST_TABLE ||
+					old_dest == CONFLICT_LOG_DEST_ALL);
+
+	if (want_table && !has_oldtable)
+	{
+		char		relname[NAMEDATALEN];
+
+		snprintf(relname, NAMEDATALEN, "pg_conflict_log_%u", sub->oid);
+
+		/*
+		 * In upgrade scenarios, the conflict log table already exists. Update
+		 * the catalog to record the association.
+		 */
+		relid = get_relname_relid(relname, PG_CONFLICT_NAMESPACE);
+		if (!OidIsValid(relid))
+			relid = create_conflict_log_table(sub->oid, sub->name);
+
+		update_relid = true;
+	}
+	else if (!want_table && has_oldtable)
+	{
+		ObjectAddress object;
+
+		/*
+		 * Conflict log tables are recorded as internal dependencies of the
+		 * subscription.  Drop the table if it is not required anymore to
+		 * avoid stale or orphaned relations.
+		 *
+		 * XXX: At present, only conflict log tables are managed this way. In
+		 * future if we introduce additional internal dependencies, we may
+		 * need a targeted deletion to avoid deletion of any other objects.
+		 */
+		ObjectAddressSet(object, SubscriptionRelationId, sub->oid);
+		performDeletion(&object, DROP_CASCADE,
+						PERFORM_DELETION_INTERNAL |
+						PERFORM_DELETION_SKIP_ORIGINAL);
+
+		update_relid = true;
+	}
+
+	*conflicttablerelid = relid;
+	return update_relid;
+}
+
 /*
  * Alter the existing subscription.
  */
@@ -1804,53 +1904,23 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 
 					if (opts.conflictlogdest != old_dest)
 					{
-						bool want_table = (opts.conflictlogdest == CONFLICT_LOG_DEST_TABLE ||
-										   opts.conflictlogdest == CONFLICT_LOG_DEST_ALL);
-						bool has_oldtable = (old_dest == CONFLICT_LOG_DEST_TABLE ||
-											 old_dest == CONFLICT_LOG_DEST_ALL);
+						bool		update_relid;
+						Oid			relid = InvalidOid;
 
 						values[Anum_pg_subscription_subconflictlogdest - 1] =
 							CStringGetTextDatum(ConflictLogDestNames[opts.conflictlogdest]);
 						replaces[Anum_pg_subscription_subconflictlogdest - 1] = true;
 
-						if (want_table && !has_oldtable)
+						update_relid = AlterSubscriptionConflictLogDestination(sub,
+																			   opts.conflictlogdest,
+																			   &relid);
+						if (update_relid)
 						{
-							Oid		relid;
-
-							relid = create_conflict_log_table(subid, sub->name);
-
 							values[Anum_pg_subscription_subconflictlogrelid - 1] =
 														ObjectIdGetDatum(relid);
 							replaces[Anum_pg_subscription_subconflictlogrelid - 1] =
 														true;
 						}
-						else if (!want_table && has_oldtable)
-						{
-							ObjectAddress object;
-
-							/*
-							 * Conflict log tables are recorded as internal
-							 * dependencies of the subscription.  Drop the
-							 * table if it is not required anymore to avoid
-							 * stale or orphaned relations.
-							 *
-							 * XXX: At present, only conflict log tables are
-							 * managed this way.  In future if we introduce
-							 * additional internal dependencies, we may need
-							 * a targeted deletion to avoid deletion of any
-							 * other objects.
-							 */
-							ObjectAddressSet(object, SubscriptionRelationId,
-											 subid);
-							performDeletion(&object, DROP_CASCADE,
-											PERFORM_DELETION_INTERNAL |
-											PERFORM_DELETION_SKIP_ORIGINAL);
-
-							values[Anum_pg_subscription_subconflictlogrelid - 1] =
-												ObjectIdGetDatum(InvalidOid);
-							replaces[Anum_pg_subscription_subconflictlogrelid - 1] =
-												true;
-						}
 					}
 				}
 
diff --git a/src/backend/utils/adt/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c
index b505a6b4fee..59c3e7f0146 100644
--- a/src/backend/utils/adt/pg_upgrade_support.c
+++ b/src/backend/utils/adt/pg_upgrade_support.c
@@ -181,6 +181,16 @@ binary_upgrade_set_next_pg_authid_oid(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+Datum
+binary_upgrade_set_next_pg_subscription_oid(PG_FUNCTION_ARGS)
+{
+	Oid			subid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_pg_subscription_oid = subid;
+	PG_RETURN_VOID();
+}
+
 Datum
 binary_upgrade_create_empty_extension(PG_FUNCTION_ARGS)
 {
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index d56dcc701ce..ea7f197796c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1981,6 +1981,8 @@ checkExtensionMembership(DumpableObject *dobj, Archive *fout)
 static void
 selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout)
 {
+	DumpOptions *dopt = fout->dopt;
+
 	/*
 	 * DUMP_COMPONENT_DEFINITION typically implies a CREATE SCHEMA statement
 	 * and (for --clean) a DROP SCHEMA statement.  (In the absence of
@@ -2010,6 +2012,32 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout)
 		 */
 		nsinfo->dobj.dump_contains = nsinfo->dobj.dump = DUMP_COMPONENT_ACL;
 	}
+	else if (strcmp(nsinfo->dobj.name, "pg_conflict") == 0)
+	{
+		if (dopt->binary_upgrade)
+		{
+			/*
+			 * The pg_conflict schema is a strange beast that sits in a sort
+			 * of no-mans-land between being a system object and a user
+			 * object. CREATE SCHEMA would fail, so its
+			 * DUMP_COMPONENT_DEFINITION is just a comment.
+			 */
+			nsinfo->create = false;
+			nsinfo->dobj.dump = DUMP_COMPONENT_ALL;
+			nsinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION;
+			nsinfo->dobj.dump_contains = DUMP_COMPONENT_ALL;
+
+			/*
+			 * Also, make like it has a comment even if it doesn't; this is so
+			 * that we'll emit a command to drop the comment, if appropriate.
+			 * (Without this, we'd not call dumpCommentExtended for it.)
+			 */
+			nsinfo->dobj.components |= DUMP_COMPONENT_COMMENT;
+		}
+		else
+			nsinfo->dobj.dump_contains = nsinfo->dobj.dump =
+				DUMP_COMPONENT_NONE;
+	}
 	else if (strncmp(nsinfo->dobj.name, "pg_", 3) == 0 ||
 			 strcmp(nsinfo->dobj.name, "information_schema") == 0)
 	{
@@ -2067,9 +2095,31 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout)
 static void
 selectDumpableTable(TableInfo *tbinfo, Archive *fout)
 {
+	DumpOptions *dopt = fout->dopt;
+
 	if (checkExtensionMembership(&tbinfo->dobj, fout))
 		return;					/* extension membership overrides all else */
 
+	if (strcmp(tbinfo->dobj.namespace->dobj.name, "pg_conflict") == 0)
+	{
+		if (dopt->binary_upgrade)
+		{
+			/*
+			 * Dump pg_conflict tables only during binary upgrade. The schema
+			 * is assumed to already exist.
+			 */
+			tbinfo->dobj.dump = DUMP_COMPONENT_DEFINITION;
+
+			/*
+			 * Suppress the "ALTER TABLE ... OWNER TO ..." command for this
+			 * table. This prevents pg_dump from outputting the owner change.
+			 */
+			tbinfo->rolname = NULL;
+		}
+		else
+			tbinfo->dobj.dump = DUMP_COMPONENT_NONE;
+	}
+
 	/*
 	 * If specific tables are being dumped, dump just those tables; else, dump
 	 * according to the parent namespace's dump flag.
@@ -5184,6 +5234,8 @@ getSubscriptions(Archive *fout)
 	int			i_subfailover;
 	int			i_subretaindeadtuples;
 	int			i_submaxretention;
+	int			i_subconflictlogrelid;
+	int			i_sublogdestination;
 	int			i,
 				ntups;
 
@@ -5282,9 +5334,16 @@ getSubscriptions(Archive *fout)
 							 " '-1' AS subwalrcvtimeout,\n");
 
 	if (fout->remoteVersion >= 190000)
-		appendPQExpBufferStr(query, " fs.srvname AS subservername\n");
+		appendPQExpBufferStr(query, " fs.srvname AS subservername,\n");
 	else
-		appendPQExpBufferStr(query, " NULL AS subservername\n");
+		appendPQExpBufferStr(query, " NULL AS subservername,\n");
+
+	if (fout->remoteVersion >= 190000)
+		appendPQExpBufferStr(query,
+							 " s.subconflictlogrelid, s.subconflictlogdest\n");
+	else
+		appendPQExpBufferStr(query,
+							 " NULL AS subconflictlogrelid, NULL AS subconflictlogdest\n");
 
 	appendPQExpBufferStr(query,
 						 "FROM pg_subscription s\n");
@@ -5333,6 +5392,8 @@ getSubscriptions(Archive *fout)
 	i_subpublications = PQfnumber(res, "subpublications");
 	i_suborigin = PQfnumber(res, "suborigin");
 	i_suboriginremotelsn = PQfnumber(res, "suboriginremotelsn");
+	i_subconflictlogrelid = PQfnumber(res, "subconflictlogrelid");
+	i_sublogdestination = PQfnumber(res, "subconflictlogdest");
 
 	subinfo = pg_malloc_array(SubscriptionInfo, ntups);
 
@@ -5391,6 +5452,38 @@ getSubscriptions(Archive *fout)
 			subinfo[i].suboriginremotelsn =
 				pg_strdup(PQgetvalue(res, i, i_suboriginremotelsn));
 
+		if (PQgetisnull(res, i, i_subconflictlogrelid))
+			subinfo[i].subconflictlogrelid = InvalidOid;
+		else
+		{
+			TableInfo  *tableInfo;
+
+			subinfo[i].subconflictlogrelid =
+				atooid(PQgetvalue(res, i, i_subconflictlogrelid));
+
+			if (subinfo[i].subconflictlogrelid)
+			{
+				tableInfo = findTableByOid(subinfo[i].subconflictlogrelid);
+				if (!tableInfo)
+					pg_fatal("could not find conflict log table with OID %u",
+							 subinfo[i].subconflictlogrelid);
+
+				addObjectDependency(&subinfo[i].dobj, tableInfo->dobj.dumpId);
+			}
+		}
+
+		if (PQgetisnull(res, i, i_sublogdestination))
+			subinfo[i].subconflictlogdest = NULL;
+		else
+			subinfo[i].subconflictlogdest =
+				pg_strdup(PQgetvalue(res, i, i_sublogdestination));
+
+		if (PQgetisnull(res, i, i_sublogdestination))
+			subinfo[i].subconflictlogdest = NULL;
+		else
+			subinfo[i].subconflictlogdest =
+				pg_strdup(PQgetvalue(res, i, i_sublogdestination));
+
 		/* Decide whether we want to dump it */
 		selectDumpableObject(&(subinfo[i].dobj), fout);
 	}
@@ -5583,6 +5676,14 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
 	appendPQExpBuffer(delq, "DROP SUBSCRIPTION %s;\n",
 					  qsubname);
 
+	if (dopt->binary_upgrade)
+	{
+		appendPQExpBufferStr(query, "\n-- For binary upgrade, must preserve pg_subscription.oid\n");
+		appendPQExpBuffer(query,
+						  "SELECT pg_catalog.binary_upgrade_set_next_pg_subscription_oid('%u'::pg_catalog.oid);\n\n",
+						  subinfo->dobj.catId.oid);
+	}
+
 	appendPQExpBuffer(query, "CREATE SUBSCRIPTION %s ",
 					  qsubname);
 	if (subinfo->subservername)
@@ -5656,6 +5757,11 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
 
 	appendPQExpBufferStr(query, ");\n");
 
+	appendPQExpBuffer(query,
+					  "\n\nALTER SUBSCRIPTION %s SET (conflict_log_destination = %s);\n",
+					  qsubname,
+					  subinfo->subconflictlogdest);
+
 	/*
 	 * 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 5a6726d8b12..a43a3049343 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -722,6 +722,7 @@ typedef struct _SubscriptionInfo
 	bool		subfailover;
 	bool		subretaindeadtuples;
 	int			submaxretention;
+	Oid			subconflictlogrelid;
 	char	   *subservername;
 	char	   *subconninfo;
 	char	   *subslotname;
@@ -730,6 +731,7 @@ typedef struct _SubscriptionInfo
 	char	   *subpublications;
 	char	   *suborigin;
 	char	   *suboriginremotelsn;
+	char	   *subconflictlogdest;
 } SubscriptionInfo;
 
 /*
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 03e5c1c1116..c27b232e799 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -1131,6 +1131,19 @@ repairTableAttrDefMultiLoop(DumpableObject *tableobj,
 	addObjectDependency(attrdefobj, tableobj->dumpId);
 }
 
+/*
+ * Because we make subscriptions depend on their conflict log tables, while
+ * there is an automatic dependency in the other direction, we need to break
+ * the loop. Remove the automatic dependency, allowing the table to be created
+ * first.
+ */
+static void
+repairSubscriptionTableLoop(DumpableObject *subobj, DumpableObject *tableobj)
+{
+	/* Remove table's dependency on subscription */
+	removeObjectDependency(tableobj, subobj->dumpId);
+}
+
 /*
  * CHECK, NOT NULL constraints on domains work just like those on tables ...
  */
@@ -1361,6 +1374,24 @@ repairDependencyLoop(DumpableObject **loop,
 		return;
 	}
 
+	/*
+	 * Subscription and its Conflict Log Table
+	 */
+	if (nLoop == 2 &&
+		loop[0]->objType == DO_TABLE &&
+		loop[1]->objType == DO_SUBSCRIPTION)
+	{
+		repairSubscriptionTableLoop(loop[1], loop[0]);
+		return;
+	}
+	if (nLoop == 2 &&
+		loop[0]->objType == DO_SUBSCRIPTION &&
+		loop[1]->objType == DO_TABLE)
+	{
+		repairSubscriptionTableLoop(loop[0], loop[1]);
+		return;
+	}
+
 	/* index on partitioned table and corresponding index on partition */
 	if (nLoop == 2 &&
 		loop[0]->objType == DO_INDEX &&
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 3bc8e51561d..81998e2f96e 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3276,9 +3276,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_destination= table);',
 		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_destination = table);\E
 			/xm,
 		like => { %full_runs, section_post_data => 1, },
 		unlike => {
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 2127d297bfe..135ef658c2c 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -35,6 +35,10 @@
  *
  *	We control all assignments of pg_database.oid because we want the directory
  *	names to match between the old and new cluster.
+ *
+ *	We control assignment of pg_subscription.oid because we want the oid to
+ *	match between the old and new cluster to make use of subscription's
+ *	conflict log table which is named using the subscription oid.
  */
 
 
diff --git a/src/bin/pg_upgrade/t/004_subscription.pl b/src/bin/pg_upgrade/t/004_subscription.pl
index f68821df2a3..81956964ede 100644
--- a/src/bin/pg_upgrade/t/004_subscription.pl
+++ b/src/bin/pg_upgrade/t/004_subscription.pl
@@ -290,7 +290,7 @@ $publisher->safe_psql(
 $old_sub->safe_psql(
 	'postgres', qq[
 		CREATE TABLE tab_upgraded2(id int);
-		CREATE SUBSCRIPTION regress_sub5 CONNECTION '$connstr' PUBLICATION regress_pub5;
+		CREATE SUBSCRIPTION regress_sub5 CONNECTION '$connstr' PUBLICATION regress_pub5 with (conflict_log_destination = 'table');
 ]);
 
 # The table tab_upgraded2 will be in the init state as the subscriber's
@@ -312,7 +312,10 @@ my $tab_upgraded1_oid = $old_sub->safe_psql('postgres',
 	"SELECT oid FROM pg_class WHERE relname = 'tab_upgraded1'");
 my $tab_upgraded2_oid = $old_sub->safe_psql('postgres',
 	"SELECT oid FROM pg_class WHERE relname = 'tab_upgraded2'");
-
+my $sub5_oid = $old_sub->safe_psql('postgres',
+	"SELECT oid FROM pg_subscription where subname = 'regress_sub5'");
+my $sub_clt_relid = $old_sub->safe_psql('postgres',
+	"SELECT subconflictlogrelid FROM pg_subscription WHERE subname = 'regress_sub5'");
 $old_sub->stop;
 
 # Change configuration so that initial table sync does not get started
@@ -393,6 +396,13 @@ $result = $new_sub->safe_psql('postgres',
 	"SELECT xmin IS NOT NULL from pg_replication_slots WHERE slot_name = 'pg_conflict_detection'");
 is($result, qq(t), "conflict detection slot exists");
 
+# The subscription oid and the subscription conflict log table relid should be preserved
+$result = $new_sub->safe_psql('postgres', "SELECT oid FROM pg_subscription WHERE subname = 'regress_sub5'");
+is($result, qq($sub5_oid), "subscription oid should have been preserved");
+
+$result = $new_sub->safe_psql('postgres', "SELECT subconflictlogrelid FROM pg_subscription WHERE subname = 'regress_sub5'");
+is($result, qq($sub_clt_relid), "subscription conflict log table relid should have been preserved");
+
 # Resume the initial sync and wait until all tables of subscription
 # 'regress_sub5' are synchronized
 $new_sub->append_conf('postgresql.conf',
diff --git a/src/include/catalog/binary_upgrade.h b/src/include/catalog/binary_upgrade.h
index 7bf7ae44385..b15b18e7dc9 100644
--- a/src/include/catalog/binary_upgrade.h
+++ b/src/include/catalog/binary_upgrade.h
@@ -32,6 +32,7 @@ extern PGDLLIMPORT RelFileNumber binary_upgrade_next_toast_pg_class_relfilenumbe
 
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_enum_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_authid_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_pg_subscription_oid;
 
 extern PGDLLIMPORT bool binary_upgrade_record_init_privs;
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fa9ae79082b..c9293df27aa 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12024,6 +12024,10 @@
   proname => 'binary_upgrade_create_conflict_detection_slot', proisstrict => 'f',
   provolatile => 'v', proparallel => 'u', prorettype => 'void',
   proargtypes => '', prosrc => 'binary_upgrade_create_conflict_detection_slot' },
+{ oid => '8407', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_pg_subscription_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_pg_subscription_oid' },
 
 # conversion functions
 { oid => '4310', descr => 'internal conversion function for KOI8R to WIN1251',
diff --git a/src/test/modules/spgist_name_ops/expected/spgist_name_ops.out b/src/test/modules/spgist_name_ops/expected/spgist_name_ops.out
index 1ee65ede243..39d43368c42 100644
--- a/src/test/modules/spgist_name_ops/expected/spgist_name_ops.out
+++ b/src/test/modules/spgist_name_ops/expected/spgist_name_ops.out
@@ -59,11 +59,12 @@ select * from t
  binary_upgrade_set_next_multirange_pg_type_oid       |  1 | binary_upgrade_set_next_multirange_pg_type_oid
  binary_upgrade_set_next_pg_authid_oid                |    | binary_upgrade_set_next_pg_authid_oid
  binary_upgrade_set_next_pg_enum_oid                  |    | binary_upgrade_set_next_pg_enum_oid
+ binary_upgrade_set_next_pg_subscription_oid          |    | binary_upgrade_set_next_pg_subscription_oid
  binary_upgrade_set_next_pg_tablespace_oid            |    | binary_upgrade_set_next_pg_tablespace_oid
  binary_upgrade_set_next_pg_type_oid                  |    | binary_upgrade_set_next_pg_type_oid
  binary_upgrade_set_next_toast_pg_class_oid           |  1 | binary_upgrade_set_next_toast_pg_class_oid
  binary_upgrade_set_next_toast_relfilenode            |    | binary_upgrade_set_next_toast_relfilenode
-(13 rows)
+(14 rows)
 
 -- Verify clean failure when INCLUDE'd columns result in overlength tuple
 -- The error message details are platform-dependent, so show only SQLSTATE
@@ -108,11 +109,12 @@ select * from t
  binary_upgrade_set_next_multirange_pg_type_oid       |  1 | binary_upgrade_set_next_multirange_pg_type_oid
  binary_upgrade_set_next_pg_authid_oid                |    | binary_upgrade_set_next_pg_authid_oid
  binary_upgrade_set_next_pg_enum_oid                  |    | binary_upgrade_set_next_pg_enum_oid
+ binary_upgrade_set_next_pg_subscription_oid          |    | binary_upgrade_set_next_pg_subscription_oid
  binary_upgrade_set_next_pg_tablespace_oid            |    | binary_upgrade_set_next_pg_tablespace_oid
  binary_upgrade_set_next_pg_type_oid                  |    | binary_upgrade_set_next_pg_type_oid
  binary_upgrade_set_next_toast_pg_class_oid           |  1 | binary_upgrade_set_next_toast_pg_class_oid
  binary_upgrade_set_next_toast_relfilenode            |    | binary_upgrade_set_next_toast_relfilenode
-(13 rows)
+(14 rows)
 
 \set VERBOSITY sqlstate
 insert into t values(repeat('xyzzy', 12), 42, repeat('xyzzy', 4000));
-- 
2.43.0

