From 43b8a1aa5daaa495c9d6423a5604090431a7392e Mon Sep 17 00:00:00 2001
From: Zhijie Hou <houzj.fnst@fujitsu.com>
Date: Wed, 15 Oct 2025 16:57:15 +0800
Subject: [PATCH v20251016] Introduce "REFRESH SEQUENCES" for subscriptions

This patch adds support for a new SQL command:
ALTER SUBSCRIPTION ... REFRESH SEQUENCES
This command updates the sequence entries present in the
pg_subscription_rel catalog table with the INIT state to trigger
resynchronization.

Additionally, the following subscription commands:
ALTER SUBSCRIPTION ... REFRESH PUBLICATION
ALTER SUBSCRIPTION ... ADD PUBLICATION
ALTER SUBSCRIPTION ... DROP PUBLICATION
ALTER SUBSCRIPTION ... SET PUBLICATION
have been extended to also refresh sequence mappings. These commands will:
Add newly published sequences that are not yet part of the subscription.
Remove sequences that are no longer included in the publication.

This ensures that sequence replication remains aligned with the current
state of the publication on the publisher side, improving consistency
and reducing manual maintenance.

Author: Vignesh C, Tomas Vondra
Reviewer:  Amit Kapila, Shveta Malik, Dilip Kumar, Peter Smith, Nisha Moond
Discussion: https://www.postgresql.org/message-id/CAA4eK1LC+KJiAkSrpE_NwvNdidw9F2os7GERUeSxSKv71gXysQ@mail.gmail.com
---
 doc/src/sgml/catalogs.sgml                  |   2 +-
 src/backend/catalog/pg_subscription.c       |  63 ++-
 src/backend/commands/subscriptioncmds.c     | 418 ++++++++++++++------
 src/backend/executor/execReplication.c      |  27 +-
 src/backend/parser/gram.y                   |   9 +
 src/backend/replication/logical/relation.c  |   1 +
 src/backend/replication/logical/syncutils.c |   3 +-
 src/backend/replication/logical/tablesync.c |   2 +-
 src/backend/replication/logical/worker.c    |   2 +
 src/backend/replication/pgoutput/pgoutput.c |   6 +-
 src/bin/psql/tab-complete.in.c              |  10 +-
 src/include/catalog/pg_subscription_rel.h   |   3 +-
 src/include/executor/executor.h             |   4 +-
 src/include/nodes/parsenodes.h              |   1 +
 src/tools/pgindent/typedefs.list            |   1 +
 15 files changed, 399 insertions(+), 153 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 9b3aae8603b..51534a6f88c 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -8205,7 +8205,7 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
   </para>
 
   <para>
-   This catalog only contains tables known to the subscription after running
+   This catalog contains tables and sequences known to the subscription after running
    either <link linkend="sql-createsubscription"><command>CREATE SUBSCRIPTION</command></link> or
    <link linkend="sql-altersubscription"><command>ALTER SUBSCRIPTION ... REFRESH
    PUBLICATION</command></link>.
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index e06587b0265..c615005c923 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -284,7 +284,7 @@ AddSubscriptionRelState(Oid subid, Oid relid, char state,
 							  ObjectIdGetDatum(relid),
 							  ObjectIdGetDatum(subid));
 	if (HeapTupleIsValid(tup))
