From 2a8cf85fe2f98e7c3a808843260d6c8e8f342bb4 Mon Sep 17 00:00:00 2001
From: Dean Rasheed <dean.a.rasheed@gmail.com>
Date: Tue, 9 Jun 2026 19:00:09 +0100
Subject: [PATCH v11 4/9] Add support for global temporary sequences.

A global temporary sequence may be created directly using CREATE
GLOBAL TEMP SEQUENCE, or indirectly by including a serial or identity
column when creating a global temporary table.

The sequence definition is permanent, but its data is temporary. Thus
it operates independently in each session, and it returns sequential
values starting from the sequence's start value in each session.
---
 doc/src/sgml/ref/create_sequence.sgml       |  32 ++-
 src/backend/catalog/global_temp.c           |  80 +++++--
 src/backend/catalog/heap.c                  |   2 +-
 src/backend/catalog/storage.c               |   5 +-
 src/backend/commands/sequence.c             |  66 ++++++
 src/backend/parser/parse_utilcmd.c          |   3 +-
 src/bin/pg_dump/pg_dump.c                   |   4 +-
 src/bin/psql/describe.c                     |   6 +
 src/bin/psql/tab-complete.in.c              |  13 +-
 src/include/catalog/global_temp.h           |   5 +-
 src/include/commands/sequence.h             |   1 +
 src/test/isolation/expected/global-temp.out | 242 +++++++++----------
 src/test/isolation/specs/global-temp.spec   |   2 +-
 src/test/recovery/t/001_stream_rep.pl       |  28 ++-
 src/test/regress/expected/global_temp.out   | 250 ++++++++++++++++----
 src/test/regress/sql/global_temp.sql        |  56 ++++-
 src/test/subscription/t/039_global_temp.pl  |  30 ++-
 17 files changed, 606 insertions(+), 219 deletions(-)

diff --git a/doc/src/sgml/ref/create_sequence.sgml b/doc/src/sgml/ref/create_sequence.sgml
index 0ffcd0febd1..3a20521b7e3 100644
--- a/doc/src/sgml/ref/create_sequence.sgml
+++ b/doc/src/sgml/ref/create_sequence.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE [ { TEMPORARY | TEMP } | UNLOGGED ] SEQUENCE [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable>
+CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] SEQUENCE [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable>
     [ AS <replaceable class="parameter">data_type</replaceable> ]
     [ INCREMENT [ BY ] <replaceable class="parameter">increment</replaceable> ]
     [ MINVALUE <replaceable class="parameter">minvalue</replaceable> | NO MINVALUE ] [ MAXVALUE <replaceable class="parameter">maxvalue</replaceable> | NO MAXVALUE ]
@@ -46,8 +46,8 @@ CREATE [ { TEMPORARY | TEMP } | UNLOGGED ] SEQUENCE [ IF NOT EXISTS ] <replaceab
   <para>
    If a schema name is given then the sequence is created in the
    specified schema.  Otherwise it is created in the current schema.
-   Temporary sequences exist in a special schema, so a schema name cannot be
-   given when creating a temporary sequence.
+   Local temporary sequences exist in a special temporary schema, so a
+   schema name cannot be given when creating a local temporary sequence.
    The sequence name must be distinct from the name of any other relation
    (table, sequence, index, view, materialized view, or foreign table) in
    the same schema.
@@ -82,15 +82,31 @@ SELECT * FROM <replaceable>name</replaceable>;
 
   <variablelist>
    <varlistentry>
-    <term><literal>TEMPORARY</literal> or <literal>TEMP</literal></term>
+    <term><literal>[ GLOBAL | LOCAL ] { TEMPORARY | TEMP }</literal></term>
     <listitem>
      <para>
-      If specified, the sequence object is created only for this
+      If specified, a temporary sequence is created.  Temporary sequences may
+      be either global or local.  If neither <literal>GLOBAL</literal> nor
+      <literal>LOCAL</literal> is specified, then local is assumed.
+     </para>
+
+     <para>
+      A local temporary sequence object is created only for this
       session, and is automatically dropped on session exit.  Existing
       permanent sequences with the same name are not visible (in this
       session) while the temporary sequence exists, unless they are
       referenced with schema-qualified names.
      </para>
+
+     <para>
+      A global temporary sequence is not dropped on session exit, and is
+      visible to all sessions, but the sequence operates independently in
+      each session, starting with the sequence's starting value in each
+      session.  Thus the values returned will not be unique across all
+      sessions.  A global temporary sequence may be created in any schema, by
+      specifying a schema-qualified name, but it may not be created in the
+      special temporary schema used for local temporary sequences.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -290,7 +306,8 @@ SELECT * FROM <replaceable>name</replaceable>;
   </para>
 
   <para>
-   Unexpected results might be obtained if a <replaceable
+   For non-temporary sequences,
+   unexpected results might be obtained if a <replaceable
    class="parameter">cache</replaceable> setting greater than one is
    used for a sequence object that will be used concurrently by
    multiple sessions.  Each session will allocate and cache successive
@@ -306,7 +323,8 @@ SELECT * FROM <replaceable>name</replaceable>;
 
   <para>
    Furthermore, although multiple sessions are guaranteed to allocate
-   distinct sequence values, the values might be generated out of
+   distinct sequence values for a non-temporary sequence,
+   the values might be generated out of
    sequence when all the sessions are considered.  For example, with
    a <replaceable class="parameter">cache</replaceable> setting of 10,
    session A might reserve values 1..10 and return
diff --git a/src/backend/catalog/global_temp.c b/src/backend/catalog/global_temp.c
index 9e184d63d14..81cb09cc4bc 100644
--- a/src/backend/catalog/global_temp.c
+++ b/src/backend/catalog/global_temp.c
@@ -60,6 +60,7 @@
 #include "access/xlogutils.h"
 #include "catalog/global_temp.h"
 #include "catalog/storage.h"
+#include "commands/sequence.h"
 #include "commands/tablecmds.h"
 #include "funcapi.h"
 #include "lib/dshash.h"
@@ -560,7 +561,7 @@ gtr_init_usage_tables(void)
  *	an existing entry was found.
  */
 static GtrUsageEntry *
-gtr_record_usage(Oid relid, char relkind, bool *found)
+gtr_record_usage(Oid relid, char relkind, bool isNew, bool *found)
 {
 	GtrUsageEntry *local_entry;
 	GtrSharedUsageKey key;
@@ -575,13 +576,23 @@ gtr_record_usage(Oid relid, char relkind, bool *found)
 	if (*found)
 		return local_entry;		/* already recorded, nothing to do */
 
-	/* Record the usage as starting in the current subtransaction */
-	local_entry->started_subid = GetCurrentSubTransactionId();
+	/*
+	 * When a sequence that was created in another backend is initialized,
+	 * record its usage non-transactionally (like its storage), so that the
+	 * sequence is not invalidated and reinitialized after (sub)rollback.
+	 * Sequence creation, on the other hand, is transactional, and may be
+	 * undone by (sub)rollback.  For all other relkinds, both creation and
+	 * initialization are transactional.
+	 */
+	if (relkind == RELKIND_SEQUENCE && !isNew)
+		local_entry->started_subid = InvalidSubTransactionId;
+	else
+	{
+		local_entry->started_subid = GetCurrentSubTransactionId();
+		EOXactUsageListAdd(relid);
+	}
 	local_entry->stopped_subid = InvalidSubTransactionId;
 
-	/* Flag the usage entry for eoxact cleanup */
-	EOXactUsageListAdd(relid);
-
 	/* Remember the relation's relkind */
 	local_entry->relkind = relkind;
 
@@ -802,12 +813,18 @@ AtEOSubXact_UsageCleanup(GtrUsageEntry *entry, bool isCommit,
  *	temporary relation, and arrange for all storage created to be deleted on
  *	backend exit.
  *
+ *	For about-to-be-created storage, if register_delete is true (the normal
+ *	case), the storage creation is transactional, and it will be deleted on
+ *	rollback.  If register_delete is false, the storage will not be deleted on
+ *	rollback (used when initializing a sequence created in another backend).
+ *
  *	This is called for global temporary relations whenever storage is created
  *	using RelationCreateStorage() or deleted using RelationDropStorage().
  */
 void
 TrackGlobalTempRelationStorage(Oid relid, RelFileLocator rlocator,
-							   ProcNumber backend, bool create)
+							   ProcNumber backend, bool create,
+							   bool register_delete)
 {
 	GtrStorageEntry *entry;
 
@@ -836,9 +853,20 @@ TrackGlobalTempRelationStorage(Oid relid, RelFileLocator rlocator,
 			smgrdounlinkall(&srel, 1, false);
 		smgrclose(srel);
 
-		/* Mark the storage as created in the current subtransaction */
+		/*
+		 * If register_delete is true, mark the storage as created in the
+		 * current subtransaction, so that it is deleted on rollback, and flag
+		 * it for eoxact cleanup.
+		 */
 		entry->relid = relid;
-		entry->created_subid = GetCurrentSubTransactionId();
+		if (register_delete)
+		{
+			entry->created_subid = GetCurrentSubTransactionId();
+			EOXactStorageListAdd(rlocator);
+		}
+		else
+			entry->created_subid = InvalidSubTransactionId;
+
 		entry->dropped_subid = InvalidSubTransactionId;
 	}
 	else
@@ -849,10 +877,10 @@ TrackGlobalTempRelationStorage(Oid relid, RelFileLocator rlocator,
 			elog(ERROR, "Storage not found for relation %u", relid);
 
 		entry->dropped_subid = GetCurrentSubTransactionId();
-	}
 
-	/* Flag the storage for eoxact cleanup */
-	EOXactStorageListAdd(rlocator);
+		/* Flag the storage for eoxact cleanup */
+		EOXactStorageListAdd(rlocator);
+	}
 }
 
 /*
@@ -927,7 +955,13 @@ InitGlobalTempRelation(Relation relation)
 	if (RELKIND_HAS_STORAGE(relation->rd_rel->relkind) &&
 		FIND_LOCAL_STORAGE_ENTRY(relation->rd_locator) == NULL)
 	{
-		/* Create (and track) storage for the relation */
+		/*
+		 * Create (and track) storage for the relation.  For a sequence, the
+		 * storage is created non-transactionally, so that the initialization
+		 * survives rollback and, as for a permanent sequence, rollback
+		 * doesn't cause a sequence restart.  Otherwise, for other relkinds,
+		 * the storage is created transactionally.
+		 */
 		if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))
 			table_relation_set_new_filelocator(relation,
 											   &relation->rd_locator,
@@ -938,7 +972,7 @@ InitGlobalTempRelation(Relation relation)
 			RelationCreateStorage(relation->rd_id,
 								  relation->rd_locator,
 								  relation->rd_rel->relpersistence,
-								  true);
+								  relation->rd_rel->relkind != RELKIND_SEQUENCE);
 
 		/*
 		 * Register the relation's ON COMMIT action, if it's DELETE ROWS (may
@@ -977,10 +1011,14 @@ InitGlobalTempRelation(Relation relation)
 			if (nblocks > 0)
 				relation->rd_index->indisvalid = false;
 		}
+
+		/* If it's a sequence, initialize it */
+		if (relation->rd_rel->relkind == RELKIND_SEQUENCE)
+			InitGlobalTempSequence(relation);
 	}
 
 	/* Track our use of the relation, if we haven't already done so */
