From 0961f835e70d1ee4fa784927e9d9c91f213e5a99 Mon Sep 17 00:00:00 2001
From: Ajin Cherian <itsajin@gmail.com>
Date: Wed, 22 Apr 2026 18:27:45 +1000
Subject: [PATCH v2] Preserve replication origin OIDs in pg_upgrade

Migrate all replication origins during pg_upgrade with special handling
of replication origins of subscribers.
When pg_upgrade migrates a subscriber, the replication origin OIDs
(roident) assigned to subscriptions can change across the upgrade.
This is a problem because commit-timestamp records embed roident and
are copied directly from the old cluster's pg_commit_ts directory,
causing spurious "update_origin_differs" conflicts after the upgrade.

Fix this by skipping replorigin_create() in CreateSubscription() during
binary-upgrade mode, and instead adding a new helper function
binary_upgrade_set_next_replorigin_oid(oid, subname) that creates the
replication origin with the preserved roident from the old cluster.
pg_dump is extended to emit this call before the existing
binary_upgrade_replorigin_advance() in the binary-upgrade script.
---
 src/backend/commands/subscriptioncmds.c    | 10 ++-
 src/backend/replication/logical/origin.c   | 72 ++++++++++++++++++++
 src/backend/utils/adt/pg_upgrade_support.c | 76 ++++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  | 35 ++++++++++
 src/bin/pg_dump/pg_dump.h                  |  1 +
 src/bin/pg_dump/pg_dumpall.c               | 66 +++++++++++++++++++
 src/bin/pg_upgrade/check.c                 | 11 ++--
 src/bin/pg_upgrade/info.c                  | 11 +++-
 src/bin/pg_upgrade/pg_upgrade.h            |  1 +
 src/bin/pg_upgrade/t/004_subscription.pl   | 32 +++++++++
 src/include/catalog/binary_upgrade.h       |  2 +
 src/include/catalog/pg_proc.dat            | 11 ++++
 src/include/replication/origin.h           |  1 +
 13 files changed, 320 insertions(+), 9 deletions(-)

diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index d512e87cfe3..3788671506c 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -846,9 +846,15 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 	 * apply workers initialization, and to handle origin creation dynamically
 	 * when tables are added to the subscription. It is not clear whether
 	 * preventing creation of origins is worth additional complexity.
+	 *
+	 * In binary-upgrade mode, skip origin creation here. This is required to
+	 * preserve the roident from the old cluster for this subscription.
 	 */
-	ReplicationOriginNameForLogicalRep(subid, InvalidOid, originname, sizeof(originname));
-	replorigin_create(originname);
+	if (!IsBinaryUpgrade)
+	{
+		ReplicationOriginNameForLogicalRep(subid, InvalidOid, originname, sizeof(originname));
+		replorigin_create(originname);
+	}
 
 	/*
 	 * Connect to remote side to execute requested commands and fetch table
diff --git a/src/backend/replication/logical/origin.c b/src/backend/replication/logical/origin.c
index c9dfb094c2b..3c5c2246df3 100644
--- a/src/backend/replication/logical/origin.c
+++ b/src/backend/replication/logical/origin.c
@@ -379,6 +379,78 @@ replorigin_create(const char *roname)
 	return roident;
 }
 
+/*
+ * TODO: unify with replorigin_create().
+ *
+ * XXX: Should we put this function in pg_upgrade_support.c, because it's
+ * usable only while upgrading?
+ */
+ReplOriginId
+replorigin_create_with_reploriginid(ReplOriginId node, const char *roname)
+{
+	HeapTuple	tuple = NULL;
+	Relation	rel;
+	Datum		roname_d;
+	SysScanDesc scan;
+	ScanKeyData key;
+	bool		nulls[Natts_pg_replication_origin];
+	Datum		values[Natts_pg_replication_origin];
+	bool		collides;
+
+	Assert(node != InvalidReplOriginId);
+
+	/*
+	 * To avoid needing a TOAST table for pg_replication_origin, we limit
+	 * replication origin names to 512 bytes.  This should be more than enough
+	 * for all practical use.
+	 */
+	if (strlen(roname) > MAX_RONAME_LEN)
+		ereport(ERROR,
+				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+				 errmsg("replication origin name is too long"),
+				 errdetail("Replication origin names must be no longer than %d bytes.",
+						   MAX_RONAME_LEN)));
+
+	roname_d = CStringGetTextDatum(roname);
+
+	Assert(IsTransactionState());
+
+	rel = table_open(ReplicationOriginRelationId, ExclusiveLock);
+
+	Assert(!OidIsValid(rel->rd_rel->reltoastrelid));
+
+	ScanKeyInit(&key,
+				Anum_pg_replication_origin_roident,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(node));
+	scan = systable_beginscan(rel, ReplicationOriginIdentIndex,
+							  true /* indexOK */ ,
+							  SnapshotSelf,
+							  1, &key);
+	collides = HeapTupleIsValid(systable_getnext(scan));
+
+	if (collides)
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("replication origin ID %d already exists", node)));
+
+	systable_endscan(scan);
+
+	memset(&nulls, 0, sizeof(nulls));
+
+	values[Anum_pg_replication_origin_roident - 1] = ObjectIdGetDatum(node);
+	values[Anum_pg_replication_origin_roname - 1] = roname_d;
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+	CatalogTupleInsert(rel, tuple);
+	CommandCounterIncrement();
+
+	table_close(rel, ExclusiveLock);
+
+	heap_freetuple(tuple);
+	return node;
+}
+
 /*
  * Helper function to drop a replication origin.
  */
