From b6a239589a0598fe9091ae51de6debb1b96f7eac 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 v10 04/11] 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           |  68 ++++--
 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           |   3 +-
 src/include/commands/sequence.h             |   1 +
 src/test/isolation/expected/global-temp.out | 176 ++++++++--------
 src/test/isolation/specs/global-temp.spec   |   2 +-
 src/test/regress/expected/global_temp.out   | 218 ++++++++++++++++----
 src/test/regress/sql/global_temp.sql        |  47 ++++-
 src/test/subscription/t/039_global_temp.pl  |  30 ++-
 15 files changed, 505 insertions(+), 169 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 203f318a50b..6ff85777f62 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 "lib/dshash.h"
 #include "miscadmin.h"
@@ -531,7 +532,7 @@ gtr_init_usage_tables(void)
  *	have usage records for this relation.
  */
 static void
-gtr_record_usage(Oid relid)
+gtr_record_usage(Oid relid, char relkind)
 {
 	GtrUsageEntry *local_entry;
 	GtrSharedUsageKey key;
@@ -546,13 +547,23 @@ gtr_record_usage(Oid relid)
 	if (found)
 		return;					/* already recorded, nothing to do */
 
-	/* Record the usage as starting in the current subtransaction */
-	local_entry->started_subid = GetCurrentSubTransactionId();
+	/*
+	 * For a sequence, the storage is created non-transactionally, and isn't
+	 * deleted on (sub)rollback, and so the sequence is not invalidated and
+	 * reinitialized after (sub)rollback.  Do the same for the usage record,
+	 * so that we always regard a sequence with storage as in use.  Otherwise,
+	 * for any other relkind, record the usage as starting in the current
+	 * subtransaction, and flag it for eoxact cleanup.
+	 */
+	if (relkind == RELKIND_SEQUENCE)
+		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);
-
 	/* Add/update shared usage entry */
 	key.dbid = MyDatabaseId;
 	key.relid = relid;
@@ -700,12 +711,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 for sequences).
+ *
  *	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;
 
@@ -734,9 +751,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
@@ -748,10 +776,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);
+	}
 }
 
 /*
@@ -828,7 +856,13 @@ InitGlobalTempRelation(Relation relation)
 		 hash_search(gtr_local_storage,
 					 &relation->rd_locator, HASH_FIND, NULL) == 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,
@@ -839,7 +873,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
@@ -878,6 +912,10 @@ 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 */
@@ -899,7 +937,7 @@ void
 TrackGlobalTempRelation(Relation relation)
 {
 	/* Record our use of the relation */
-	gtr_record_usage(relation->rd_id);
+	gtr_record_usage(relation->rd_id, relation->rd_rel->relkind);
 }
 
 /*
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 d83616a8507..69e80673427 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -493,7 +493,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 525cd102a86..fcdd5d03e70 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -19362,7 +19362,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 c62ae57e9f5..b32e61301ac 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1807,6 +1807,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 bc74d8d546a..2611c2dc791 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -3813,14 +3813,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> */
@@ -3841,9 +3844,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 a2d9163f2f1..9d33fa89fda 100644
--- a/src/include/catalog/global_temp.h
+++ b/src/include/catalog/global_temp.h
@@ -17,7 +17,8 @@
 #include "utils/rel.h"
 
 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);
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 3b118822d6d..9383e599dfa 100644
--- a/src/test/isolation/expected/global-temp.out
+++ b/src/test/isolation/expected/global-temp.out
@@ -4,15 +4,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)
 
 
@@ -41,28 +41,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)
 
 
@@ -71,27 +71,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)
 
 
@@ -100,28 +100,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)
 
 
@@ -131,39 +131,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)
 
 
@@ -175,27 +175,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)
 
 
@@ -310,9 +310,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');
@@ -329,9 +329,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)
 
 
@@ -352,9 +352,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: 
@@ -371,9 +371,9 @@ Seq Scan on tmp
   Filter: (val = 's2'::text)
 (3 rows)
 
-key|val
----+---
-  1|s2 
+key|val|seq
+---+---+---
+  1|s2 |  1
 (1 row)
 
 
@@ -394,9 +394,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: 
@@ -413,9 +413,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;
@@ -432,8 +432,8 @@ 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 1133c0a5374..5d8a559eff4 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/regress/expected/global_temp.out b/src/test/regress/expected/global_temp.out
index e3749374dca..3e2a9b219e7 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 index
@@ -78,9 +79,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;
@@ -96,9 +97,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;
@@ -145,8 +146,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
@@ -279,83 +280,210 @@ 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 view creation
 INSERT INTO tmp1 VALUES (1, 'xxx');
 CREATE VIEW v AS SELECT * FROM tmp1;
 SELECT * FROM v;
- a |  b  
----+-----
- 1 | xxx
+ a |  b  | c 
+---+-----+---
+ 1 | xxx | 2
 (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 | 2
 (1 row)
 
 DROP VIEW v;
 CREATE GLOBAL TEMP VIEW v AS SELECT * FROM tmp1; -- fail
 ERROR:  views cannot be global temporary because they do not have storage
+-- 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)
+
diff --git a/src/test/regress/sql/global_temp.sql b/src/test/regress/sql/global_temp.sql
index dc9d1fb392a..799e474a99a 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);
@@ -197,3 +197,48 @@ SELECT * FROM v;
 DROP VIEW v;
 
 CREATE GLOBAL TEMP VIEW v AS SELECT * FROM tmp1; -- fail
+
+-- 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);
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.43.0