-	TrackGlobalTempRelation(relation);
+	TrackGlobalTempRelation(relation, false);
 }
 
 /*
@@ -990,19 +1028,19 @@ InitGlobalTempRelation(Relation relation)
  *	so.
  *
  *	NB: this processing must be idempotent, because it is called both when a
- *	global temporary relation is created in this session, and when one that
- *	was created by some other backend is opened for the first time, as well as
- *	after a relcache invalidation.
+ *	global temporary relation is created in this session (isNew == true), and
+ *	when one that was created by some other backend is opened for the first
+ *	time, as well as after a relcache invalidation (isNew == false).
  */
 void
-TrackGlobalTempRelation(Relation relation)
+TrackGlobalTempRelation(Relation relation, bool isNew)
 {
 	GtrUsageEntry *entry;
 	bool		found;
 
 	/* Record our use of the relation, if we haven't done so already */
 	entry = gtr_record_usage(relation->rd_id, relation->rd_rel->relkind,
-							 &found);
+							 isNew, &found);
 
 	/*
 	 * For a new entry, fill out the session-local relation information, with
@@ -1025,7 +1063,7 @@ TrackGlobalTempRelation(Relation relation)
 		entry->history.info.indisvalid = (relation->rd_index == NULL ||
 										  relation->rd_index->indisvalid);
 
-		entry->history.subid = GetCurrentSubTransactionId();
+		entry->history.subid = entry->started_subid;
 		entry->history.prev = NULL;
 	}
 }
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index ae40e0bee40..16979fe54c3 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1003,7 +1003,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 
 	/* If it's a global temporary relation, track our use of it */
 	if (RELATION_IS_GLOBAL_TEMP(new_rel_desc))
-		TrackGlobalTempRelation(new_rel_desc);
+		TrackGlobalTempRelation(new_rel_desc, true);
 }
 
 /* --------------------------------
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index 62fc44b669f..6ff7a3d9978 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -141,7 +141,8 @@ RelationCreateStorage(Oid relid, RelFileLocator rlocator, char relpersistence,
 		case RELPERSISTENCE_GLOBAL_TEMP:
 			/* Track storage created for global temporary relations */
 			procNumber = ProcNumberForTempRelations();