diff --git a/src/backend/utils/adt/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c
index b505a6b4fee..06cb1465b20 100644
--- a/src/backend/utils/adt/pg_upgrade_support.c
+++ b/src/backend/utils/adt/pg_upgrade_support.c
@@ -28,6 +28,7 @@
 #include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/syscache.h"
 #include "utils/pg_lsn.h"
 
 
@@ -181,6 +182,48 @@ binary_upgrade_set_next_pg_authid_oid(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+/*
+ * binary_upgrade_set_next_replorigin_oid
+ *
+ * Create the replication origin with the oid specified, preserving the
+ * origin id of the subscription from the old cluster.
+ * This must be called before binary_upgrade_replorigin_advance() for the
+ * same subscription. CreateSubscription() skips replorigin_create() in
+ * binary upgrade mode specifically to allow this function to control the
+ * roident.
+ */
+Datum
+binary_upgrade_set_next_replorigin_oid(PG_FUNCTION_ARGS)
+{
+	ReplOriginId next_oid;
+	char	   *subname;
+	Oid			subid;
+	char		originname[NAMEDATALEN];
+
+	CHECK_IS_BINARY_UPGRADE;
+
+	if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
+		elog(ERROR,
+			 "null argument to binary_upgrade_set_next_replorigin_oid is not allowed");
+
+	next_oid = (ReplOriginId) PG_GETARG_OID(0);
+	subname = text_to_cstring(PG_GETARG_TEXT_PP(1));
+
+	if (next_oid == InvalidReplOriginId)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("replication origin OID must be valid")));
+
+	subid = get_subscription_oid(subname, false);
+	ReplicationOriginNameForLogicalRep(subid, InvalidOid, originname,
+									   sizeof(originname));
+
+	/* Create the origin with the preserved OID from the old cluster */
+	replorigin_create_with_reploriginid(next_oid, originname);
+
+	PG_RETURN_VOID();
+}
+
 Datum
 binary_upgrade_create_empty_extension(PG_FUNCTION_ARGS)
 {
@@ -435,3 +478,36 @@ binary_upgrade_create_conflict_detection_slot(PG_FUNCTION_ARGS)
 
 	PG_RETURN_VOID();
 }
+
+Datum
+binary_upgrade_create_replication_origin(PG_FUNCTION_ARGS)
+{
+	ReplOriginId	node;
+	Name			originname;
+
+	CHECK_IS_BINARY_UPGRADE;
+
+	/* NULL node id and origin name are not allowed */
+	if (PG_ARGISNULL(0) ||
+		PG_ARGISNULL(1))
+		elog(ERROR, "null argument to binary_upgrade_create_replication_origin is not allowed");
+
+	node = PG_GETARG_OID(0);
+	originname = PG_GETARG_NAME(1);
+
+	replorigin_create_with_reploriginid(node, NameStr(*originname));
+
+	if (!PG_ARGISNULL(2))
+	{
+		XLogRecPtr		remote_commit = PG_GETARG_LSN(2);
+
+		/* Lock to prevent the replication origin from vanishing */
+		LockRelationOid(ReplicationOriginRelationId, RowExclusiveLock);
+		replorigin_advance(node, remote_commit, InvalidXLogRecPtr,
+						   false /* backward */ ,
+						   false /* WAL log */ );
+		UnlockRelationOid(ReplicationOriginRelationId, RowExclusiveLock);
+	}
+
+	PG_RETURN_VOID();
+}
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index d56dcc701ce..94699b60677 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5180,6 +5180,7 @@ getSubscriptions(Archive *fout)
 	int			i_subpublications;
 	int			i_suborigin;
 	int			i_suboriginremotelsn;
