From b79f707ab3f41de37f1ff4552b7a669d229eba7a Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 14 Jul 2026 12:22:39 +0530
Subject: [PATCH v3 2/3] Reject changing subscriptions with sequences to
 pre-PG19 publishers

Sequence synchronization relies on pg_get_sequence_data(), which is
available only on PostgreSQL 19 and later. Changing an existing
subscription that contains replicated sequences to a pre-PostgreSQL 19
publisher using ALTER SUBSCRIPTION ... CONNECTION or
ALTER SUBSCRIPTION ... SERVER would leave sequence synchronization
unsupported.

Prevent this by checking the publisher version whenever the connection
or server of a subscription containing replicated sequences is changed.
If the new publisher is older than PostgreSQL 19, report an error.

Also document that sequence replication requires a PostgreSQL 19 or
later publisher, including in the documentation.
---
 doc/src/sgml/logical-replication.sgml     | 18 ++++++++-
 doc/src/sgml/ref/alter_subscription.sgml  | 16 ++++++++
 src/backend/catalog/pg_subscription.c     | 45 +++++++++++++++++++++++
 src/backend/commands/subscriptioncmds.c   | 45 +++++++++++++++++++++--
 src/include/catalog/pg_subscription_rel.h |  1 +
 5 files changed, 120 insertions(+), 5 deletions(-)

diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 690598bff98..274ba8b447a 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -1772,6 +1772,13 @@ Included in publications:
  <sect1 id="logical-replication-sequences">
   <title>Replicating Sequences</title>
 
+  <note>
+   <para>
+    Sequence synchronization requires the publisher to be running
+    <productname>PostgreSQL</productname> 19 or later.
+   </para>
+  </note>
+
   <para>
    To synchronize sequences from a publisher to a subscriber, first publish
    them using <link linkend="sql-createpublication-params-for-all-sequences">
@@ -2368,7 +2375,16 @@ CONTEXT:  processing remote data for replication origin "pg_16395" during "INSER
      <command>ALTER SUBSCRIPTION ... REFRESH SEQUENCES</command></link>
      or by copying the current data from the publisher (perhaps using
      <command>pg_dump</command>) or by determining a sufficiently high value
-     from the tables themselves.
+     from the tables themselves.  Note that
+     <link linkend="sql-altersubscription-params-refresh-sequences">
+     <command>ALTER SUBSCRIPTION ... REFRESH SEQUENCES</command></link> only
+     re-synchronizes sequences that are already known to the subscription
+     (see <xref linkend="logical-replication-sequences"/>); in particular, it
+     requires the publisher to be running <productname>PostgreSQL</productname>
+     19 or later. Before relying on it to prepare for a switchover or
+     failover, confirm that the publisher's version supports sequence
+     replication and that the sequences of interest are already known to the
+     subscription.
     </para>
    </listitem>
 
diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml
index 8d64744375a..f81792918f3 100644
--- a/doc/src/sgml/ref/alter_subscription.sgml
+++ b/doc/src/sgml/ref/alter_subscription.sgml
@@ -111,6 +111,14 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
       set by <xref linkend="sql-createsubscription"/> with the foreign server
       <replaceable>servername</replaceable>.
      </para>
+     <note>
+      <para>
+       If the subscription already has sequences, the new publisher must be
+       running <productname>PostgreSQL</productname> 19 or later. Earlier
+       <productname>PostgreSQL</productname> versions do not support sequence
+       synchronization.
+      </para>
+     </note>
     </listitem>
    </varlistentry>
 
@@ -122,6 +130,14 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
       set by <xref linkend="sql-createsubscription"/> with the connection
       string <replaceable>conninfo</replaceable>.
      </para>
+     <note>
+      <para>
+       If the subscription already has sequences, the new publisher must be
+       running <productname>PostgreSQL</productname> 19 or later. Earlier
+       <productname>PostgreSQL</productname> versions do not support sequence
+       synchronization.
+      </para>
+     </note>
     </listitem>
    </varlistentry>
 
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index 2068e03c571..fea9625aba2 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -614,6 +614,51 @@ HasSubscriptionTables(Oid subid)
 	return has_subtables;
 }
 