-			TrackGlobalTempRelationStorage(relid, rlocator, procNumber, true);
+			TrackGlobalTempRelationStorage(relid, rlocator, procNumber, true,
+										   register_delete);
 			needs_wal = false;
 			break;
 		case RELPERSISTENCE_UNLOGGED:
@@ -221,7 +222,7 @@ RelationDropStorage(Relation rel)
 	/* Track to-be-deleted storage for global temporary relations */
 	if (RELATION_IS_GLOBAL_TEMP(rel))
 		TrackGlobalTempRelationStorage(rel->rd_id, rel->rd_locator,
-									   rel->rd_backend, false);
+									   rel->rd_backend, false, false);
 
 	/* Add the relation to the list of stuff to delete at commit */
 	pending = (PendingRelDelete *)
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 551667650ba..9a7cace6c37 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -321,6 +321,72 @@ ResetSequence(Oid seq_relid)
 	sequence_close(seq_rel, NoLock);
 }
 
+/*
+ * InitGlobalTempSequence - initialize a global temporary sequence
+ *
+ * This is called the first time a global temporary sequence is accessed from
+ * a backend other than the backend that created it.  On entry, the sequence
+ * should have valid catalog entries, and its physical disk file should have
+ * been created, but be empty.
+ */
+void
+InitGlobalTempSequence(Relation seq_rel)
+{
+	Oid			seq_relid = RelationGetRelid(seq_rel);
+	SeqTable	elm;
+	HeapTuple	pgstuple;
+	Form_pg_sequence pgsform;
+	int64		startv;
+	int			i;
+	Datum		value[SEQ_COL_LASTCOL];
+	bool		null[SEQ_COL_LASTCOL];
+	TupleDesc	tupDesc;
+	HeapTuple	tuple;
+
+	/* Find or create a hash table entry for this sequence */
+	if (seqhashtab == NULL)
+		create_seq_hashtable();
+
+	elm = (SeqTable) hash_search(seqhashtab, &seq_relid, HASH_ENTER, NULL);
+
+	/* Initialize the sequence state */
+	elm->filenumber = seq_rel->rd_rel->relfilenode;
+	elm->lxid = InvalidLocalTransactionId;
+	elm->last_valid = false;
+	elm->last = elm->cached = 0;
+
+	/* Read the sequence definition from pg_sequence */
+	pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
+	if (!HeapTupleIsValid(pgstuple))
+		elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
+	pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
+	startv = pgsform->seqstart;
+	ReleaseSysCache(pgstuple);
+
+	/* Build a new sequence tuple */
+	for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++)
+	{
+		switch (i)
+		{
+			case SEQ_COL_LASTVAL:
+				value[i - 1] = Int64GetDatumFast(startv);
+				break;
+			case SEQ_COL_LOG:
+				value[i - 1] = Int64GetDatum((int64) 0);
+				break;
+			case SEQ_COL_CALLED:
+				value[i - 1] = BoolGetDatum(false);
+				break;
+		}
+		null[i - 1] = false;
+	}
+	tupDesc = RelationGetDescr(seq_rel);
+	tuple = heap_form_tuple(tupDesc, value, null);
+
+	/* Initialize the sequence's data */
+	fill_seq_with_data(seq_rel, tuple);
+}
+
 /*
  * Initialize a sequence's relation with the specified tuple as content
  *
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 3ee37774da0..707235b0e2e 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -505,7 +505,8 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqpersistence = cxt->rel ? cxt->rel->rd_rel->relpersistence : cxt->relation->relpersistence;
 	if (loggedEl)
 	{
-		if (seqpersistence == RELPERSISTENCE_TEMP)
+		if (seqpersistence == RELPERSISTENCE_TEMP ||
+			seqpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot set logged status of a temporary sequence"),
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 26d0bc89c0c..dde918aabcd 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -19284,7 +19284,9 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 		appendPQExpBuffer(query,
 						  "CREATE %sSEQUENCE %s\n",
 						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  "UNLOGGED " :
+						  tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ?
+						  "GLOBAL TEMP " : "",
 						  fmtQualifiedDumpable(tbinfo));
 
 		if (seq->seqtype != SEQTYPE_BIGINT)
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 1ab7c782d3b..f86efa92e7f 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1803,6 +1803,12 @@ describeOneTableDetails(const char *schemaname,
 		if (tableinfo.relpersistence == RELPERSISTENCE_UNLOGGED)
 			printfPQExpBuffer(&title, _("Unlogged sequence \"%s.%s\""),
 							  schemaname, relationname);
+		else if (tableinfo.relpersistence == RELPERSISTENCE_TEMP)
+			printfPQExpBuffer(&title, _("Temporary sequence \"%s.%s\""),
+							  schemaname, relationname);
+		else if (tableinfo.relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			printfPQExpBuffer(&title, _("Global temporary sequence \"%s.%s\""),
+							  schemaname, relationname);
 		else
 			printfPQExpBuffer(&title, _("Sequence \"%s.%s\""),
 							  schemaname, relationname);
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 49d94e83210..0665692d069 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -3766,14 +3766,17 @@ match_previous_words(int pattern_id,
 
 /* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	else if (TailMatches("CREATE", "SEQUENCE", MatchAny) ||
-			 TailMatches("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
+			 TailMatches("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny) ||
+			 TailMatches("CREATE", "GLOBAL|LOCAL", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
 		COMPLETE_WITH("AS", "INCREMENT BY", "MINVALUE", "MAXVALUE", "NO",
 					  "CACHE", "CYCLE", "OWNED BY", "START WITH");
 	else if (TailMatches("CREATE", "SEQUENCE", MatchAny, "AS") ||
-			 TailMatches("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "AS"))
+			 TailMatches("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "AS") ||
+			 TailMatches("CREATE", "GLOBAL|LOCAL", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "AS"))
 		COMPLETE_WITH_CS("smallint", "integer", "bigint");
 	else if (TailMatches("CREATE", "SEQUENCE", MatchAny, "NO") ||
-			 TailMatches("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
+			 TailMatches("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO") ||
+			 TailMatches("CREATE", "GLOBAL|LOCAL", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
@@ -3794,9 +3797,9 @@ match_previous_words(int pattern_id,
 	/* Complete "CREATE GLOBAL|LOCAL" with TEMP or TEMPORARY */
 	else if (TailMatches("CREATE", "GLOBAL|LOCAL"))
 		COMPLETE_WITH("TEMP", "TEMPORARY");