+	int			i_suboriginoid;
 	int			i_subenabled;
 	int			i_subfailover;
 	int			i_subretaindeadtuples;
@@ -5254,6 +5255,11 @@ getSubscriptions(Archive *fout)
 		appendPQExpBufferStr(query, " NULL AS suboriginremotelsn,\n"
 							 " false AS subenabled,\n");
 
+	if (dopt->binary_upgrade && fout->remoteVersion >= 190000)
+		appendPQExpBufferStr(query, " ro.roident AS suboriginoid,\n");
+	else
+		appendPQExpBufferStr(query, " NULL AS suboriginoid,\n");
+
 	if (fout->remoteVersion >= 170000)
 		appendPQExpBufferStr(query,
 							 " s.subfailover,\n");
@@ -5299,6 +5305,11 @@ getSubscriptions(Archive *fout)
 							 "LEFT JOIN pg_catalog.pg_replication_origin_status o \n"
 							 "    ON o.external_id = 'pg_' || s.oid::text \n");
 
+	if (dopt->binary_upgrade && fout->remoteVersion >= 190000)
+		appendPQExpBufferStr(query,
+							 "LEFT JOIN pg_catalog.pg_replication_origin ro \n"
+							 "    ON ro.roname = 'pg_' || s.oid::text \n");
+
 	appendPQExpBufferStr(query,
 						 "WHERE s.subdbid = (SELECT oid FROM pg_database\n"
 						 "                   WHERE datname = current_database())");
@@ -5333,6 +5344,7 @@ getSubscriptions(Archive *fout)
 	i_subpublications = PQfnumber(res, "subpublications");
 	i_suborigin = PQfnumber(res, "suborigin");
 	i_suboriginremotelsn = PQfnumber(res, "suboriginremotelsn");
+	i_suboriginoid = PQfnumber(res, "suboriginoid");
 
 	subinfo = pg_malloc_array(SubscriptionInfo, ntups);
 
@@ -5390,6 +5402,11 @@ getSubscriptions(Archive *fout)
 		else
 			subinfo[i].suboriginremotelsn =
 				pg_strdup(PQgetvalue(res, i, i_suboriginremotelsn));
+		if (PQgetisnull(res, i, i_suboriginoid))
+			subinfo[i].suboriginoid = NULL;
+		else
+			subinfo[i].suboriginoid =
+				pg_strdup(PQgetvalue(res, i, i_suboriginoid));
 
 		/* Decide whether we want to dump it */
 		selectDumpableObject(&(subinfo[i].dobj), fout);