-		elog(ERROR, "subscription table %u in subscription %u already exists",
+		elog(ERROR, "subscription relation %u in subscription %u already exists",
 			 relid, subid);
 
 	/* Form the tuple. */
@@ -480,7 +480,9 @@ RemoveSubscriptionRel(Oid subid, Oid relid)
 		 * leave tablesync slots or origins in the system when the
 		 * corresponding table is dropped.
 		 */
-		if (!OidIsValid(subid) && subrel->srsubstate != SUBREL_STATE_READY)
+		if (!OidIsValid(subid) &&
+			get_rel_relkind(subrel->srrelid) != RELKIND_SEQUENCE &&
+			subrel->srsubstate != SUBREL_STATE_READY)
 		{
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -517,7 +519,8 @@ HasSubscriptionTables(Oid subid)
 	Relation	rel;
 	ScanKeyData skey[1];
 	SysScanDesc scan;
-	bool		has_subrels;
+	HeapTuple	tup;
+	bool		has_subrels = false;
 
 	rel = table_open(SubscriptionRelRelationId, AccessShareLock);
 
@@ -529,8 +532,23 @@ HasSubscriptionTables(Oid subid)
 	scan = systable_beginscan(rel, InvalidOid, false,
 							  NULL, 1, skey);
 
-	/* If even a single tuple exists then the subscription has tables. */
-	has_subrels = HeapTupleIsValid(systable_getnext(scan));
+	while (HeapTupleIsValid(tup = systable_getnext(scan)))
+	{
+		Form_pg_subscription_rel subrel;
+
+		subrel = (Form_pg_subscription_rel) GETSTRUCT(tup);
+
+		/*
+		 * Skip sequence tuples. If even a single table tuple exists then the
+		 * subscription has tables.
+		 */
+		if (get_rel_relkind(subrel->srrelid) == RELKIND_RELATION ||
+			get_rel_relkind(subrel->srrelid) == RELKIND_PARTITIONED_TABLE)
+		{
+			has_subrels = true;
+			break;
+		}
+	}
 
 	/* Cleanup */
 	systable_endscan(scan);
@@ -542,12 +560,21 @@ HasSubscriptionTables(Oid subid)
 /*
  * Get the relations for the subscription.
  *
- * If not_ready is true, return only the relations that are not in a ready
- * state, otherwise return all the relations of the subscription.  The
- * returned list is palloc'ed in the current memory context.
+ * get_tables: get relations for tables of the subscription.
+ *
+ * get_sequences: get relations for sequences of the subscription.
+ *
+ * not_ready:
+ * If getting tables and not_ready is false, retrieve all tables;
+ * otherwise, retrieve only tables that have not reached the READY state.
+ * If getting sequences and not_ready is false, retrieve all sequences;
+ * otherwise, retrieve only sequences that have not reached the READY state.
+ *
+ * The returned list is palloc'ed in the current memory context.
  */
 List *
-GetSubscriptionRelations(Oid subid, bool not_ready)
+GetSubscriptionRelations(Oid subid, bool get_tables, bool get_sequences,
+						 bool not_ready)
 {
 	List	   *res = NIL;
 	Relation	rel;
@@ -556,6 +583,9 @@ GetSubscriptionRelations(Oid subid, bool not_ready)
 	ScanKeyData skey[2];
 	SysScanDesc scan;
 
+	/* One or both of 'get_tables' and 'get_sequences' must be true. */
+	Assert(get_tables || get_sequences);
+
 	rel = table_open(SubscriptionRelRelationId, AccessShareLock);
 
 	ScanKeyInit(&skey[nkeys++],
@@ -578,9 +608,24 @@ GetSubscriptionRelations(Oid subid, bool not_ready)
 		SubscriptionRelState *relstate;
 		Datum		d;
 		bool		isnull;
+		char		relkind;
 
 		subrel = (Form_pg_subscription_rel) GETSTRUCT(tup);
 
+		/* Relation is either a sequence or a table */
+		relkind = get_rel_relkind(subrel->srrelid);
+		Assert(relkind == RELKIND_SEQUENCE || relkind == RELKIND_RELATION ||
+			   relkind == RELKIND_PARTITIONED_TABLE);
+
+		/* Skip sequences if they were not requested */
+		if ((relkind == RELKIND_SEQUENCE) && !get_sequences)
+			continue;
+
+		/* Skip tables if they were not requested */
+		if ((relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE)
+			&& !get_tables)
+			continue;
+
 		relstate = (SubscriptionRelState *) palloc(sizeof(SubscriptionRelState));
 		relstate->relid = subrel->srrelid;
 		relstate->state = subrel->srsubstate;
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 0f54686b699..174c930e4bb 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -106,12 +106,18 @@ typedef struct SubOpts
 	XLogRecPtr	lsn;
 } SubOpts;
 
-static List *fetch_table_list(WalReceiverConn *wrconn, List *publications);
+typedef struct PublicationRelKind
+{
+	RangeVar   *rv;
+	char		relkind;
+} PublicationRelKind;
+
+static List *fetch_relation_list(WalReceiverConn *wrconn, List *publications);
 static void check_publications_origin(WalReceiverConn *wrconn,
 									  List *publications, bool copydata,
 									  bool retain_dead_tuples, char *origin,
 									  Oid *subrel_local_oids, int subrel_count,
-									  char *subname);
+									  char *subname, bool only_sequences);
 static void check_pub_dead_tuple_retention(WalReceiverConn *wrconn);
 static void check_duplicates_in_publist(List *publist, Datum *datums);
 static List *merge_publications(List *oldpublist, List *newpublist, bool addpub, const char *subname);
@@ -736,20 +742,27 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 
 	recordDependencyOnOwner(SubscriptionRelationId, subid, owner);
 
+	/*
+	 * Currently, a replication origin is created for all subscriptions,
+	 * including those for empty or sequence-only publications. While this is
+	 * unnecessary, optimizing it would necessitate additional checks to skip
+	 * creating origin in DDL operations and apply workers, along with
+	 * handling the creation of the origin once tables are added to the
+	 * subscription. Given that such subscriptions are infrequent and
+	 * maintaining an origin incurs minimal cost, it doesn't seem to be worth
+	 * doing anything about it.
+	 */
 	ReplicationOriginNameForLogicalRep(subid, InvalidOid, originname, sizeof(originname));
 	replorigin_create(originname);
 
 	/*
 	 * Connect to remote side to execute requested commands and fetch table
-	 * info.
+	 * and sequence info.
 	 */
 	if (opts.connect)
 	{
 		char	   *err;
 		WalReceiverConn *wrconn;
-		List	   *tables;
-		ListCell   *lc;
-		char		table_state;
 		bool		must_use_password;
 
 		/* Try to connect to the publisher. */
@@ -764,10 +777,14 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 
 		PG_TRY();
 		{
+			bool		has_tables = false;
+			List	   *pubrels;
+			char		relation_state;
+
 			check_publications(wrconn, publications);
 			check_publications_origin(wrconn, publications, opts.copy_data,
 									  opts.retaindeadtuples, opts.origin,
-									  NULL, 0, stmt->subname);
+									  NULL, 0, stmt->subname, false);
 
 			if (opts.retaindeadtuples)
 				check_pub_dead_tuple_retention(wrconn);
@@ -776,25 +793,28 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 			 * Set sync state based on if we were asked to do data copy or
 			 * not.
 			 */
-			table_state = opts.copy_data ? SUBREL_STATE_INIT : SUBREL_STATE_READY;
+			relation_state = opts.copy_data ? SUBREL_STATE_INIT : SUBREL_STATE_READY;
 
 			/*
-			 * Get the table list from publisher and build local table status
-			 * info.
+			 * Build local relation status info. Relations are for both tables
+			 * and sequences from the publisher.
 			 */
-			tables = fetch_table_list(wrconn, publications);
-			foreach(lc, tables)
+			pubrels = fetch_relation_list(wrconn, publications);
+
+			foreach_ptr(PublicationRelKind, pubrelinfo, pubrels)
 			{
-				RangeVar   *rv = (RangeVar *) lfirst(lc);
 				Oid			relid;
+				char		relkind;
+				RangeVar   *rv = pubrelinfo->rv;
 
 				relid = RangeVarGetRelid(rv, AccessShareLock, false);
+				relkind = get_rel_relkind(relid);
 
 				/* Check for supported relkind. */
-				CheckSubscriptionRelkind(get_rel_relkind(relid),
+				CheckSubscriptionRelkind(relkind, pubrelinfo->relkind,
 										 rv->schemaname, rv->relname);
-
-				AddSubscriptionRelState(subid, relid, table_state,
+				has_tables |= (relkind != RELKIND_SEQUENCE);
+				AddSubscriptionRelState(subid, relid, relation_state,
 										InvalidXLogRecPtr, true);
 			}
 
@@ -802,6 +822,15 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 			 * If requested, create permanent slot for the subscription. We
 			 * won't use the initial snapshot for anything, so no need to
 			 * export it.
+			 *
+			 * Currently, a replication slot is created for all subscriptions,
+			 * including those for empty or sequence-only publications. While
+			 * this is unnecessary, optimizing this behavior would require
+			 * additional handling to ensure the apply worker operates
+			 * smoothly without acquiring a slot on the publisher, thus adding
+			 * complexity to the apply worker. Given that such subscriptions
+			 * are infrequent, it doesn't seem to be worth doing anything
+			 * about it.
 			 */
 			if (opts.create_slot)
 			{
@@ -825,7 +854,7 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 				 * PENDING, to allow ALTER SUBSCRIPTION ... REFRESH
 				 * PUBLICATION to work.
 				 */
-				if (opts.twophase && !opts.copy_data && tables != NIL)
+				if (opts.twophase && !opts.copy_data && has_tables)
 					twophase_enabled = true;
 
 				walrcv_create_slot(wrconn, opts.slot_name, false, twophase_enabled,
@@ -879,13 +908,12 @@ AlterSubscription_refresh(Subscription *sub, bool copy_data,
 						  List *validate_publications)
 {
 	char	   *err;
-	List	   *pubrel_names;
+	List	   *pubrels = NIL;
 	List	   *subrel_states;
 	Oid		   *subrel_local_oids;
 	Oid		   *pubrel_local_oids;
 	ListCell   *lc;
 	int			off;
-	int			remove_rel_len;
 	int			subrel_count;
 	Relation	rel = NULL;
 	typedef struct SubRemoveRels
@@ -893,7 +921,8 @@ AlterSubscription_refresh(Subscription *sub, bool copy_data,
 		Oid			relid;
 		char		state;
 	} SubRemoveRels;
-	SubRemoveRels *sub_remove_rels;
+
+	List	   *sub_remove_rels = NIL;
 	WalReceiverConn *wrconn;
 	bool		must_use_password;
 
@@ -915,17 +944,17 @@ AlterSubscription_refresh(Subscription *sub, bool copy_data,
 		if (validate_publications)
 			check_publications(wrconn, validate_publications);
 
-		/* Get the table list from publisher. */
-		pubrel_names = fetch_table_list(wrconn, sub->publications);
+		/* Get the relation list from publisher. */
+		pubrels = fetch_relation_list(wrconn, sub->publications);
 
-		/* Get local table list. */
-		subrel_states = GetSubscriptionRelations(sub->oid, false);
+		/* Get local relation list. */
+		subrel_states = GetSubscriptionRelations(sub->oid, true, true, false);
 		subrel_count = list_length(subrel_states);
 
 		/*
-		 * Build qsorted array of local table oids for faster lookup. This can
-		 * potentially contain all tables in the database so speed of lookup
-		 * is important.
+		 * Build qsorted array of local relation oids for faster lookup. This
+		 * can potentially contain all relation in the database so speed of
+		 * lookup is important.
 		 */
 		subrel_local_oids = palloc(subrel_count * sizeof(Oid));
 		off = 0;
@@ -940,33 +969,31 @@ AlterSubscription_refresh(Subscription *sub, bool copy_data,
 
 		check_publications_origin(wrconn, sub->publications, copy_data,
 								  sub->retaindeadtuples, sub->origin,
-								  subrel_local_oids, subrel_count, sub->name);
+								  subrel_local_oids, subrel_count, sub->name,
+								  false);
 
 		/*
-		 * Rels that we want to remove from subscription and drop any slots
-		 * and origins corresponding to them.
-		 */
-		sub_remove_rels = palloc(subrel_count * sizeof(SubRemoveRels));
-
-		/*
-		 * Walk over the remote tables and try to match them to locally known
-		 * tables. If the table is not known locally create a new state for
-		 * it.
+		 * Walk over the remote relations and try to match them to locally
+		 * known relations. If the relation is not known locally create a new
+		 * state for it.
 		 *
-		 * Also builds array of local oids of remote tables for the next step.
+		 * Also builds array of local oids of remote relations for the next
+		 * step.
 		 */
 		off = 0;
-		pubrel_local_oids = palloc(list_length(pubrel_names) * sizeof(Oid));
+		pubrel_local_oids = palloc(list_length(pubrels) * sizeof(Oid));
 
-		foreach(lc, pubrel_names)
+		foreach_ptr(PublicationRelKind, pubrelinfo, pubrels)
 		{
-			RangeVar   *rv = (RangeVar *) lfirst(lc);
+			RangeVar   *rv = pubrelinfo->rv;
 			Oid			relid;
+			char		relkind;
 
 			relid = RangeVarGetRelid(rv, AccessShareLock, false);
+			relkind = get_rel_relkind(relid);
 
 			/* Check for supported relkind. */
-			CheckSubscriptionRelkind(get_rel_relkind(relid),
+			CheckSubscriptionRelkind(relkind, pubrelinfo->relkind,
 									 rv->schemaname, rv->relname);
 
 			pubrel_local_oids[off++] = relid;
@@ -978,28 +1005,29 @@ AlterSubscription_refresh(Subscription *sub, bool copy_data,
 										copy_data ? SUBREL_STATE_INIT : SUBREL_STATE_READY,
 										InvalidXLogRecPtr, true);
 				ereport(DEBUG1,
-						(errmsg_internal("table \"%s.%s\" added to subscription \"%s\"",
-										 rv->schemaname, rv->relname, sub->name)));
+						errmsg_internal("%s \"%s.%s\" added to subscription \"%s\"",
+										relkind == RELKIND_SEQUENCE ? "sequence" : "table",
+										rv->schemaname, rv->relname, sub->name));
 			}
 		}
 
 		/*
-		 * Next remove state for tables we should not care about anymore using
-		 * the data we collected above
+		 * Next remove state for relations we should not care about anymore
+		 * using the data we collected above
 		 */
-		qsort(pubrel_local_oids, list_length(pubrel_names),
+		qsort(pubrel_local_oids, list_length(pubrels),
 			  sizeof(Oid), oid_cmp);
 
-		remove_rel_len = 0;
 		for (off = 0; off < subrel_count; off++)
 		{
 			Oid			relid = subrel_local_oids[off];
 
 			if (!bsearch(&relid, pubrel_local_oids,
-						 list_length(pubrel_names), sizeof(Oid), oid_cmp))
+						 list_length(pubrels), sizeof(Oid), oid_cmp))
 			{
 				char		state;
 				XLogRecPtr	statelsn;
+				char		relkind = get_rel_relkind(relid);
 
 				/*
 				 * Lock pg_subscription_rel with AccessExclusiveLock to
@@ -1021,41 +1049,55 @@ AlterSubscription_refresh(Subscription *sub, bool copy_data,
 				/* Last known rel state. */
 				state = GetSubscriptionRelState(sub->oid, relid, &statelsn);
 
-				sub_remove_rels[remove_rel_len].relid = relid;
-				sub_remove_rels[remove_rel_len++].state = state;
-
 				RemoveSubscriptionRel(sub->oid, relid);
 
-				logicalrep_worker_stop(sub->oid, relid);
-
 				/*
-				 * For READY state, we would have already dropped the
-				 * tablesync origin.
+				 * XXX Currently there is no sequencesync worker, so we only
+				 * stop tablesync workers.
 				 */
-				if (state != SUBREL_STATE_READY)
+				if (relkind != RELKIND_SEQUENCE)
 				{
-					char		originname[NAMEDATALEN];
+					SubRemoveRels *rel = palloc(sizeof(SubRemoveRels));
+
+					rel->relid = relid;
+					rel->state = state;
+
+					sub_remove_rels = lappend(sub_remove_rels, rel);
+
+					logicalrep_worker_stop(sub->oid, relid);
 
 					/*
-					 * Drop the tablesync's origin tracking if exists.
-					 *
-					 * It is possible that the origin is not yet created for
-					 * tablesync worker, this can happen for the states before
-					 * SUBREL_STATE_FINISHEDCOPY. The tablesync worker or
-					 * apply worker can also concurrently try to drop the
-					 * origin and by this time the origin might be already
-					 * removed. For these reasons, passing missing_ok = true.
+					 * For READY state, we would have already dropped the
+					 * tablesync origin.
 					 */
-					ReplicationOriginNameForLogicalRep(sub->oid, relid, originname,
-													   sizeof(originname));
-					replorigin_drop_by_name(originname, true, false);
+					if (state != SUBREL_STATE_READY)
+					{
+						char		originname[NAMEDATALEN];
+
+						/*
+						 * Drop the tablesync's origin tracking if exists.
+						 *
+						 * It is possible that the origin is not yet created
+						 * for tablesync worker, this can happen for the
+						 * states before SUBREL_STATE_FINISHEDCOPY. The
+						 * tablesync worker or apply worker can also
+						 * concurrently try to drop the origin and by this
+						 * time the origin might be already removed. For these
+						 * reasons, passing missing_ok = true.
+						 */
+						ReplicationOriginNameForLogicalRep(sub->oid, relid,
+														   originname,
+														   sizeof(originname));
+						replorigin_drop_by_name(originname, true, false);
+					}
 				}
 
 				ereport(DEBUG1,
-						(errmsg_internal("table \"%s.%s\" removed from subscription \"%s\"",
-										 get_namespace_name(get_rel_namespace(relid)),
-										 get_rel_name(relid),
-										 sub->name)));
+						errmsg_internal("%s \"%s.%s\" removed from subscription \"%s\"",
+										relkind == RELKIND_SEQUENCE ? "sequence" : "table",
+										get_namespace_name(get_rel_namespace(relid)),
+										get_rel_name(relid),
+										sub->name));
 			}
 		}
 
@@ -1064,10 +1106,10 @@ AlterSubscription_refresh(Subscription *sub, bool copy_data,
 		 * to be at the end because otherwise if there is an error while doing
 		 * the database operations we won't be able to rollback dropped slots.
 		 */
-		for (off = 0; off < remove_rel_len; off++)
+		foreach_ptr(SubRemoveRels, rel, sub_remove_rels)
 		{
-			if (sub_remove_rels[off].state != SUBREL_STATE_READY &&
-				sub_remove_rels[off].state != SUBREL_STATE_SYNCDONE)
+			if (rel->state != SUBREL_STATE_READY &&
+				rel->state != SUBREL_STATE_SYNCDONE)
 			{
 				char		syncslotname[NAMEDATALEN] = {0};
 
@@ -1081,7 +1123,7 @@ AlterSubscription_refresh(Subscription *sub, bool copy_data,
 				 * dropped slots and fail. For these reasons, we allow
 				 * missing_ok = true for the drop.
 				 */
-				ReplicationSlotNameForTablesync(sub->oid, sub_remove_rels[off].relid,
+				ReplicationSlotNameForTablesync(sub->oid, rel->relid,
 												syncslotname, sizeof(syncslotname));
 				ReplicationSlotDropAtPubNode(wrconn, syncslotname, true);
 			}
@@ -1097,6 +1139,58 @@ AlterSubscription_refresh(Subscription *sub, bool copy_data,
 		table_close(rel, NoLock);
 }
 
+/*
+ * Marks all sequences with INIT state.
+ */
+static void
+AlterSubscription_refresh_seq(Subscription *sub)
+{
+	List	   *subrel_states;
+	char	   *err = NULL;
+	WalReceiverConn *wrconn;
+	bool		must_use_password;
+
+	/* Load the library providing us libpq calls. */
+	load_file("libpqwalreceiver", false);
+
+	/* Try to connect to the publisher. */
+	must_use_password = sub->passwordrequired && !sub->ownersuperuser;
+	wrconn = walrcv_connect(sub->conninfo, true, true, must_use_password,
+							sub->name, &err);
+	if (!wrconn)
+		ereport(ERROR,
+				errcode(ERRCODE_CONNECTION_FAILURE),
+				errmsg("subscription \"%s\" could not connect to the publisher: %s",
+					   sub->name, err));
+
+	PG_TRY();
+	{
+		check_publications_origin(wrconn, sub->publications, false,
+								  sub->retaindeadtuples, sub->origin, NULL, 0,
+								  sub->name, true);
+
+		/* Get local sequence list. */
+		subrel_states = GetSubscriptionRelations(sub->oid, false, true, false);
+		foreach_ptr(SubscriptionRelState, subrel, subrel_states)
+		{
+			Oid			relid = subrel->relid;
+
+			UpdateSubscriptionRelState(sub->oid, relid, SUBREL_STATE_INIT,
+									   InvalidXLogRecPtr, false);
+			ereport(DEBUG1,
+					errmsg_internal("sequence \"%s.%s\" of subscription \"%s\" set to INIT state",
+									get_namespace_name(get_rel_namespace(relid)),
+									get_rel_name(relid),
+									sub->name));
+		}
+	}
+	PG_FINALLY();
+	{
+		walrcv_disconnect(wrconn);
+	}
+	PG_END_TRY();
+}
+
 /*
  * Common checks for altering failover, two_phase, and retain_dead_tuples
  * options.
@@ -1733,6 +1827,19 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 				break;
 			}
 
+		case ALTER_SUBSCRIPTION_REFRESH_SEQUENCES:
+			{
+				if (!sub->enabled)
+					ereport(ERROR,
+							errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							errmsg("%s is not allowed for disabled subscriptions",
+								   "ALTER SUBSCRIPTION ... REFRESH SEQUENCES"));
+
+				AlterSubscription_refresh_seq(sub);
+
+				break;
+			}
+
 		case ALTER_SUBSCRIPTION_SKIP:
 			{
 				parse_subscription_options(pstate, stmt->options, SUBOPT_LSN, &opts);
@@ -1826,7 +1933,7 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 
 			check_publications_origin(wrconn, sub->publications, false,
 									  retain_dead_tuples, origin, NULL, 0,
-									  sub->name);
+									  sub->name, false);
 
 			if (update_failover || update_two_phase)
 				walrcv_alter_slot(wrconn, sub->slotname,
@@ -2008,7 +2115,7 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
 	 * the apply and tablesync workers and they can't restart because of
 	 * exclusive lock on the subscription.
 	 */
-	rstates = GetSubscriptionRelations(subid, true);
+	rstates = GetSubscriptionRelations(subid, true, false, true);
 	foreach(lc, rstates)
 	{
 		SubscriptionRelState *rstate = (SubscriptionRelState *) lfirst(lc);
@@ -2318,17 +2425,17 @@ AlterSubscriptionOwner_oid(Oid subid, Oid newOwnerId)
 }
 
 /*
- * Check and log a warning if the publisher has subscribed to the same table,
- * its partition ancestors (if it's a partition), or its partition children (if
- * it's a partitioned table), from some other publishers. This check is
- * required in the following scenarios:
+ * Check and log a warning if the publisher has subscribed to the same relation
+ * (table or sequence), its partition ancestors (if it's a partition), or its
+ * partition children (if it's a partitioned table), from some other publishers.
+ * This check is required in the following scenarios:
  *
  * 1) For CREATE SUBSCRIPTION and ALTER SUBSCRIPTION ... REFRESH PUBLICATION
  *    statements with "copy_data = true" and "origin = none":
  *    - Warn the user that data with an origin might have been copied.
- *    - This check is skipped for tables already added, as incremental sync via
- *      WAL allows origin tracking. The list of such tables is in
- *      subrel_local_oids.
+ *    - This check is skipped for tables and sequences already added, as
+ *      incremental sync via WAL allows origin tracking. The list of such tables
+ *      is in subrel_local_oids.
  *
  * 2) For CREATE SUBSCRIPTION and ALTER SUBSCRIPTION ... REFRESH PUBLICATION
  *    statements with "retain_dead_tuples = true" and "origin = any", and for
@@ -2338,13 +2445,19 @@ AlterSubscriptionOwner_oid(Oid subid, Oid newOwnerId)
  *    - Warn the user that only conflict detection info for local changes on
  *      the publisher is retained. Data from other origins may lack sufficient
  *      details for reliable conflict detection.
+ *    - This check targets for tables only.
  *    - See comments atop worker.c for more details.
+ *
+ * 3) For ALTER SUBSCRIPTION ... REFRESH SEQUENCE statements with "copy_data =
+ *    true" and "origin = none":
+ *    - Warn the user that sequence data from another origin might have been
+ *      copied.
  */
 static void
 check_publications_origin(WalReceiverConn *wrconn, List *publications,
 						  bool copydata, bool retain_dead_tuples,
 						  char *origin, Oid *subrel_local_oids,
-						  int subrel_count, char *subname)
+						  int subrel_count, char *subname, bool only_sequences)
 {
 	WalRcvExecResult *res;
 	StringInfoData cmd;
@@ -2353,9 +2466,10 @@ check_publications_origin(WalReceiverConn *wrconn, List *publications,
 	List	   *publist = NIL;
 	int			i;
 	bool		check_rdt;
-	bool		check_table_sync;
+	bool		check_sync;
 	bool		origin_none = origin &&
 		pg_strcasecmp(origin, LOGICALREP_ORIGIN_NONE) == 0;
+	const char *query;
 
 	/*
 	 * Enable retain_dead_tuples checks only when origin is set to 'any',
@@ -2365,28 +2479,42 @@ check_publications_origin(WalReceiverConn *wrconn, List *publications,
 	check_rdt = retain_dead_tuples && !origin_none;
 
 	/*
-	 * Enable table synchronization checks only when origin is 'none', to
-	 * ensure that data from other origins is not inadvertently copied.
+	 * Enable table and sequence synchronization checks only when origin is
+	 * 'none', to ensure that data from other origins is not inadvertently
+	 * copied.
 	 */
-	check_table_sync = copydata && origin_none;
+	check_sync = copydata && origin_none;
 
-	/* retain_dead_tuples and table sync checks occur separately */
-	Assert(!(check_rdt && check_table_sync));
+	/* retain_dead_tuples and data synchronization checks occur separately */
+	Assert(!(check_rdt && check_sync));
 
 	/* Return if no checks are required */
-	if (!check_rdt && !check_table_sync)
+	if (!check_rdt && !check_sync)
 		return;
 
 	initStringInfo(&cmd);
-	appendStringInfoString(&cmd,
-						   "SELECT DISTINCT P.pubname AS pubname\n"
-						   "FROM pg_publication P,\n"
-						   "     LATERAL pg_get_publication_tables(P.pubname) GPT\n"
-						   "     JOIN pg_subscription_rel PS ON (GPT.relid = PS.srrelid OR"
-						   "     GPT.relid IN (SELECT relid FROM pg_partition_ancestors(PS.srrelid) UNION"
-						   "                   SELECT relid FROM pg_partition_tree(PS.srrelid))),\n"
-						   "     pg_class C JOIN pg_namespace N ON (N.oid = C.relnamespace)\n"
-						   "WHERE C.oid = GPT.relid AND P.pubname IN (");
+
+	query = "SELECT DISTINCT P.pubname AS pubname\n"
+		"FROM pg_publication P,\n"
+		"     LATERAL %s GPR\n"
+		"     JOIN pg_subscription_rel PS ON (GPR.relid = PS.srrelid OR"
+		"     (GPR.istable AND"
+		"      GPR.relid IN (SELECT relid FROM pg_partition_ancestors(PS.srrelid) UNION"
+		"                    SELECT relid FROM pg_partition_tree(PS.srrelid)))),\n"
+		"     pg_class C JOIN pg_namespace N ON (N.oid = C.relnamespace)\n"
+		"WHERE C.oid = GPR.relid AND P.pubname IN (";
+
+	if (walrcv_server_version(wrconn) < 190000 || check_rdt)
+		appendStringInfo(&cmd, query,
+						 "(SELECT relid, TRUE as istable FROM pg_get_publication_tables(P.pubname))");
+	else if (only_sequences)
+		appendStringInfo(&cmd, query,
+						 "(SELECT relid, FALSE as istable FROM pg_get_publication_sequences(P.pubname))");
+	else
+		appendStringInfo(&cmd, query,
+						 "(SELECT relid, TRUE as istable FROM pg_get_publication_tables(P.pubname) UNION ALL"
+						 " SELECT relid, FALSE as istable FROM pg_get_publication_sequences(P.pubname))");
+
 	GetPublicationsStr(publications, &cmd, true);
 	appendStringInfoString(&cmd, ")\n");
 
@@ -2399,7 +2527,7 @@ check_publications_origin(WalReceiverConn *wrconn, List *publications,
 	 * existing tables may now include changes from other origins due to newly
 	 * created subscriptions on the publisher.
 	 */
-	if (check_table_sync)
+	if (check_sync)
 	{
 		for (i = 0; i < subrel_count; i++)
 		{
@@ -2418,10 +2546,10 @@ check_publications_origin(WalReceiverConn *wrconn, List *publications,
 	if (res->status != WALRCV_OK_TUPLES)
 		ereport(ERROR,
 				(errcode(ERRCODE_CONNECTION_FAILURE),
-				 errmsg("could not receive list of replicated tables from the publisher: %s",
+				 errmsg("could not receive list of replicated relations from the publisher: %s",
 						res->err)));
 
-	/* Process tables. */
+	/* Process relations. */
 	slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
 	while (tuplestore_gettupleslot(res->tuplestore, true, false, slot))
 	{
@@ -2436,7 +2564,7 @@ check_publications_origin(WalReceiverConn *wrconn, List *publications,
 	}
 
 	/*
-	 * Log a warning if the publisher has subscribed to the same table from
+	 * Log a warning if the publisher has subscribed to the same relation from
 	 * some other publisher. We cannot know the origin of data during the
 	 * initial sync. Data origins can be found only from the WAL by looking at
 	 * the origin id.
@@ -2455,11 +2583,11 @@ check_publications_origin(WalReceiverConn *wrconn, List *publications,
 		/* Prepare the list of publication(s) for warning message. */
 		GetPublicationsStr(publist, pubnames, false);
 
-		if (check_table_sync)
+		if (check_sync || only_sequences)
 		{
 			appendStringInfo(err_msg, _("subscription \"%s\" requested copy_data with origin = NONE but might copy data that had a different origin"),
 							 subname);
-			appendStringInfoString(err_hint, _("Verify that initial data copied from the publisher tables did not come from other origins."));
+			appendStringInfoString(err_hint, _("Verify that initial data copied from the publisher relations did not come from other origins."));
 		}
 		else
 		{
@@ -2471,8 +2599,8 @@ check_publications_origin(WalReceiverConn *wrconn, List *publications,
 		ereport(WARNING,
 				errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				errmsg_internal("%s", err_msg->data),
-				errdetail_plural("The subscription subscribes to a publication (%s) that contains tables that are written to by other subscriptions.",
-								 "The subscription subscribes to publications (%s) that contain tables that are written to by other subscriptions.",
+				errdetail_plural("The subscription subscribes to a publication (%s) that contains relations that are written to by other subscriptions.",
+								 "The subscription subscribes to publications (%s) that contain relations that are written to by other subscriptions.",
 								 list_length(publist), pubnames->data),
 				errhint_internal("%s", err_hint->data));
 	}
@@ -2594,8 +2722,23 @@ CheckSubDeadTupleRetention(bool check_guc, bool sub_disabled,
 }
 
 /*
- * Get the list of tables which belong to specified publications on the
- * publisher connection.
+ * Return true iff 'rv' is a member of the list.
+ */
+static bool
+list_member_rangevar(const List *list, RangeVar *rv)
+{
+	foreach_ptr(PublicationRelKind, relinfo, list)
+	{
+		if (equal(relinfo->rv, rv))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Get the list of tables and sequences which belong to specified publications
+ * on the publisher connection.
  *
  * Note that we don't support the case where the column list is different for
  * the same table in different publications to avoid sending unwanted column
@@ -2603,15 +2746,17 @@ CheckSubDeadTupleRetention(bool check_guc, bool sub_disabled,
  * list and row filter are specified for different publications.
  */
 static List *
-fetch_table_list(WalReceiverConn *wrconn, List *publications)
+fetch_relation_list(WalReceiverConn *wrconn, List *publications)
 {
 	WalRcvExecResult *res;
 	StringInfoData cmd;
 	TupleTableSlot *slot;
-	Oid			tableRow[3] = {TEXTOID, TEXTOID, InvalidOid};
-	List	   *tablelist = NIL;
+	Oid			tableRow[4] = {TEXTOID, TEXTOID, InvalidOid, CHAROID};
+	List	   *relationlist = NIL;
 	int			server_version = walrcv_server_version(wrconn);
 	bool		check_columnlist = (server_version >= 150000);
+	bool		support_relkind_seq = (server_version >= 190000);
+	int			column_count = check_columnlist ? (support_relkind_seq ? 4 : 3) : 2;
 	StringInfo	pub_names = makeStringInfo();
 
 	initStringInfo(&cmd);
@@ -2619,7 +2764,7 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications)
 	/* Build the pub_names comma-separated string. */
 	GetPublicationsStr(publications, pub_names, true);
 
-	/* Get the list of tables from the publisher. */
+	/* Get the list of relations from the publisher */
 	if (server_version >= 160000)
 	{
 		tableRow[2] = INT2VECTOROID;
@@ -2637,14 +2782,27 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications)
 		 * to worry if different publications have specified them in a
 		 * different order. See pub_collist_validate.
 		 */
-		appendStringInfo(&cmd, "SELECT DISTINCT n.nspname, c.relname, gpt.attrs\n"
-						 "       FROM pg_class c\n"
+		appendStringInfo(&cmd, "SELECT DISTINCT n.nspname, c.relname, gpt.attrs");
+
+		if (support_relkind_seq)
+			appendStringInfo(&cmd, ", c.relkind\n");
+
+		appendStringInfo(&cmd, "   FROM pg_class c\n"
 						 "         JOIN pg_namespace n ON n.oid = c.relnamespace\n"
 						 "         JOIN ( SELECT (pg_get_publication_tables(VARIADIC array_agg(pubname::text))).*\n"
 						 "                FROM pg_publication\n"
 						 "                WHERE pubname IN ( %s )) AS gpt\n"
 						 "             ON gpt.relid = c.oid\n",
 						 pub_names->data);
+
+		/* From version 19, inclusion of sequences in the target is supported */
+		if (support_relkind_seq)
+			appendStringInfo(&cmd,
+							 "UNION ALL\n"
+							 "  SELECT DISTINCT s.schemaname, s.sequencename, NULL::int2vector AS attrs, " CppAsString2(RELKIND_SEQUENCE) "::\"char\" AS relkind\n"
+							 "  FROM pg_catalog.pg_publication_sequences s\n"
+							 "  WHERE s.pubname IN (%s)",
+							 pub_names->data);
 	}
 	else
 	{
@@ -2662,7 +2820,7 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications)
 
 	destroyStringInfo(pub_names);
 
-	res = walrcv_exec(wrconn, cmd.data, check_columnlist ? 3 : 2, tableRow);
+	res = walrcv_exec(wrconn, cmd.data, column_count, tableRow);
 	pfree(cmd.data);
 
 	if (res->status != WALRCV_OK_TUPLES)
@@ -2678,22 +2836,32 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications)
 		char	   *nspname;
 		char	   *relname;
 		bool		isnull;
-		RangeVar   *rv;
+		char		relkind = RELKIND_RELATION;
+		PublicationRelKind *relinfo = palloc_object(PublicationRelKind);
 
 		nspname = TextDatumGetCString(slot_getattr(slot, 1, &isnull));
 		Assert(!isnull);
 		relname = TextDatumGetCString(slot_getattr(slot, 2, &isnull));
 		Assert(!isnull);
 
-		rv = makeRangeVar(nspname, relname, -1);
+		if (support_relkind_seq)
+		{
+			relkind = DatumGetChar(slot_getattr(slot, 4, &isnull));
+			Assert(!isnull);
+		}
+
+		relinfo->rv = makeRangeVar(nspname, relname, -1);
+		relinfo->relkind = relkind;
 
-		if (check_columnlist && list_member(tablelist, rv))
+		if (relkind != RELKIND_SEQUENCE &&
+			check_columnlist &&
+			list_member_rangevar(relationlist, relinfo->rv))
 			ereport(ERROR,
 					errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					errmsg("cannot use different column lists for table \"%s.%s\" in different publications",
 						   nspname, relname));
 		else
-			tablelist = lappend(tablelist, rv);
+			relationlist = lappend(relationlist, relinfo);
 
 		ExecClearTuple(slot);
 	}
@@ -2701,7 +2869,7 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications)
 
 	walrcv_clear_result(res);
 
-	return tablelist;
+	return relationlist;
 }
 
 /*
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index b409d4ecbf5..3f61714ea7f 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -1112,18 +1112,35 @@ CheckCmdReplicaIdentity(Relation rel, CmdType cmd)
 
 
 /*
- * Check if we support writing into specific relkind.
+ * Check if we support writing into specific relkind of local relation and check
+ * if it aligns with the relkind of the relation on the publisher.
  *
  * The nspname and relname are only needed for error reporting.
  */
 void
-CheckSubscriptionRelkind(char relkind, const char *nspname,
-						 const char *relname)
+CheckSubscriptionRelkind(char localrelkind, char remoterelkind,
+						 const char *nspname, const char *relname)
 {
-	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
+	if (localrelkind != RELKIND_RELATION &&
+		localrelkind != RELKIND_PARTITIONED_TABLE &&
+		localrelkind != RELKIND_SEQUENCE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot use relation \"%s.%s\" as logical replication target",
 						nspname, relname),
-				 errdetail_relkind_not_supported(relkind)));
+				 errdetail_relkind_not_supported(localrelkind)));
+
+	/*
+	 * Allow RELKIND_RELATION and RELKIND_PARTITIONED_TABLE to be treated
+	 * interchangeably, but ensure that sequences (RELKIND_SEQUENCE) match
+	 * exactly on both publisher and subscriber.
+	 */
+	if ((localrelkind == RELKIND_SEQUENCE && remoterelkind != RELKIND_SEQUENCE) ||
+		(localrelkind != RELKIND_SEQUENCE && remoterelkind == RELKIND_SEQUENCE))
+		ereport(ERROR,
+				errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				errmsg("relation \"%s.%s\" type mismatch: source \"%s\", target \"%s\"",
+					   nspname, relname,
+					   remoterelkind == RELKIND_SEQUENCE ? "sequence" : "table",
+					   localrelkind == RELKIND_SEQUENCE ? "sequence" : "table"));
 }
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index dc0c2886674..a4b29c822e8 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10992,6 +10992,15 @@ AlterSubscriptionStmt:
 					n->options = $6;
 					$$ = (Node *) n;
 				}
+			| ALTER SUBSCRIPTION name REFRESH SEQUENCES
+				{
+					AlterSubscriptionStmt *n =
+						makeNode(AlterSubscriptionStmt);
+
+					n->kind = ALTER_SUBSCRIPTION_REFRESH_SEQUENCES;
+					n->subname = $3;
+					$$ = (Node *) n;
+				}
 			| ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition
 				{
 					AlterSubscriptionStmt *n =
diff --git a/src/backend/replication/logical/relation.c b/src/backend/replication/logical/relation.c
index f59046ad620..37b799f0604 100644
--- a/src/backend/replication/logical/relation.c
+++ b/src/backend/replication/logical/relation.c
@@ -425,6 +425,7 @@ logicalrep_rel_open(LogicalRepRelId remoteid, LOCKMODE lockmode)
 
 		/* Check for supported relkind. */
 		CheckSubscriptionRelkind(entry->localrel->rd_rel->relkind,
+								 remoterel->relkind,
 								 remoterel->nspname, remoterel->relname);
 
 		/*
diff --git a/src/backend/replication/logical/syncutils.c b/src/backend/replication/logical/syncutils.c
index 1bb3ca01db0..46d88838894 100644
--- a/src/backend/replication/logical/syncutils.c
+++ b/src/backend/replication/logical/syncutils.c
@@ -150,7 +150,8 @@ FetchRelationStates(bool *started_tx)
 		}
 
 		/* Fetch tables and sequences that are in non-ready state. */
-		rstates = GetSubscriptionRelations(MySubscription->oid, true);
+		rstates = GetSubscriptionRelations(MySubscription->oid, true, true,
+										   true);
 
 		/* Allocate the tracking info in a permanent memory context. */
 		oldctx = MemoryContextSwitchTo(CacheMemoryContext);
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 2ba12517e93..40e1ed3c20e 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -840,7 +840,7 @@ fetch_remote_table_info(char *nspname, char *relname, LogicalRepRelation *lrel,
 		/*
 		 * We don't support the case where the column list is different for
 		 * the same table when combining publications. See comments atop
-		 * fetch_table_list. So there should be only one row returned.
+		 * fetch_relation_list. So there should be only one row returned.
 		 * Although we already checked this when creating the subscription, we
 		 * still need to check here in case the column list was changed after
 		 * creating the subscription and before the sync worker is started.
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 3c58ad88476..d986ba2ea50 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -3367,6 +3367,7 @@ apply_handle_tuple_routing(ApplyExecutionData *edata,
 	 * at CREATE/ALTER SUBSCRIPTION would be insufficient.
 	 */
 	CheckSubscriptionRelkind(partrel->rd_rel->relkind,
+							 relmapentry->remoterel.relkind,
 							 get_namespace_name(RelationGetNamespace(partrel)),
 							 RelationGetRelationName(partrel));
 
@@ -3563,6 +3564,7 @@ apply_handle_tuple_routing(ApplyExecutionData *edata,
 
 					/* Check that new partition also has supported relkind. */
 					CheckSubscriptionRelkind(partrel_new->rd_rel->relkind,
+											 relmapentry->remoterel.relkind,
 											 get_namespace_name(RelationGetNamespace(partrel_new)),
 											 RelationGetRelationName(partrel_new));
 
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 847806b0a2e..05cc7512520 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1137,9 +1137,9 @@ pgoutput_column_list_init(PGOutputData *data, List *publications,
 	 *
 	 * Note that we don't support the case where the column list is different
 	 * for the same table when combining publications. See comments atop
-	 * fetch_table_list. But one can later change the publication so we still
-	 * need to check all the given publication-table mappings and report an
-	 * error if any publications have a different column list.
+	 * fetch_relation_list. But one can later change the publication so we
+	 * still need to check all the given publication-table mappings and report
+	 * an error if any publications have a different column list.
 	 */
 	foreach(lc, publications)
 	{
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index ad37f9f6ed0..fa08059671b 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2319,11 +2319,11 @@ match_previous_words(int pattern_id,
 	/* ALTER SUBSCRIPTION <name> */
 	else if (Matches("ALTER", "SUBSCRIPTION", MatchAny))
 		COMPLETE_WITH("CONNECTION", "ENABLE", "DISABLE", "OWNER TO",
-					  "RENAME TO", "REFRESH PUBLICATION", "SET", "SKIP (",
-					  "ADD PUBLICATION", "DROP PUBLICATION");
-	/* ALTER SUBSCRIPTION <name> REFRESH PUBLICATION */
-	else if (Matches("ALTER", "SUBSCRIPTION", MatchAny, MatchAnyN, "REFRESH", "PUBLICATION"))
-		COMPLETE_WITH("WITH (");
+					  "RENAME TO", "REFRESH PUBLICATION", "REFRESH SEQUENCES",
+					  "SET", "SKIP (", "ADD PUBLICATION", "DROP PUBLICATION");
+	/* ALTER SUBSCRIPTION <name> REFRESH */
+	else if (Matches("ALTER", "SUBSCRIPTION", MatchAny, MatchAnyN, "REFRESH"))
+		COMPLETE_WITH("PUBLICATION", "SEQUENCES");
 	/* ALTER SUBSCRIPTION <name> REFRESH PUBLICATION WITH ( */
 	else if (Matches("ALTER", "SUBSCRIPTION", MatchAny, MatchAnyN, "REFRESH", "PUBLICATION", "WITH", "("))
 		COMPLETE_WITH("copy_data");
diff --git a/src/include/catalog/pg_subscription_rel.h b/src/include/catalog/pg_subscription_rel.h
index 61b63c6bb7a..49deec052c6 100644
--- a/src/include/catalog/pg_subscription_rel.h
+++ b/src/include/catalog/pg_subscription_rel.h
@@ -90,7 +90,8 @@ extern char GetSubscriptionRelState(Oid subid, Oid relid, XLogRecPtr *sublsn);
 extern void RemoveSubscriptionRel(Oid subid, Oid relid);
 
 extern bool HasSubscriptionTables(Oid subid);
-extern List *GetSubscriptionRelations(Oid subid, bool not_ready);
+extern List *GetSubscriptionRelations(Oid subid, bool get_tables,
+									  bool get_sequences, bool not_ready);
 
 extern void UpdateDeadTupleRetentionStatus(Oid subid, bool active);
 
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 3248e78cd28..0ba86c2ad72 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -784,8 +784,8 @@ extern void ExecSimpleRelationDelete(ResultRelInfo *resultRelInfo,
 									 TupleTableSlot *searchslot);
 extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd);
 
-extern void CheckSubscriptionRelkind(char relkind, const char *nspname,
-									 const char *relname);
+extern void CheckSubscriptionRelkind(char localrelkind, char remoterelkind,
+									 const char *nspname, const char *relname);
 
 /*
  * prototypes from functions in nodeModifyTable.c
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4e445fe0cd7..ecbddd12e1b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4362,6 +4362,7 @@ typedef enum AlterSubscriptionType
 	ALTER_SUBSCRIPTION_ADD_PUBLICATION,
 	ALTER_SUBSCRIPTION_DROP_PUBLICATION,
 	ALTER_SUBSCRIPTION_REFRESH_PUBLICATION,
+	ALTER_SUBSCRIPTION_REFRESH_SEQUENCES,
 	ALTER_SUBSCRIPTION_ENABLED,
 	ALTER_SUBSCRIPTION_SKIP,
 } AlterSubscriptionType;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index ee1cab6190f..fb6a67d94e0 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2362,6 +2362,7 @@ PublicationObjSpec
 PublicationObjSpecType
 PublicationPartOpt
 PublicationRelInfo
+PublicationRelKind
 PublicationSchemaInfo
 PublicationTable
 PublishGencolsType
-- 
2.31.1