-	/* Complete "CREATE GLOBAL TEMP/TEMPORARY" with TABLE */
+	/* Complete "CREATE GLOBAL TEMP/TEMPORARY" with SEQUENCE or TABLE */
 	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
-		COMPLETE_WITH("TABLE");
+		COMPLETE_WITH("SEQUENCE", "TABLE");
 
 	/*
 	 * Complete "CREATE [ LOCAL ] TEMP/TEMPORARY" with SEQUENCE, TABLE, or
diff --git a/src/include/catalog/global_temp.h b/src/include/catalog/global_temp.h
index 63c23a6930c..10e5ba6d261 100644
--- a/src/include/catalog/global_temp.h
+++ b/src/include/catalog/global_temp.h
@@ -48,11 +48,12 @@ typedef struct GtrInfo
 	} while (0)
 
 extern void TrackGlobalTempRelationStorage(Oid relid, RelFileLocator rlocator,
-										   ProcNumber backend, bool create);
+										   ProcNumber backend, bool create,
+										   bool register_delete);
 extern void ReassignGlobalTempRelationStorage(RelFileLocator rlocator,
 											  Oid newRelid);
 extern void InitGlobalTempRelation(Relation relation);
-extern void TrackGlobalTempRelation(Relation relation);
+extern void TrackGlobalTempRelation(Relation relation, bool isNew);
 extern void ForgetGlobalTempRelation(Oid relid);
 extern void InvalidateGlobalTempRelation(Oid relid);
 extern void ProcessInvalidatedGlobalTempRelations(void);
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 2c3c4a3f074..6f514ac6dd1 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -47,6 +47,7 @@ extern ObjectAddress AlterSequence(ParseState *pstate, AlterSeqStmt *stmt);
 extern void SequenceChangePersistence(Oid relid, char newrelpersistence);
 extern void DeleteSequenceTuple(Oid relid);
 extern void ResetSequence(Oid seq_relid);
+extern void InitGlobalTempSequence(Relation seq_rel);
 extern void SetSequence(Oid relid, int64 next, bool iscalled);
 extern void ResetSequenceCaches(void);
 
diff --git a/src/test/isolation/expected/global-temp.out b/src/test/isolation/expected/global-temp.out
index ccab38b8c59..27a7fe28a52 100644
--- a/src/test/isolation/expected/global-temp.out
+++ b/src/test/isolation/expected/global-temp.out
@@ -15,15 +15,15 @@ starting permutation: ins1 ins2 sel1 sel2
 step ins1: INSERT INTO tmp VALUES (1, 's1');
 step ins2: INSERT INTO tmp VALUES (1, 's2');
 step sel1: SELECT * FROM tmp;
-key|val
----+---
-  1|s1 
+key|val|seq
+---+---+---
+  1|s1 |  1
 (1 row)
 
 step sel2: SELECT * FROM tmp;
-key|val
----+---
-  1|s2 
+key|val|seq
+---+---+---
+  1|s2 |  1
 (1 row)
 
 
@@ -52,28 +52,28 @@ step ins1: INSERT INTO tmp VALUES (1, 's1');
 step b2: BEGIN;
 step ins2: INSERT INTO tmp VALUES (1, 's2');
 step sel1: SELECT * FROM tmp;
-key|val
----+---
-  1|s1 
+key|val|seq
+---+---+---
+  1|s1 |  1
 (1 row)
 
 step sel2: SELECT * FROM tmp;
-key|val
----+---
-  1|s2 
+key|val|seq
+---+---+---
+  1|s2 |  1
 (1 row)
 
 step c2: COMMIT;
 step sel1: SELECT * FROM tmp;
-key|val
----+---
-  1|s1 
+key|val|seq
+---+---+---
+  1|s1 |  1
 (1 row)
 
 step sel2: SELECT * FROM tmp;
-key|val
----+---
-  1|s2 
+key|val|seq
+---+---+---
+  1|s2 |  1
 (1 row)
 
 
@@ -82,27 +82,27 @@ step ins1: INSERT INTO tmp VALUES (1, 's1');
 step b2: BEGIN;
 step ins2: INSERT INTO tmp VALUES (1, 's2');
 step sel1: SELECT * FROM tmp;
-key|val
----+---
-  1|s1 
+key|val|seq
+---+---+---
+  1|s1 |  1
 (1 row)
 
 step sel2: SELECT * FROM tmp;
-key|val
----+---
-  1|s2 
+key|val|seq
+---+---+---
+  1|s2 |  1
 (1 row)
 
 step r2: ROLLBACK;
 step sel1: SELECT * FROM tmp;
-key|val
----+---
-  1|s1 
+key|val|seq
+---+---+---
+  1|s1 |  1
 (1 row)
 
 step sel2: SELECT * FROM tmp;
-key|val
----+---
+key|val|seq
+---+---+---
 (0 rows)
 
 
@@ -111,28 +111,28 @@ step ins1: INSERT INTO tmp VALUES (1, 's1');
 step b2: BEGIN;
 step ins2: INSERT INTO tmp VALUES (1, 's2');
 step sel1: SELECT * FROM tmp;
-key|val
----+---
-  1|s1 
+key|val|seq
+---+---+---
+  1|s1 |  1
 (1 row)
 
 step sel2: SELECT * FROM tmp;
-key|val
----+---
-  1|s2 
+key|val|seq
+---+---+---
+  1|s2 |  1
 (1 row)
 
 step sp2: SAVEPOINT sp;
 step r2: ROLLBACK;
 step sel1: SELECT * FROM tmp;
-key|val
----+---
-  1|s1 
+key|val|seq
+---+---+---
+  1|s1 |  1
 (1 row)
 
 step sel2: SELECT * FROM tmp;
-key|val
----+---
+key|val|seq
+---+---+---
 (0 rows)
 
 
@@ -142,39 +142,39 @@ step b2: BEGIN;
 step sp2: SAVEPOINT sp;
 step ins2: INSERT INTO tmp VALUES (1, 's2');
 step sel1: SELECT * FROM tmp;
-key|val
----+---
-  1|s1 
+key|val|seq
+---+---+---
+  1|s1 |  1
 (1 row)
 
 step sel2: SELECT * FROM tmp;
-key|val
----+---
-  1|s2 
+key|val|seq
+---+---+---
+  1|s2 |  1
 (1 row)
 
 step rsp2: ROLLBACK TO SAVEPOINT sp;
 step sel1: SELECT * FROM tmp;
-key|val
----+---
-  1|s1 
+key|val|seq
+---+---+---
+  1|s1 |  1
 (1 row)
 
 step sel2: SELECT * FROM tmp;
-key|val
----+---
+key|val|seq
+---+---+---
 (0 rows)
 
 step r2: ROLLBACK;
 step sel1: SELECT * FROM tmp;
-key|val
----+---
-  1|s1 
+key|val|seq
+---+---+---
+  1|s1 |  1
 (1 row)
 
 step sel2: SELECT * FROM tmp;
-key|val
----+---
+key|val|seq
+---+---+---
 (0 rows)
 
 
@@ -186,27 +186,27 @@ step sp2: SAVEPOINT sp;
 step t2: TRUNCATE tmp;
 step rsp2: ROLLBACK TO SAVEPOINT sp;
 step sel1: SELECT * FROM tmp;
-key|val
----+---
-  1|s1 
+key|val|seq
+---+---+---
+  1|s1 |  1
 (1 row)
 
 step sel2: SELECT * FROM tmp;
-key|val
----+---
-  1|s2 
+key|val|seq
+---+---+---
+  1|s2 |  1
 (1 row)
 
 step r2: ROLLBACK;
 step sel1: SELECT * FROM tmp;
-key|val
----+---
-  1|s1 
+key|val|seq
+---+---+---
+  1|s1 |  1
 (1 row)
 
 step sel2: SELECT * FROM tmp;
-key|val
----+---
+key|val|seq
+---+---+---
 (0 rows)
 
 
@@ -338,40 +338,40 @@ step ins1: INSERT INTO tmp VALUES (1, 's1');
 step ins2: INSERT INTO tmp VALUES (1, 's2');
 step t2: TRUNCATE tmp;
 step sel1: SELECT * FROM tmp;
-key|val
----+---
-  1|s1 
+key|val|seq
+---+---+---
+  1|s1 |  1
 (1 row)
 
 step sel2: SELECT * FROM tmp;
-key|val
----+---
+key|val|seq
+---+---+---
 (0 rows)
 
 step ins2: INSERT INTO tmp VALUES (1, 's2');
 step t1: TRUNCATE tmp;
 step sel1: SELECT * FROM tmp;
-key|val
----+---
+key|val|seq
+---+---+---
 (0 rows)
 
 step sel2: SELECT * FROM tmp;
-key|val
----+---
-  1|s2 
+key|val|seq
+---+---+---
+  1|s2 |  2
 (1 row)
 
 step ins1: INSERT INTO tmp VALUES (1, 's1');
 step t2: TRUNCATE tmp;
 step sel1: SELECT * FROM tmp;
-key|val
----+---
-  1|s1 
+key|val|seq
+---+---+---
+  1|s1 |  2
 (1 row)
 
 step sel2: SELECT * FROM tmp;
-key|val
----+---
+key|val|seq
+---+---+---
 (0 rows)
 
 
@@ -408,15 +408,15 @@ regress_isolation_tablespace|       |base/NNN/tNNN_NNN
 (1 row)
 
 step sel1: SELECT * FROM tmp;
-key|val
----+---
-  1|s1 
+key|val|seq
+---+---+---
+  1|s1 |  1
 (1 row)
 
 step sel2: SELECT * FROM tmp;
-key|val
----+---
-  1|s2 
+key|val|seq
+---+---+---
+  1|s2 |  1
 (1 row)
 
 step reset_tblspace: ALTER TABLE tmp SET TABLESPACE pg_default;
@@ -574,9 +574,9 @@ Index Scan using tmp_val_idx on tmp
   Index Cond: (val = 's1'::text)   
 (2 rows)
 
-key|val
----+---
-  1|s1 
+key|val|seq
+---+---+---
+  1|s1 |  1
 (1 row)
 
 step ins2: INSERT INTO tmp VALUES (1, 's2');
@@ -593,9 +593,9 @@ Index Scan using tmp_val_idx on tmp
   Index Cond: (val = 's2'::text)   
 (2 rows)
 
-key|val
----+---
-  1|s2 
+key|val|seq
+---+---+---
+  1|s2 |  1
 (1 row)
 
 
@@ -616,9 +616,9 @@ Index Scan using tmp_val_idx on tmp
   Index Cond: (val = 's1'::text)   
 (2 rows)
 
-key|val
----+---
-  1|s1 
+key|val|seq
+---+---+---
+  1|s1 |  1
 (1 row)
 
 step sel2_idx: 
@@ -635,9 +635,9 @@ Seq Scan on tmp
   Filter: (val = 's2'::text)
 (3 rows)
 
-key|val
----+---
-  1|s2 
+key|val|seq
+---+---+---
+  1|s2 |  1
 (1 row)
 
 
@@ -658,9 +658,9 @@ Index Scan using tmp_val_idx on tmp
   Index Cond: (val = 's1'::text)   
 (2 rows)
 
-key|val
----+---
-  1|s1 
+key|val|seq
+---+---+---
+  1|s1 |  1
 (1 row)
 
 step sel2_idx: 
@@ -677,9 +677,9 @@ Seq Scan on tmp
   Filter: (val = 's2'::text)
 (3 rows)
 
-key|val
----+---
-  1|s2 
+key|val|seq
+---+---+---
+  1|s2 |  1
 (1 row)
 
 step reidx2: REINDEX INDEX tmp_val_idx;
@@ -696,9 +696,9 @@ Index Scan using tmp_val_idx on tmp
   Index Cond: (val = 's2'::text)   
 (2 rows)
 
-key|val
----+---
-  1|s2 
+key|val|seq
+---+---+---
+  1|s2 |  1
 (1 row)
 
 
@@ -719,9 +719,9 @@ Index Scan using tmp_val_idx on tmp
   Index Cond: (val = 's1'::text)   
 (2 rows)
 
-key|val
----+---
-  1|s1 
+key|val|seq
+---+---+---
+  1|s1 |  1
 (1 row)
 
 step sel2_idx: 
@@ -738,9 +738,9 @@ Seq Scan on tmp
   Filter: (val = 's2'::text)
 (3 rows)
 
-key|val
----+---
-  1|s2 
+key|val|seq
+---+---+---
+  1|s2 |  1
 (1 row)
 
 step analyze2: ANALYZE tmp;
@@ -758,9 +758,9 @@ Seq Scan on tmp
   Filter: (val = 's2'::text)
 (3 rows)
 
-key|val
----+---
-  1|s2 
+key|val|seq
+---+---+---
+  1|s2 |  1
 (1 row)
 
 step reidx2: REINDEX INDEX tmp_val_idx;
@@ -777,9 +777,9 @@ Index Scan using tmp_val_idx on tmp
   Index Cond: (val = 's2'::text)   
 (2 rows)
 
-key|val
----+---
-  1|s2 
+key|val|seq
+---+---+---
+  1|s2 |  1
 (1 row)
 
 
diff --git a/src/test/isolation/specs/global-temp.spec b/src/test/isolation/specs/global-temp.spec
index 850432fda4d..300a0314e8e 100644
--- a/src/test/isolation/specs/global-temp.spec
+++ b/src/test/isolation/specs/global-temp.spec
@@ -1,7 +1,7 @@
 # Test global temporary relations
 
 setup {
-  CREATE GLOBAL TEMP TABLE tmp (key int PRIMARY KEY, val text);
+  CREATE GLOBAL TEMP TABLE tmp (key int PRIMARY KEY, val text, seq serial);
 
   CREATE GLOBAL TEMP TABLE tmp_parted (key int PRIMARY KEY, val text) PARTITION BY LIST (key);
   CREATE GLOBAL TEMP TABLE tmp_p1 PARTITION OF tmp_parted FOR VALUES IN (1);
diff --git a/src/test/recovery/t/001_stream_rep.pl b/src/test/recovery/t/001_stream_rep.pl
index e578f1c6616..a840aafb588 100644
--- a/src/test/recovery/t/001_stream_rep.pl
+++ b/src/test/recovery/t/001_stream_rep.pl
@@ -106,8 +106,11 @@ like(
 	"Accessing GTT fails on standby 2");
 
 # Likewise, but for a sequence
-$node_primary->safe_psql('postgres',
-	"CREATE SEQUENCE seq1; SELECT nextval('seq1')");
+$node_primary->safe_psql('postgres', q{
+CREATE SEQUENCE seq1;
+SELECT nextval('seq1');
+CREATE GLOBAL TEMP SEQUENCE gtseq;
+});
 
 # Wait for standbys to catch up
 $node_primary->wait_for_replay_catchup($node_standby_1);
@@ -121,6 +124,20 @@ $result = $node_standby_2->safe_psql('postgres', "SELECT * FROM seq1");
 print "standby 2: $result\n";
 is($result, qq(33|0|t), 'check streamed sequence content on standby 2');
 
+($ret, $stdout, $stderr) = $node_standby_1->psql(
+	'postgres', 'SELECT * FROM gtseq');
+like(
+	$stderr,
+	qr/ERROR:  cannot access temporary or unlogged relations during recovery/,
+	"Accessing global temporary sequence fails on standby 1");
+
+($ret, $stdout, $stderr) = $node_standby_2->psql(
+	'postgres', 'SELECT * FROM gtseq');
+like(
+	$stderr,
+	qr/ERROR:  cannot access temporary or unlogged relations during recovery/,
+	"Accessing global temporary sequence fails on standby 2");
+
 # Check pg_sequence_last_value() returns NULL for unlogged sequence on standby
 $node_primary->safe_psql('postgres',
 	"CREATE UNLOGGED SEQUENCE ulseq; SELECT nextval('ulseq')");
@@ -131,6 +148,13 @@ is( $node_standby_1->safe_psql(
 	't',
 	'pg_sequence_last_value() on unlogged sequence on standby 1');
 
+# Likewise for global temporary sequence
+is( $node_standby_1->safe_psql(
+		'postgres',
+		"SELECT pg_sequence_last_value('gtseq'::regclass) IS NULL"),
+	't',
+	'pg_sequence_last_value() on global temporary sequence on standby 1');
+
 # Check that only READ-only queries can run on standbys
 is($node_standby_1->psql('postgres', 'INSERT INTO tab_int VALUES (1)'),
 	3, 'read-only queries on standby 1');
diff --git a/src/test/regress/expected/global_temp.out b/src/test/regress/expected/global_temp.out
index 44ad16684a8..abbd5cde670 100644
--- a/src/test/regress/expected/global_temp.out
+++ b/src/test/regress/expected/global_temp.out
@@ -13,16 +13,17 @@ CREATE GLOBAL TEMP TABLE pg_temp.tmp1 (a int); -- fail
 ERROR:  cannot create global temporary relation in temporary schema
 LINE 1: CREATE GLOBAL TEMP TABLE pg_temp.tmp1 (a int);
                                  ^
-CREATE GLOBAL TEMP TABLE tmp1 (a int PRIMARY KEY, b text);
+CREATE GLOBAL TEMP TABLE tmp1 (a int PRIMARY KEY, b text, c serial);
 CREATE SCHEMA global_temp_xxx CREATE GLOBAL TEMP TABLE tmp2 (a int);
 CREATE SCHEMA global_temp_yyy;
 CREATE GLOBAL TEMP TABLE global_temp_yyy.tmp3 (a int);
 \d tmp1
-  Global temporary table "global_temp_tests.tmp1"
- Column |  Type   | Collation | Nullable | Default 
---------+---------+-----------+----------+---------
+              Global temporary table "global_temp_tests.tmp1"
+ Column |  Type   | Collation | Nullable |             Default             
+--------+---------+-----------+----------+---------------------------------
  a      | integer |           | not null | 
  b      | text    |           |          | 
+ c      | integer |           | not null | nextval('tmp1_c_seq'::regclass)
 Indexes:
     "tmp1_pkey" PRIMARY KEY, btree (a)
 
@@ -54,16 +55,16 @@ NOTICE:  drop cascades to table global_temp_yyy.tmp3
 -- Basic tests
 INSERT INTO tmp1 VALUES (1, 'xxx');
 SELECT * FROM tmp1;
- a |  b  
----+-----
- 1 | xxx
+ a |  b  | c 
+---+-----+---
+ 1 | xxx | 1
 (1 row)
 
 \c
 SET search_path = global_temp_tests;
 SELECT * FROM tmp1;
- a | b 
----+---
+ a | b | c 
+---+---+---
 (0 rows)
 
 -- Test pg_gtr_info() and pg_gtrs_in_use()
@@ -81,8 +82,8 @@ SELECT * FROM pg_gtrs_in_use();
 (0 rows)
 
 SELECT * FROM tmp1;
- a | b 
----+---
+ a | b | c 
+---+---+---
 (0 rows)
 
 SELECT c.relfilenode = c.oid,
@@ -119,9 +120,9 @@ SELECT * FROM tmp1 WHERE a = 1;
 (2 rows)
 
 SELECT * FROM tmp1 WHERE a = 1;
- a |  b  
----+-----
- 1 | xxx
+ a |  b  | c 
+---+-----+---
+ 1 | xxx | 1
 (1 row)
 
 RESET enable_seqscan;
@@ -137,9 +138,9 @@ SELECT * FROM tmp1 WHERE b = 'xxx';
 (2 rows)
 
 SELECT * FROM tmp1 WHERE b = 'xxx';
- a |  b  
----+-----
- 1 | xxx
+ a |  b  | c 
+---+-----+---
+ 1 | xxx | 1
 (1 row)
 
 RESET enable_seqscan;
@@ -200,8 +201,8 @@ ERROR:  ON COMMIT DROP cannot be used on global temporary tables
 -- Two-phase commit not allowed with global temp tables
 BEGIN;
 SELECT * FROM tmp1;
- a | b 
----+---
+ a | b | c 
+---+---+---
 (0 rows)
 
 PREPARE TRANSACTION 'twophase'; -- fail
@@ -447,64 +448,64 @@ INSERT INTO tmp1 VALUES (1, 'xxx');
 BEGIN;
 TRUNCATE tmp1;
 SELECT * FROM tmp1;
- a | b 
----+---
+ a | b | c 
+---+---+---
 (0 rows)
 
 ROLLBACK;
 SELECT * FROM tmp1;
- a |  b  
----+-----
- 1 | xxx
+ a |  b  | c 
+---+-----+---
+ 1 | xxx | 1
 (1 row)
 
 BEGIN;
 SAVEPOINT sp1;
 TRUNCATE tmp1;
 SELECT * FROM tmp1;
- a | b 
----+---
+ a | b | c 
+---+---+---
 (0 rows)
 
 RELEASE sp1;
 SELECT * FROM tmp1;
- a | b 
----+---
+ a | b | c 
+---+---+---
 (0 rows)
 
 ROLLBACK;
 SELECT * FROM tmp1;
- a |  b  
----+-----
- 1 | xxx
+ a |  b  | c 
+---+-----+---
+ 1 | xxx | 1
 (1 row)
 
 BEGIN;
 SAVEPOINT sp1;
 TRUNCATE tmp1;
 SELECT * FROM tmp1;
- a | b 
----+---
+ a | b | c 
+---+---+---
 (0 rows)
 
 ROLLBACK TO sp1;
 SELECT * FROM tmp1;
- a |  b  
----+-----
- 1 | xxx
+ a |  b  | c 
+---+-----+---
+ 1 | xxx | 1
 (1 row)
 
 COMMIT;
 SELECT * FROM tmp1;
- a |  b  
----+-----
- 1 | xxx
+ a |  b  | c 
+---+-----+---
+ 1 | xxx | 1
 (1 row)
 
 TRUNCATE tmp1;
 SELECT * FROM tmp1;
- a | b 
----+---
+ a | b | c 
+---+---+---
 (0 rows)
 
 -- Test REPACK -- relfilenode only changes locally
@@ -571,26 +572,27 @@ SELECT c.relname,
   FROM pg_gtrs_in_use() t LEFT JOIN pg_class c ON c.oid = t.oid
  WHERE c.relname !~ 'pg_toast_'
  ORDER BY c.relname;
-  relname  | ?column? | ?column? 
------------+----------+----------
- tmp1      | t        | t
- tmp1_pkey | t        | t
-(2 rows)
+  relname   | ?column? | ?column? 
+------------+----------+----------
+ tmp1       | t        | t
+ tmp1_c_seq | t        | t
+ tmp1_pkey  | t        | t
+(3 rows)
 
 -- Test view creation
 CREATE VIEW v AS SELECT * FROM tmp1;
 SELECT * FROM v;
- a |  b  
----+-----
- 1 | xxx
+ a |  b  | c 
+---+-----+---
+ 1 | xxx | 1
 (1 row)
 
 DROP VIEW v;
 CREATE TEMP VIEW v AS SELECT * FROM tmp1;
 SELECT * FROM v;
- a |  b  
----+-----
- 1 | xxx
+ a |  b  | c 
+---+-----+---
+ 1 | xxx | 1
 (1 row)
 
 DROP VIEW v;
@@ -630,3 +632,147 @@ WHERE oid = 'tmp1'::regclass;
  t      | t
 (1 row)
 
+-- Test global temp sequence
+CREATE GLOBAL TEMP SEQUENCE s MINVALUE 100 MAXVALUE 130 INCREMENT 10 START WITH 110 CYCLE;
+\d s
+         Global temporary sequence "global_temp_tests.s"
+  Type  | Start | Minimum | Maximum | Increment | Cycles? | Cache 
+--------+-------+---------+---------+-----------+---------+-------
+ bigint |   110 |     100 |     130 |        10 | yes     |     1
+
+SELECT nextval('s') FROM generate_series(1, 5);
+ nextval 
+---------
+     110
+     120
+     130
+     100
+     110
+(5 rows)
+
+\c
+SET search_path = global_temp_tests;
+SELECT nextval('s') FROM generate_series(1, 5);
+ nextval 
+---------
+     110
+     120
+     130
+     100
+     110
+(5 rows)
+
+-- Test that sequence initialization survives ROLLBACK
+\c
+SET search_path = global_temp_tests;
+BEGIN;
+SELECT nextval('s') FROM generate_series(1, 2);
+ nextval 
+---------
+     110
+     120
+(2 rows)
+
+ROLLBACK;
+SELECT nextval('s') FROM generate_series(1, 2);
+ nextval 
+---------
+     130
+     100
+(2 rows)
+
+\c
+SET search_path = global_temp_tests;
+BEGIN;
+SAVEPOINT sp;
+SELECT nextval('s') FROM generate_series(1, 2);
+ nextval 
+---------
+     110
+     120
+(2 rows)
+
+ROLLBACK TO sp;
+SELECT nextval('s') FROM generate_series(1, 3);
+ nextval 
+---------
+     130
+     100
+     110
+(3 rows)
+
+ROLLBACK;
+SELECT nextval('s') FROM generate_series(1, 2);
+ nextval 
+---------
+     120
+     130
+(2 rows)
+
+-- Test lastval() after ROLLBACK of sequence initialization
+\c
+SET search_path = global_temp_tests;
+BEGIN;
+SELECT nextval('s') FROM generate_series(1, 2);
+ nextval 
+---------
+     110
+     120
+(2 rows)
+
+ROLLBACK;
+SELECT lastval();
+ lastval 
+---------
+     120
+(1 row)
+
+\c
+SET search_path = global_temp_tests;
+BEGIN;
+SAVEPOINT sp;
+SELECT nextval('s') FROM generate_series(1, 2);
+ nextval 
+---------
+     110
+     120
+(2 rows)
+
+ROLLBACK TO sp;
+SELECT lastval();
+ lastval 
+---------
+     120
+(1 row)
+
+ROLLBACK;
+SELECT lastval();
+ lastval 
+---------
+     120
+(1 row)
+
+SELECT nextval('s') FROM generate_series(1, 2);
+ nextval 
+---------
+     130
+     100
+(2 rows)
+
+-- Test rollback of sequence creation
+\c
+SET search_path = global_temp_tests;
+BEGIN;
+CREATE GLOBAL TEMP SEQUENCE s2;
+SELECT oid::regclass FROM pg_gtrs_in_use();
+ oid 
+-----
+ s2
+(1 row)
+
+ROLLBACK;
+SELECT oid::regclass FROM pg_gtrs_in_use();
+ oid 
+-----
+(0 rows)
+
diff --git a/src/test/regress/sql/global_temp.sql b/src/test/regress/sql/global_temp.sql
index e6d386b2b52..af0073ad242 100644
--- a/src/test/regress/sql/global_temp.sql
+++ b/src/test/regress/sql/global_temp.sql
@@ -11,7 +11,7 @@ SET ROLE regress_global_temp_user;
 
 -- Test table creation
 CREATE GLOBAL TEMP TABLE pg_temp.tmp1 (a int); -- fail
-CREATE GLOBAL TEMP TABLE tmp1 (a int PRIMARY KEY, b text);
+CREATE GLOBAL TEMP TABLE tmp1 (a int PRIMARY KEY, b text, c serial);
 CREATE SCHEMA global_temp_xxx CREATE GLOBAL TEMP TABLE tmp2 (a int);
 CREATE SCHEMA global_temp_yyy;
 CREATE GLOBAL TEMP TABLE global_temp_yyy.tmp3 (a int);
@@ -347,3 +347,57 @@ SELECT EXISTS (SELECT 1 FROM pg_class t WHERE t.oid = c.reltoastrelid),
        EXISTS (SELECT 1 FROM pg_gtrs_in_use() t WHERE t.oid = c.reltoastrelid) AS used
 FROM pg_class c
 WHERE oid = 'tmp1'::regclass;
+
+-- Test global temp sequence
+CREATE GLOBAL TEMP SEQUENCE s MINVALUE 100 MAXVALUE 130 INCREMENT 10 START WITH 110 CYCLE;
+\d s
+SELECT nextval('s') FROM generate_series(1, 5);
+\c
+SET search_path = global_temp_tests;
+SELECT nextval('s') FROM generate_series(1, 5);
+
+-- Test that sequence initialization survives ROLLBACK
+\c
+SET search_path = global_temp_tests;
+BEGIN;
+SELECT nextval('s') FROM generate_series(1, 2);
+ROLLBACK;
+SELECT nextval('s') FROM generate_series(1, 2);
+
+\c
+SET search_path = global_temp_tests;
+BEGIN;
+SAVEPOINT sp;
+SELECT nextval('s') FROM generate_series(1, 2);
+ROLLBACK TO sp;
+SELECT nextval('s') FROM generate_series(1, 3);
+ROLLBACK;
+SELECT nextval('s') FROM generate_series(1, 2);
+
+-- Test lastval() after ROLLBACK of sequence initialization
+\c
+SET search_path = global_temp_tests;
+BEGIN;
+SELECT nextval('s') FROM generate_series(1, 2);
+ROLLBACK;
+SELECT lastval();
+
+\c
+SET search_path = global_temp_tests;
+BEGIN;
+SAVEPOINT sp;
+SELECT nextval('s') FROM generate_series(1, 2);
+ROLLBACK TO sp;
+SELECT lastval();
+ROLLBACK;
+SELECT lastval();
+SELECT nextval('s') FROM generate_series(1, 2);
+
+-- Test rollback of sequence creation
+\c
+SET search_path = global_temp_tests;
+BEGIN;
+CREATE GLOBAL TEMP SEQUENCE s2;
+SELECT oid::regclass FROM pg_gtrs_in_use();
+ROLLBACK;
+SELECT oid::regclass FROM pg_gtrs_in_use();
diff --git a/src/test/subscription/t/039_global_temp.pl b/src/test/subscription/t/039_global_temp.pl
index 9c1851cb909..cc8dfbd8222 100644
--- a/src/test/subscription/t/039_global_temp.pl
+++ b/src/test/subscription/t/039_global_temp.pl
@@ -18,18 +18,21 @@ my $node_subscriber = PostgreSQL::Test::Cluster->new('subscriber');
 $node_subscriber->init;
 $node_subscriber->start;
 
-# Create tables on publisher
+# Create relations on publisher
 $node_publisher->safe_psql('postgres', qq(
 	CREATE TABLE perm_test (a int);
 	CREATE TABLE gtt_test (a int);
 	INSERT INTO perm_test VALUES (1);
 	INSERT INTO gtt_test VALUES (1);
+	CREATE SEQUENCE gt_test_seq;
 ));
 
-# Create same tables on subscriber, except make gtt_test global temporary
+# Create same relations on subscriber, except make gtt_test and gt_test_seq
+# global temporary
 $node_subscriber->safe_psql('postgres', qq(
 	CREATE TABLE perm_test (a int);
 	CREATE GLOBAL TEMP TABLE gtt_test (a int);
+	CREATE GLOBAL TEMP SEQUENCE gt_test_seq;
 ));
 
 # Setup logical replication on publisher
@@ -37,6 +40,7 @@ my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
 $node_publisher->safe_psql('postgres', qq(
 	CREATE PUBLICATION regress_perm_pub FOR TABLE perm_test;
 	CREATE PUBLICATION regress_gtt_pub FOR TABLE gtt_test;
+	CREATE PUBLICATION regress_gt_seq_pub FOR ALL SEQUENCES;
 ));
 
 # Setup logical replication for GTT on subscriber -- should fail
@@ -51,6 +55,18 @@ like(
 .*DETAIL:  This operation is not supported for global temporary relations\./,
 	"could not use global temporary table as subscriber");
 
+# Likewise for global temporary sequence
+($ret, $stdout, $stderr) =
+	$node_subscriber->psql('postgres', qq(
+		CREATE SUBSCRIPTION regress_sub
+			CONNECTION '$publisher_connstr' PUBLICATION regress_gt_seq_pub;
+));
+like(
+	$stderr,
+	qr/ERROR:  cannot use relation "public\.gt_test_seq" as logical replication target
+.*DETAIL:  This operation is not supported for global temporary relations\./,
+	"could not use global temporary sequence as subscriber");
+
 # Setup logical replication for permanent table -- OK
 $node_subscriber->safe_psql('postgres', qq(
 	CREATE SUBSCRIPTION regress_sub
@@ -67,6 +83,16 @@ like(
 .*DETAIL:  This operation is not supported for global temporary relations\./,
 	"could not use global temporary table as subscriber");
 
+# Likewise for global temporary sequence
+($ret, $stdout, $stderr) =
+	$node_subscriber->psql('postgres',
+		"ALTER SUBSCRIPTION regress_sub SET PUBLICATION regress_gt_seq_pub;");
+like(
+	$stderr,
+	qr/ERROR:  cannot use relation "public\.gt_test_seq" as logical replication target
+.*DETAIL:  This operation is not supported for global temporary relations\./,
+	"could not use global temporary table as subscriber");
+
 # Replace the subscriber table with a permanent one and try again
 $node_subscriber->safe_psql('postgres', qq(
 	DROP TABLE gtt_test;
-- 
2.51.0