@@ -5662,6 +5679,23 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
 	 */
 	if (dopt->binary_upgrade && fout->remoteVersion >= 170000)
 	{
+		/*
+		 * Pin the replication origin OID and create the origin.
+		 * CreateSubscription() skips replorigin_create() in binary upgrade
+		 * mode to allow us to control the roident and is preserved after
+		 * upgrade.
+		 */
+		if (subinfo->suboriginoid)
+		{
+			appendPQExpBufferStr(query,
+								"\n-- For binary upgrade, must preserve the replication origin OID.\n");
+			appendPQExpBufferStr(query,
+								"SELECT pg_catalog.binary_upgrade_set_next_replorigin_oid(");
+			appendPQExpBuffer(query, "%s::pg_catalog.oid, ", subinfo->suboriginoid);
+			appendStringLiteralAH(query, subinfo->dobj.name, fout);
+			appendPQExpBufferStr(query, ");\n");
+		}
+
 		if (subinfo->suboriginremotelsn)
 		{
 			/*
@@ -5675,6 +5709,7 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
 			 */
 			appendPQExpBufferStr(query,
 								 "\n-- For binary upgrade, must preserve the remote_lsn for the subscriber's replication origin.\n");
+
 			appendPQExpBufferStr(query,
 								 "SELECT pg_catalog.binary_upgrade_replorigin_advance(");
 			appendStringLiteralAH(query, subinfo->dobj.name, fout);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 5a6726d8b12..71cc66d75bc 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -730,6 +730,7 @@ typedef struct _SubscriptionInfo
 	char	   *subpublications;
 	char	   *suborigin;
 	char	   *suboriginremotelsn;
+	char	   *suboriginoid;
 } SubscriptionInfo;
 
 /*
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 9e904f76baa..9f6498948f8 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -25,6 +25,7 @@
 #include <time.h>
 #include <unistd.h>
 
+#include "access/xlogdefs.h"
 #include "catalog/pg_authid_d.h"
 #include "common/connect.h"
 #include "common/file_perm.h"
@@ -76,6 +77,7 @@ static void dropDBs(PGconn *conn);
 static void dumpUserConfig(PGconn *conn, const char *username);
 static void dumpDatabases(PGconn *conn);
 static void dumpTimestamp(const char *msg);
+static void dumpReplicationOrigins(PGconn *conn);
 static int	runPgDump(const char *dbname, const char *create_opts, char *dbfile);
 static void buildShSecLabels(PGconn *conn,
 							 const char *catalog_name, Oid objectId,
@@ -813,6 +815,10 @@ main(int argc, char *argv[])
 			/* Dump role GUC privileges */
 			if (server_version >= 150000 && !skip_acls)
 				dumpRoleGUCPrivs(conn);
+
+			/* Dump replication origins */
+			if (server_version >= 190000 && binary_upgrade && archDumpFormat == archNull)
+				dumpReplicationOrigins(conn);
 		}
 
 		/* Dump tablespaces */
@@ -2339,6 +2345,66 @@ dumpTimestamp(const char *msg)
 		fprintf(OPF, "-- %s %s\n\n", msg, buf);
 }
 
+static void
+dumpReplicationOrigins(PGconn *conn)
+{
+	PQExpBuffer buf = createPQExpBuffer();
+	PGresult   *res;
+	int			i_roident;
+	int			i_roname;
+	int			i_remotelsn;
+
+	/* Get replication origins in current database. */
+	appendPQExpBufferStr(buf,
+						 "SELECT o.*, os.remote_lsn "
+						 "FROM pg_catalog.pg_replication_origin o "
+						 "LEFT OUTER JOIN pg_catalog.pg_replication_origin_status os ON o.roident = os.local_id "
+						 "WHERE NOT EXISTS ("
+						 "    SELECT 1 FROM pg_catalog.pg_subscription s"
+						 "    WHERE o.roname = 'pg_' || s.oid::text);");
+
+	res = executeQuery(conn, buf->data);
+
+	i_roident = PQfnumber(res, "roident");
+	i_roname = PQfnumber(res, "roname");
+	i_remotelsn = PQfnumber(res, "remote_lsn");
+
+	if (PQntuples(res) > 0 && archDumpFormat == archNull)
+		fprintf(OPF, "--\n-- Replication Origins \n--\n\n");
+
+	for (int i = 0; i < PQntuples(res); i++)
+	{
+		ReplOriginId roident;
+		const char *roname;
+
+		roident = atooid(PQgetvalue(res, i, i_roident));
+		roname = PQgetvalue(res, i, i_roname);
+
+		resetPQExpBuffer(buf);
+
+		appendPQExpBufferStr(buf, "\n-- For binary upgrade, must preserve replication origin roident and remote_lsn\n");
+		appendPQExpBuffer(buf,
+						  "SELECT pg_catalog.binary_upgrade_create_replication_origin('%u'::pg_catalog.oid, '%s'::pg_catalog.text",
+						  roident, roname);
+
+		if (!PQgetisnull(res, i, i_remotelsn))
+		{
+			appendPQExpBuffer(buf, ", '%s'::pg_catalog.pg_lsn",
+							  PQgetvalue(res, i, i_remotelsn));
+		}
+		else
+			appendPQExpBufferStr(buf, ", NULL");
+
+		appendPQExpBuffer(buf, ");\n");
+
+		fprintf(OPF, "%s", buf->data);
+	}
+
+	PQclear(res);
+	destroyPQExpBuffer(buf);
+}
+
+
 /*
  * read_dumpall_filters - retrieve database identifier patterns from file
  *
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 5a7afe62eab..5180c281dcc 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -2306,8 +2306,7 @@ check_new_cluster_replication_slots(void)
  * check_new_cluster_subscription_configuration()
  *
  * Verify that the max_active_replication_origins configuration specified is
- * enough for creating the subscriptions. This is required to create the
- * replication origin for each subscription.
+ * enough for creating all the replication origins.
  */
 static void
 check_new_cluster_subscription_configuration(void)