+/*
+ * Does the subscription have any sequences?
+ *
+ * Use this function only to know true/false, and when you have no need for
+ * the List returned by GetSubscriptionRelations.
+ */
+bool
+HasSubscriptionSequences(Oid subid)
+{
+	Relation	rel;
+	ScanKeyData skey[1];
+	SysScanDesc scan;
+	HeapTuple	tup;
+	bool		has_subsequences = false;
+
+	rel = table_open(SubscriptionRelRelationId, AccessShareLock);
+
+	ScanKeyInit(&skey[0],
+				Anum_pg_subscription_rel_srsubid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(subid));
+
+	scan = systable_beginscan(rel, InvalidOid, false,
+							  NULL, 1, skey);
+
+	while (HeapTupleIsValid(tup = systable_getnext(scan)))
+	{
+		Form_pg_subscription_rel subrel;
+
+		subrel = (Form_pg_subscription_rel) GETSTRUCT(tup);
+
+		if (get_rel_relkind(subrel->srrelid) == RELKIND_SEQUENCE)
+		{
+			has_subsequences = true;
+			break;
+		}
+	}
+
+	/* Cleanup */
+	systable_endscan(scan);
+	table_close(rel, AccessShareLock);
+
+	return has_subsequences;
+}
+
 /*
  * Get the relations for the subscription.
  *
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 48f4ff05d83..85da5e18971 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -139,6 +139,8 @@ static void check_publications_origin_sequences(WalReceiverConn *wrconn,
 												int subrel_count,
 												char *subname);
 static void check_pub_dead_tuple_retention(WalReceiverConn *wrconn);
+static void check_pub_sequence_sync_support(WalReceiverConn *wrconn,
+											char *subname);
 static void check_duplicates_in_publist(List *publist, Datum *datums);
 static List *merge_publications(List *oldpublist, List *newpublist, bool addpub, const char *subname);
 static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err);
@@ -1606,6 +1608,7 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 	bool		update_failover = false;
 	bool		update_two_phase = false;
 	bool		check_pub_rdt = false;
+	bool		check_pub_seq = false;
 	bool		retain_dead_tuples;
 	int			max_retention;
 	bool		retention_active;
@@ -2142,6 +2145,12 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 			 * retain_dead_tuples.
 			 */
 			check_pub_rdt = sub->retaindeadtuples;
+
+			/*
+			 * If the subscription already has sequences, ensure the new
+			 * publisher is new enough to synchronize them.
+			 */
+			check_pub_seq = HasSubscriptionSequences(subid);
 			break;
 
 		case ALTER_SUBSCRIPTION_CONNECTION:
@@ -2175,6 +2184,12 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 			 * retain_dead_tuples.
 			 */
 			check_pub_rdt = sub->retaindeadtuples;
+
+			/*
+			 * If the subscription already has sequences, ensure the new
+			 * publisher is new enough to synchronize them.
+			 */
+			check_pub_seq = HasSubscriptionSequences(subid);
 			break;
 
 		case ALTER_SUBSCRIPTION_SET_PUBLICATION:
@@ -2375,15 +2390,16 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 	}
 
 	/*
-	 * Try to acquire the connection necessary either for modifying the slot
-	 * or for checking if the remote server permits enabling
-	 * retain_dead_tuples.
+	 * Try to acquire the connection necessary either for modifying the slot,
+	 * for checking if the remote server permits enabling retain_dead_tuples,
+	 * or for checking if it is new enough to synchronize the subscription's
+	 * sequences.
 	 *
 	 * This has to be at the end because otherwise if there is an error while
 	 * doing the database operations we won't be able to rollback altered
 	 * slot.
 	 */
-	if (update_failover || update_two_phase || check_pub_rdt)
+	if (update_failover || update_two_phase || check_pub_rdt || check_pub_seq)
 	{
 		bool		must_use_password;
 		char	   *err;
@@ -2413,6 +2429,9 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 			if (retain_dead_tuples)
 				check_pub_dead_tuple_retention(wrconn);
 
+			if (check_pub_seq)
+				check_pub_sequence_sync_support(wrconn, sub->name);
+
 			check_publications_origin_tables(wrconn, sub->publications, false,
 											 retain_dead_tuples, origin, NULL, 0,
 											 sub->name);
@@ -3356,6 +3375,24 @@ check_pub_dead_tuple_retention(WalReceiverConn *wrconn)
 	walrcv_clear_result(res);
 }
 
+/*
+ * Check that the publisher is new enough to synchronize sequences.
+ *
+ * Sequence synchronization relies on pg_get_sequence_data(), which is only
+ * available since PostgreSQL 19.  Called when the subscription's connection
+ * or server changes and the subscription already has sequences, so that an
+ * incompatible publisher is rejected immediately rather than only being
+ * discovered later when a sequencesync worker tries and fails to run.
+ */
+static void
+check_pub_sequence_sync_support(WalReceiverConn *wrconn, char *subname)
+{
+	if (walrcv_server_version(wrconn) < 190000)
+		ereport(ERROR,
+				errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				errmsg("cannot synchronize sequences if the publisher is running a version earlier than PostgreSQL 19"));
+}
+
 /*
  * Check if the subscriber's configuration is adequate to enable the
  * retain_dead_tuples option.
diff --git a/src/include/catalog/pg_subscription_rel.h b/src/include/catalog/pg_subscription_rel.h
index 502640d3018..f5f1287b1f5 100644
--- a/src/include/catalog/pg_subscription_rel.h
+++ b/src/include/catalog/pg_subscription_rel.h
@@ -117,6 +117,7 @@ extern char GetSubscriptionRelState(Oid subid, Oid relid, XLogRecPtr *sublsn);
 extern void RemoveSubscriptionRel(Oid subid, Oid relid);
 
 extern bool HasSubscriptionTables(Oid subid);
+extern bool HasSubscriptionSequences(Oid subid);
 extern List *GetSubscriptionRelations(Oid subid, bool tables, bool sequences,
 									  bool not_ready);
 
-- 
2.50.1 (Apple Git-155)