@@ -2320,8 +2319,8 @@ check_new_cluster_subscription_configuration(void)
 	if (GET_MAJOR_VERSION(old_cluster.major_version) < 1700)
 		return;
 
-	/* Quick return if there are no subscriptions to be migrated. */
-	if (old_cluster.nsubs == 0)
+	/* Quick return if there are no origins to be migrated. */
+	if (old_cluster.nrepl_origins == 0)
 		return;
 
 	prep_status("Checking for new cluster configuration for subscriptions");
@@ -2335,10 +2334,10 @@ check_new_cluster_subscription_configuration(void)
 		pg_fatal("could not determine parameter settings on new cluster");
 
 	max_active_replication_origins = atoi(PQgetvalue(res, 0, 0));
-	if (old_cluster.nsubs > max_active_replication_origins)
+	if (old_cluster.nrepl_origins > max_active_replication_origins)
 		pg_fatal("\"max_active_replication_origins\" (%d) must be greater than or equal to the number of "
 				 "subscriptions (%d) on the old cluster",
-				 max_active_replication_origins, old_cluster.nsubs);
+				 max_active_replication_origins, old_cluster.nrepl_origins);
 
 	PQclear(res);
 	PQfinish(conn);
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 8c5679b8097..f9359bd214b 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -834,7 +834,7 @@ count_old_cluster_logical_slots(void)
 /*
  * get_subscription_info()
  *
- * Gets the information of subscriptions in the cluster.
+ * Gets the information of subscriptions and replication origins in the cluster.
  */
 void
 get_subscription_info(ClusterInfo *cluster)
@@ -842,6 +842,7 @@ get_subscription_info(ClusterInfo *cluster)
 	PGconn	   *conn;
 	PGresult   *res;
 	int			i_nsub;
+	int			i_nrepl_origins;
 	int			i_retain_dead_tuples;
 
 	conn = connectToServer(cluster, "template1");
@@ -861,6 +862,14 @@ get_subscription_info(ClusterInfo *cluster)
 	cluster->sub_retain_dead_tuples = (strcmp(PQgetvalue(res, 0, i_retain_dead_tuples), "t") == 0);
 
 	PQclear(res);
+
+	res = executeQueryOrDie(conn,
+							"SELECT count(*) AS nrepl_origins "
+							"FROM pg_catalog.pg_replication_origin");
+	i_nrepl_origins = PQfnumber(res, "nrepl_origins");
+	cluster->nrepl_origins = atoi(PQgetvalue(res, 0, i_nrepl_origins));
+	PQclear(res);
+
 	PQfinish(conn);
 }
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 1d767bbda2d..1d15700a886 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -311,6 +311,7 @@ typedef struct
 	int			num_tablespaces;
 	const char *tablespace_suffix;	/* directory specification */
 	int			nsubs;			/* number of subscriptions */
+	int			nrepl_origins;	/* number of replication origins */
 	bool		sub_retain_dead_tuples; /* whether a subscription enables
 										 * retain_dead_tuples. */
 } ClusterInfo;
diff --git a/src/bin/pg_upgrade/t/004_subscription.pl b/src/bin/pg_upgrade/t/004_subscription.pl
index f68821df2a3..43322fb0596 100644
--- a/src/bin/pg_upgrade/t/004_subscription.pl
+++ b/src/bin/pg_upgrade/t/004_subscription.pl
@@ -303,6 +303,24 @@ is($result, qq(t), "Check that the table is in init state");
 my $remote_lsn = $old_sub->safe_psql('postgres',
 	"SELECT remote_lsn FROM pg_replication_origin_status os, pg_subscription s WHERE os.external_id = 'pg_' || s.oid AND s.subname = 'regress_sub4'"
 );
+
+# Get the replication origin OIDs (roident) for all subscriptions, keyed by
+# subscription name (which is stable across upgrade, unlike suboid). These
+# must be preserved after upgrade. A mismatch would cause spurious
+# update_origin_differs conflicts.
+my %pre_upgrade_roident;
+my $roident_rows = $old_sub->safe_psql('postgres',
+    "SELECT s.subname, o.roident
+     FROM pg_subscription s
+     JOIN pg_replication_origin o ON o.roname = 'pg_' || s.oid::text
+     ORDER BY s.subname"
+);
+for my $row (split /\n/, $roident_rows)
+{
+    my ($subname, $roident) = split /\|/, $row;
+    $pre_upgrade_roident{$subname} = $roident;
+}
+
 # Have the subscription in disabled state before upgrade
 $old_sub->safe_psql('postgres', "ALTER SUBSCRIPTION regress_sub5 DISABLE");
 
@@ -371,6 +389,20 @@ regress_sub5|f|f|f),
 	"check that the subscription's running status, failover, and retain_dead_tuples are preserved"
 );
 
+# Verify that replication origin OIDs are preserved after upgrade.
+my $post_roident_rows = $new_sub->safe_psql('postgres',
+    "SELECT s.subname, o.roident
+     FROM pg_subscription s
+     JOIN pg_replication_origin o ON o.roname = 'pg_' || s.oid::text
+     ORDER BY s.subname"
+);
+for my $row (split /\n/, $post_roident_rows)
+{
+    my ($subname, $roident) = split /\|/, $row;
+    is($roident, $pre_upgrade_roident{$subname},
+        "roident preserved for subscription '$subname' after upgrade");
+}
+
 # Subscription relations should be preserved
 $result = $new_sub->safe_psql('postgres',
 	"SELECT srrelid, srsubstate FROM pg_subscription_rel ORDER BY srrelid");
diff --git a/src/include/catalog/binary_upgrade.h b/src/include/catalog/binary_upgrade.h
index 7bf7ae44385..27f6f0befff 100644
--- a/src/include/catalog/binary_upgrade.h
+++ b/src/include/catalog/binary_upgrade.h
@@ -14,8 +14,10 @@
 #ifndef BINARY_UPGRADE_H
 #define BINARY_UPGRADE_H
 
+#include "access/xlogdefs.h"
 #include "common/relpath.h"
 
+
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_tablespace_oid;
 
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fa9ae79082b..392ca481f46 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12024,6 +12024,17 @@
   proname => 'binary_upgrade_create_conflict_detection_slot', proisstrict => 'f',
   provolatile => 'v', proparallel => 'u', prorettype => 'void',
   proargtypes => '', prosrc => 'binary_upgrade_create_conflict_detection_slot' },
+{ oid => '9160', descr => 'for use by pg_upgrade (replication origin)',
+  proname => 'binary_upgrade_create_replication_origin', proisstrict => 'f',
+  provolatile => 'v', proparallel => 'u', prorettype => 'void',
+  proargtypes => 'oid name pg_lsn', prosrc => 'binary_upgrade_create_replication_origin' },
+{ oid => '9161',
+  descr => 'for use by pg_upgrade only: set next replication origin OID and create origin',
+  proname => 'binary_upgrade_set_next_replorigin_oid',
+  provolatile => 'v', proretset => 'f', prosecdef => 'f',
+  proisstrict => 'f', prorettype => 'void',
+  proargtypes => 'oid text',
+  prosrc => 'binary_upgrade_set_next_replorigin_oid' },
 
 # conversion functions
 { oid => '4310', descr => 'internal conversion function for KOI8R to WIN1251',
diff --git a/src/include/replication/origin.h b/src/include/replication/origin.h
index a69faf6eaaf..8f6b0d0635b 100644
--- a/src/include/replication/origin.h
+++ b/src/include/replication/origin.h
@@ -55,6 +55,7 @@ extern PGDLLIMPORT int max_active_replication_origins;
 /* API for querying & manipulating replication origins */
 extern ReplOriginId replorigin_by_name(const char *roname, bool missing_ok);
 extern ReplOriginId replorigin_create(const char *roname);
+extern ReplOriginId replorigin_create_with_reploriginid(ReplOriginId node, const char *roname);
 extern void replorigin_drop_by_name(const char *name, bool missing_ok, bool nowait);
 extern bool replorigin_by_oid(ReplOriginId roident, bool missing_ok,
 							  char **roname);
-- 
2.47.3

