From bd4cd930dc688c5f6364b8e30a464aad170fb9d9 Mon Sep 17 00:00:00 2001
From: Dean Rasheed <dean.a.rasheed@gmail.com>
Date: Tue, 9 Jun 2026 13:50:47 +0100
Subject: [PATCH v10 03/11] Add support for indexes on global temporary tables.

Indexes on global temporary tables are themselves global temporary.
When an index is defined, it is fully populated in the session that
defined it, but in any other active sessions an empty index is built,
and if those sessions already have data in their copies of the table,
the index is marked invalid, and is only usable if a REINDEX is
executed in those sessions.

All types of index are supported, including unique indexes, which
enables foreign keys between global temporary tables.

As with indexes on local temporary tables, concurrent index
build/reindex/drop is not supported.

Currently the "invalid" marking of a global temporary index is not
persisted in the database, so it can get lost after a relcache
invalidation. That issue will be addressed in a subsequent commit.
---
 contrib/bloom/blinsert.c                      |   6 +-
 contrib/bloom/bloom.h                         |   2 +-
 doc/src/sgml/indexam.sgml                     |  17 +-
 doc/src/sgml/ref/create_index.sgml            |  16 ++
 src/backend/access/brin/brin.c                |   4 +-
 src/backend/access/gin/gininsert.c            |   8 +-
 src/backend/access/gist/gist.c                |   6 +-
 src/backend/access/hash/hash.c                |  12 +-
 src/backend/access/nbtree/nbtree.c            |   6 +-
 src/backend/access/spgist/spginsert.c         |   6 +-
 src/backend/access/transam/xloginsert.c       |   3 +-
 src/backend/catalog/global_temp.c             |  29 +++
 src/backend/catalog/index.c                   |   5 +-
 src/backend/commands/indexcmds.c              |  27 ++-
 src/backend/commands/tablecmds.c              |  25 ++-
 src/backend/optimizer/plan/planner.c          |   1 +
 src/bin/scripts/reindexdb.c                   |   4 +
 src/include/access/amapi.h                    |   4 +-
 src/include/access/brin_internal.h            |   2 +-
 src/include/access/gin_private.h              |   2 +-
 src/include/access/gist_private.h             |   2 +-
 src/include/access/hash.h                     |   2 +-
 src/include/access/nbtree.h                   |   2 +-
 src/include/access/spgist.h                   |   2 +-
 src/include/nodes/parsenodes.h                |   3 +-
 src/test/isolation/expected/global-temp.out   | 171 +++++++++++++++++-
 src/test/isolation/specs/global-temp.spec     |  38 +++-
 .../modules/dummy_index_am/dummy_index_am.c   |   4 +-
 src/test/regress/expected/global_temp.out     | 158 +++++++++++-----
 src/test/regress/sql/global_temp.sql          |  41 ++++-
 30 files changed, 491 insertions(+), 117 deletions(-)

diff --git a/contrib/bloom/blinsert.c b/contrib/bloom/blinsert.c
index df24856d9ae..804c77ae768 100644
--- a/contrib/bloom/blinsert.c
+++ b/contrib/bloom/blinsert.c
@@ -159,13 +159,13 @@ blbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 }
 
 /*
- * Build an empty bloom index in the initialization fork.
+ * Build an empty bloom index in the specified fork.
  */
 void
-blbuildempty(Relation index)
+blbuildempty(Relation index, ForkNumber forknum)
 {
 	/* Initialize the meta page */
-	BloomInitMetapage(index, INIT_FORKNUM);
+	BloomInitMetapage(index, forknum);
 }
 
 /*
diff --git a/contrib/bloom/bloom.h b/contrib/bloom/bloom.h
index 9a4125bdfb1..4f00584006b 100644
--- a/contrib/bloom/bloom.h
+++ b/contrib/bloom/bloom.h
@@ -197,7 +197,7 @@ extern void blrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 extern void blendscan(IndexScanDesc scan);
 extern IndexBuildResult *blbuild(Relation heap, Relation index,
 								 struct IndexInfo *indexInfo);
-extern void blbuildempty(Relation index);
+extern void blbuildempty(Relation index, ForkNumber forknum);
 extern IndexBulkDeleteResult *blbulkdelete(IndexVacuumInfo *info,
 										   IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback,
 										   void *callback_state);
diff --git a/doc/src/sgml/indexam.sgml b/doc/src/sgml/indexam.sgml
index f48da318530..6649bdd6cd1 100644
--- a/doc/src/sgml/indexam.sgml
+++ b/doc/src/sgml/indexam.sgml
@@ -312,15 +312,20 @@ ambuild (Relation heapRelation,
    this flag set to <literal>false</literal>.
   </para>
 
-  <para>
+  <para id="indexam-ambuildempty">
 <programlisting>
 void
-ambuildempty (Relation indexRelation);
+ambuildempty (Relation indexRelation,
+              ForkNumber forknum);
 </programlisting>
-   Build an empty index, and write it to the initialization fork (<symbol>INIT_FORKNUM</symbol>)
-   of the given relation.  This method is called only for unlogged indexes; the
-   empty index written to the initialization fork will be copied over the main
-   relation fork on each server restart.
+   Build an empty index, and write it to the specified fork of the given
+   relation.  This method is called for global temporary indexes and for
+   unlogged indexes.  For global temporary indexes, the empty index is written
+   to the main relation fork (<symbol>MAIN_FORKNUM</symbol>) in session-local
+   storage the first time the index is accessed in the session.  For unlogged
+   indexes, the empty index is written to the initialization fork
+   (<symbol>INIT_FORKNUM</symbol>) and then copied over the main relation
+   fork on each server restart.
   </para>
 
   <para>
diff --git a/doc/src/sgml/ref/create_index.sgml b/doc/src/sgml/ref/create_index.sgml
index bb7505d171b..2dac47c0499 100644
--- a/doc/src/sgml/ref/create_index.sgml
+++ b/doc/src/sgml/ref/create_index.sgml
@@ -770,6 +770,22 @@ Indexes:
    specified.
   </para>
 
+  <para>
+   When <literal>CREATE INDEX</literal> is invoked on a global temporary
+   table, the index is initially only populated in the session that executed
+   <literal>CREATE INDEX</literal>.  Other active sessions will see the
+   index definition and automatically build an empty index using the
+   <link linkend="indexam-ambuildempty"><literal>ambuildempty</literal></link>
+   index access method, but if another active session already has data in the
+   table, the index will be marked invalid in that session, and will not be
+   usable unless <link linkend="sql-reindex"><command>REINDEX</command></link>
+   is used to rebuild it in that session.  Other active sessions that have not
+   yet inserted data into the table will be able to use the index normally;
+   each session's copy of the index will be populated as data is added to that
+   session's copy of the table.  Likewise for any future sessions started
+   after the index is defined.
+  </para>
+
   <para>
    For index methods that support ordered scans (currently, only B-tree),
    the optional clauses <literal>ASC</literal>, <literal>DESC</literal>, <literal>NULLS
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..94494c9d8fa 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1276,12 +1276,12 @@ brinbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 }
 
 void
-brinbuildempty(Relation index)
+brinbuildempty(Relation index, ForkNumber forknum)
 {
 	Buffer		metabuf;
 
 	/* An empty BRIN index has a metapage only. */
-	metabuf = ExtendBufferedRel(BMR_REL(index), INIT_FORKNUM, NULL,
+	metabuf = ExtendBufferedRel(BMR_REL(index), forknum, NULL,
 								EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK);
 
 	/* Initialize and xlog metabuffer. */
diff --git a/src/backend/access/gin/gininsert.c b/src/backend/access/gin/gininsert.c
index 37f689a2cac..618209eb6e4 100644
--- a/src/backend/access/gin/gininsert.c
+++ b/src/backend/access/gin/gininsert.c
@@ -806,18 +806,18 @@ ginbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 }
 
 /*
- *	ginbuildempty() -- build an empty gin index in the initialization fork
+ *	ginbuildempty() -- build an empty gin index in the specified fork
  */
 void
-ginbuildempty(Relation index)
+ginbuildempty(Relation index, ForkNumber forknum)
 {
 	Buffer		RootBuffer,
 				MetaBuffer;
 
 	/* An empty GIN index has two pages. */
-	MetaBuffer = ExtendBufferedRel(BMR_REL(index), INIT_FORKNUM, NULL,
+	MetaBuffer = ExtendBufferedRel(BMR_REL(index), forknum, NULL,
 								   EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK);
-	RootBuffer = ExtendBufferedRel(BMR_REL(index), INIT_FORKNUM, NULL,
+	RootBuffer = ExtendBufferedRel(BMR_REL(index), forknum, NULL,
 								   EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK);
 
 	/* Initialize and xlog metabuffer and root buffer. */
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index 8565e225be7..9eae449786b 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -134,15 +134,15 @@ createTempGistContext(void)
 }
 
 /*
- *	gistbuildempty() -- build an empty gist index in the initialization fork
+ *	gistbuildempty() -- build an empty gist index in the specified fork
  */
 void
-gistbuildempty(Relation index)
+gistbuildempty(Relation index, ForkNumber forknum)
 {
 	Buffer		buffer;
 
 	/* Initialize the root page */
-	buffer = ExtendBufferedRel(BMR_REL(index), INIT_FORKNUM, NULL,
+	buffer = ExtendBufferedRel(BMR_REL(index), forknum, NULL,
 							   EB_SKIP_EXTENSION_LOCK | EB_LOCK_FIRST);
 
 	/* Initialize and xlog buffer */
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index b2e34d2d45e..8271af63220 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -175,10 +175,10 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * (Size) 1024) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
-		sort_threshold = Min(sort_threshold, NBuffers);
-	else
+	if (RelationUsesLocalBuffers(index))
 		sort_threshold = Min(sort_threshold, NLocBuffer);
+	else
+		sort_threshold = Min(sort_threshold, NBuffers);
 
 	if (num_buckets >= sort_threshold)
 		buildstate.spool = _h_spoolinit(heap, index, num_buckets);
@@ -215,12 +215,12 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 }
 
 /*
- *	hashbuildempty() -- build an empty hash index in the initialization fork
+ *	hashbuildempty() -- build an empty hash index in the specified fork
  */
 void
-hashbuildempty(Relation index)
+hashbuildempty(Relation index, ForkNumber forknum)
 {
-	_hash_init(index, 0, INIT_FORKNUM);
+	_hash_init(index, 0, forknum);
 }
 
 /*
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index 0abdd7b49f5..39d1070bad9 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -177,16 +177,16 @@ bthandler(PG_FUNCTION_ARGS)
 }
 
 /*
- *	btbuildempty() -- build an empty btree index in the initialization fork
+ *	btbuildempty() -- build an empty btree index in the specified fork
  */
 void
-btbuildempty(Relation index)
+btbuildempty(Relation index, ForkNumber forknum)
 {
 	bool		allequalimage = _bt_allequalimage(index, false);
 	BulkWriteState *bulkstate;
 	BulkWriteBuffer metabuf;
 
-	bulkstate = smgr_bulk_start_rel(index, INIT_FORKNUM);
+	bulkstate = smgr_bulk_start_rel(index, forknum);
 
 	/* Construct metapage. */
 	metabuf = smgr_bulk_get_buf(bulkstate);
diff --git a/src/backend/access/spgist/spginsert.c b/src/backend/access/spgist/spginsert.c
index 780ef646a54..1a14f84174a 100644
--- a/src/backend/access/spgist/spginsert.c
+++ b/src/backend/access/spgist/spginsert.c
@@ -148,15 +148,15 @@ spgbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 }
 
 /*
- * Build an empty SPGiST index in the initialization fork
+ * Build an empty SPGiST index in the specified fork
  */
 void
-spgbuildempty(Relation index)
+spgbuildempty(Relation index, ForkNumber forknum)
 {
 	BulkWriteState *bulkstate;
 	BulkWriteBuffer buf;
 
-	bulkstate = smgr_bulk_start_rel(index, INIT_FORKNUM);
+	bulkstate = smgr_bulk_start_rel(index, forknum);
 
 	/* Construct metapage. */
 	buf = smgr_bulk_get_buf(bulkstate);
diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c
index c9aff944a2e..6c9e36bbadf 100644
--- a/src/backend/access/transam/xloginsert.c
+++ b/src/backend/access/transam/xloginsert.c
@@ -560,7 +560,8 @@ XLogSimpleInsertInt64(RmgrId rmid, uint8 info, int64 value)
 XLogRecPtr
 XLogGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/catalog/global_temp.c b/src/backend/catalog/global_temp.c
index 38d84a7f2c4..203f318a50b 100644
--- a/src/backend/catalog/global_temp.c
+++ b/src/backend/catalog/global_temp.c
@@ -51,8 +51,10 @@
  */
 #include "postgres.h"
 
+#include "access/amapi.h"
 #include "access/genam.h"
 #include "access/parallel.h"
+#include "access/table.h"
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "access/xlogutils.h"
@@ -849,6 +851,33 @@ InitGlobalTempRelation(Relation relation)
 
 		if (relation->rd_rel->reloncommit == RELONCOMMIT_DELETE_ROWS)
 			register_on_commit_action(relation->rd_id, ONCOMMIT_DELETE_ROWS);
+
+		/*
+		 * If it's an index, build an empty index in the main fork.
+		 *
+		 * If the table is not empty (can happen if another session added the
+		 * index after we populated the table), then mark it as invalid.  The
+		 * user will need to do a REINDEX to build it.
+		 *
+		 * Note that this check for a non-empty table, using the block count,
+		 * might give a false positive, if the table contains deleted tuples,
+		 * which would force an unnecessary reindex, but it doesn't seem worth
+		 * the effort to do a more thorough check.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_INDEX)
+		{
+			Relation	heapRelation;
+			BlockNumber nblocks;
+
+			relation->rd_indam->ambuildempty(relation, MAIN_FORKNUM);
+
+			heapRelation = table_open(relation->rd_index->indrelid, AccessShareLock);
+			nblocks = RelationGetNumberOfBlocks(heapRelation);
+			table_close(heapRelation, AccessShareLock);
+
+			if (nblocks > 0)
+				relation->rd_index->indisvalid = false;
+		}
 	}
 
 	/* Track our use of the relation, if we haven't already done so */
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 8ae1642b9dd..199c8be332b 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2172,7 +2172,8 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert((get_rel_persistence(indexId) != RELPERSISTENCE_TEMP &&
+			get_rel_persistence(indexId) != RELPERSISTENCE_GLOBAL_TEMP) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -3141,7 +3142,7 @@ index_build(Relation heapRelation,
 	{
 		smgrcreate(RelationGetSmgr(indexRelation), INIT_FORKNUM, false);
 		log_smgrcreate(&indexRelation->rd_locator, INIT_FORKNUM);
-		indexRelation->rd_indam->ambuildempty(indexRelation);
+		indexRelation->rd_indam->ambuildempty(indexRelation, INIT_FORKNUM);
 	}
 
 	/*
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index baea283aed1..aa38c048630 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -627,8 +627,16 @@ DefineIndex(ParseState *pstate,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(tableId) != RELPERSISTENCE_TEMP)
-		concurrent = true;
+	if (stmt->concurrent)
+	{
+		char		relpersistence = get_rel_persistence(tableId);
+
+		if (relpersistence == RELPERSISTENCE_TEMP ||
+			relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			concurrent = false;
+		else
+			concurrent = true;
+	}
 	else
 		concurrent = false;
 
@@ -3136,7 +3144,8 @@ ReindexIndex(const ReindexStmt *stmt, const ReindexParams *params, bool isTopLev
 	if (relkind == RELKIND_PARTITIONED_INDEX)
 		ReindexPartitions(stmt, indOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 persistence != RELPERSISTENCE_TEMP)
+			 persistence != RELPERSISTENCE_TEMP &&
+			 persistence != RELPERSISTENCE_GLOBAL_TEMP)
 		ReindexRelationConcurrently(stmt, indOid, params);
 	else
 	{
@@ -3232,6 +3241,7 @@ static Oid
 ReindexTable(const ReindexStmt *stmt, const ReindexParams *params, bool isTopLevel)
 {
 	Oid			heapOid;
+	char		persistence;
 	bool		result;
 	const RangeVar *relation = stmt->relation;
 
@@ -3249,10 +3259,13 @@ ReindexTable(const ReindexStmt *stmt, const ReindexParams *params, bool isTopLev
 									   0,
 									   RangeVarCallbackMaintainsTable, NULL);
 
+	persistence = get_rel_persistence(heapOid);
+
 	if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
 		ReindexPartitions(stmt, heapOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+			 persistence != RELPERSISTENCE_TEMP &&
+			 persistence != RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		result = ReindexRelationConcurrently(stmt, heapOid, params);
 
@@ -3681,7 +3694,8 @@ ReindexMultipleInternal(const ReindexStmt *stmt, const List *relids, const Reind
 		Assert(!RELKIND_HAS_PARTITIONS(relkind));
 
 		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			relpersistence != RELPERSISTENCE_TEMP)
+			relpersistence != RELPERSISTENCE_TEMP &&
+			relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		{
 			ReindexParams newparams = *params;
 
@@ -4123,7 +4137,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		idx->amId = indexRel->rd_rel->relam;
 
 		/* This function shouldn't be called for temporary relations. */
-		if (indexRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+		if (indexRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+			indexRel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			elog(ERROR, "cannot reindex a temporary table concurrently");
 
 		pgstat_progress_start_command(PROGRESS_COMMAND_CREATE_INDEX, idx->tableId);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index a0f5dfecd0d..8587b42d665 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9835,8 +9835,9 @@ verifyNotNullPKCompatible(HeapTuple tuple, const char *colname)
  * ALTER TABLE ADD INDEX
  *
  * There is no such command in the grammar, but parse_utilcmd.c converts
- * UNIQUE and PRIMARY KEY constraints into AT_AddIndex subcommands.  This lets
- * us schedule creation of the index at the appropriate time during ALTER.
+ * UNIQUE, PRIMARY KEY, and EXCLUSION constraints into AT_AddIndex
+ * subcommands.  This lets us schedule creation of the index at the
+ * appropriate time during ALTER.
  *
  * Return value is the address of the new index.
  */
@@ -9855,6 +9856,26 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 	/* The IndexStmt has already been through transformIndexStmt */
 	Assert(stmt->transformed);
 
+	/*
+	 * Don't allow constraints to be added to global temporary tables that are
+	 * being used by other sessions, because we have no way to scan the local
+	 * storage of another backend to validate the constraint.
+	 *
+	 * XXX: Do we actually need to do this? Adding a PRIMARY KEY is already
+	 * blocked for an in-use GTT by the check in ATRewriteTables(), and maybe
+	 * it would be OK to allow UNIQUE and EXCLUSION constraints to be added,
+	 * since the indexes enforcing them would be marked invalid in any other
+	 * sessions with data in the table, so this would effectively just be
+	 * adding a NOT VALID constraint in those other sessions, which could be
+	 * validated by doing a REINDEX.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel) &&
+		IsOtherUsingGlobalTempRelation(RelationGetRelid(rel)))
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot add or alter constraints of global temporary table \"%s\" because it is being used in another session",
+					   RelationGetRelationName(rel)));
+
 	/* suppress schema rights check when rebuilding existing index */
 	check_rights = !is_rebuild;
 	/* skip index build if phase 3 will do it or we're reusing an old one */
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index c3c158a253d..83674eb8488 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -7269,6 +7269,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * safe.
 	 */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/bin/scripts/reindexdb.c b/src/bin/scripts/reindexdb.c
index d7fb16d3c85..70da07d2369 100644
--- a/src/bin/scripts/reindexdb.c
+++ b/src/bin/scripts/reindexdb.c
@@ -655,6 +655,8 @@ get_parallel_tables_list(PGconn *conn, ReindexType type,
 								 CppAsString2(RELKIND_MATVIEW) ")\n"
 								 "   AND c.relpersistence != "
 								 CppAsString2(RELPERSISTENCE_TEMP) "\n"
+								 "   AND c.relpersistence != "
+								 CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) "\n"
 								 " ORDER BY c.relpages DESC;");
 			break;
 
@@ -678,6 +680,8 @@ get_parallel_tables_list(PGconn *conn, ReindexType type,
 									 CppAsString2(RELKIND_MATVIEW) ")\n"
 									 "   AND c.relpersistence != "
 									 CppAsString2(RELPERSISTENCE_TEMP) "\n"
+									 "   AND c.relpersistence != "
+									 CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) "\n"
 									 " AND ns.nspname IN (");
 
 				for (cell = user_list->head; cell; cell = cell->next)
diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h
index 79240333530..b3b9cb6039b 100644
--- a/src/include/access/amapi.h
+++ b/src/include/access/amapi.h
@@ -15,6 +15,7 @@
 #include "access/cmptype.h"
 #include "access/genam.h"
 #include "access/stratnum.h"
+#include "common/relpath.h"
 #include "nodes/nodes.h"
 #include "nodes/pg_list.h"
 
@@ -115,7 +116,8 @@ typedef IndexBuildResult *(*ambuild_function) (Relation heapRelation,
 											   IndexInfo *indexInfo);
 
 /* build empty index */
-typedef void (*ambuildempty_function) (Relation indexRelation);
+typedef void (*ambuildempty_function) (Relation indexRelation,
+									   ForkNumber forknum);
 
 /* insert this tuple */
 typedef bool (*aminsert_function) (Relation indexRelation,
diff --git a/src/include/access/brin_internal.h b/src/include/access/brin_internal.h
index e17c7fee511..10137275c4c 100644
--- a/src/include/access/brin_internal.h
+++ b/src/include/access/brin_internal.h
@@ -90,7 +90,7 @@ extern BrinDesc *brin_build_desc(Relation rel);
 extern void brin_free_desc(BrinDesc *bdesc);
 extern IndexBuildResult *brinbuild(Relation heap, Relation index,
 								   IndexInfo *indexInfo);
-extern void brinbuildempty(Relation index);
+extern void brinbuildempty(Relation index, ForkNumber forknum);
 extern bool brininsert(Relation idxRel, Datum *values, bool *nulls,
 					   ItemPointer heaptid, Relation heapRel,
 					   IndexUniqueCheck checkUnique,
diff --git a/src/include/access/gin_private.h b/src/include/access/gin_private.h
index 3c5fd6ba817..dd51cb4453e 100644
--- a/src/include/access/gin_private.h
+++ b/src/include/access/gin_private.h
@@ -110,7 +110,7 @@ extern char *ginbuildphasename(int64 phasenum);
 /* gininsert.c */
 extern IndexBuildResult *ginbuild(Relation heap, Relation index,
 								  struct IndexInfo *indexInfo);
-extern void ginbuildempty(Relation index);
+extern void ginbuildempty(Relation index, ForkNumber forknum);
 extern bool gininsert(Relation index, Datum *values, bool *isnull,
 					  ItemPointer ht_ctid, Relation heapRel,
 					  IndexUniqueCheck checkUnique,
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39fdd27f5b9..69acc189592 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -399,7 +399,7 @@ typedef struct GiSTOptions
 } GiSTOptions;
 
 /* gist.c */
-extern void gistbuildempty(Relation index);
+extern void gistbuildempty(Relation index, ForkNumber forknum);
 extern bool gistinsert(Relation r, Datum *values, bool *isnull,
 					   ItemPointer ht_ctid, Relation heapRel,
 					   IndexUniqueCheck checkUnique,
diff --git a/src/include/access/hash.h b/src/include/access/hash.h
index a8702f0e5ea..a719d374af1 100644
--- a/src/include/access/hash.h
+++ b/src/include/access/hash.h
@@ -362,7 +362,7 @@ typedef struct HashOptions
 
 extern IndexBuildResult *hashbuild(Relation heap, Relation index,
 								   struct IndexInfo *indexInfo);
-extern void hashbuildempty(Relation index);
+extern void hashbuildempty(Relation index, ForkNumber forknum);
 extern bool hashinsert(Relation rel, Datum *values, bool *isnull,
 					   ItemPointer ht_ctid, Relation heapRel,
 					   IndexUniqueCheck checkUnique,
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 3097e9bb1af..8053dfe3d6a 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -1151,7 +1151,7 @@ typedef struct BTOptions
 /*
  * external entry points for btree, in nbtree.c
  */
-extern void btbuildempty(Relation index);
+extern void btbuildempty(Relation index, ForkNumber forknum);
 extern bool btinsert(Relation rel, Datum *values, bool *isnull,
 					 ItemPointer ht_ctid, Relation heapRel,
 					 IndexUniqueCheck checkUnique,
diff --git a/src/include/access/spgist.h b/src/include/access/spgist.h
index 083d93f8ffd..392c6538cbf 100644
--- a/src/include/access/spgist.h
+++ b/src/include/access/spgist.h
@@ -195,7 +195,7 @@ extern bytea *spgoptions(Datum reloptions, bool validate);
 /* spginsert.c */
 extern IndexBuildResult *spgbuild(Relation heap, Relation index,
 								  struct IndexInfo *indexInfo);
-extern void spgbuildempty(Relation index);
+extern void spgbuildempty(Relation index, ForkNumber forknum);
 extern bool spginsert(Relation index, Datum *values, bool *isnull,
 					  ItemPointer ht_ctid, Relation heapRel,
 					  IndexUniqueCheck checkUnique,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index a0ab2b885e8..728ed56cb12 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3621,7 +3621,8 @@ typedef struct IndexStmt
 	bool		unique;			/* is index unique? */
 	bool		nulls_not_distinct; /* null treatment for UNIQUE constraints */
 	bool		primary;		/* is index a primary key? */
-	bool		isconstraint;	/* is it for a pkey/unique constraint? */
+	bool		isconstraint;	/* is it for a pkey/unique/exclusion
+								 * constraint? */
 	bool		iswithoutoverlaps;	/* is the constraint WITHOUT OVERLAPS? */
 	bool		deferrable;		/* is the constraint DEFERRABLE? */
 	bool		initdeferred;	/* is the constraint INITIALLY DEFERRED? */
diff --git a/src/test/isolation/expected/global-temp.out b/src/test/isolation/expected/global-temp.out
index 3b50ae0c742..3b118822d6d 100644
--- a/src/test/isolation/expected/global-temp.out
+++ b/src/test/isolation/expected/global-temp.out
@@ -199,13 +199,17 @@ key|val
 (0 rows)
 
 
-starting permutation: create1 ins1_2 alter1a alter1b alter1c alter1d ins2_2 seltype1 seltype2 drop1
-step create1: CREATE GLOBAL TEMP TABLE tmp2 (key int, val text);
+starting permutation: create1 ins1_2 alter1a alter1b alter1c alter1d alter1e alter1f alter1g alter1h ins2_2 seltype1 seltype2 drop1
+step create1: CREATE GLOBAL TEMP TABLE tmp2 (key int, val text, icol int, bcol box);
 step ins1_2: INSERT INTO tmp2 VALUES (1, 's1');
 step alter1a: ALTER TABLE tmp2 ALTER COLUMN key SET DATA TYPE numeric;
 step alter1b: ALTER TABLE tmp2 ALTER COLUMN val SET NOT NULL;
 step alter1c: ALTER TABLE tmp2 ADD CONSTRAINT tmp2_nn NOT NULL key;
 step alter1d: ALTER TABLE tmp2 ADD CONSTRAINT tmp2_chk CHECK (key > 0);
+step alter1e: ALTER TABLE tmp2 ADD CONSTRAINT tmp2_pk PRIMARY KEY (key);
+step alter1f: ALTER TABLE tmp2 ADD CONSTRAINT tmp2_un UNIQUE (val);
+step alter1g: ALTER TABLE tmp2 ADD CONSTRAINT tmp2_fk FOREIGN KEY (icol) REFERENCES tmp;
+step alter1h: ALTER TABLE tmp2 ADD CONSTRAINT tmp2_ex EXCLUDE USING gist (bcol WITH &&);
 step ins2_2: INSERT INTO tmp2 VALUES (1, 's2');
 step seltype1: SELECT key, pg_typeof(key), val FROM tmp2;
 key|pg_typeof|val
@@ -221,8 +225,8 @@ key|pg_typeof|val
 
 step drop1: DROP TABLE tmp2;
 
-starting permutation: create1 ins1_2 ins2_2 alter1a alter1b alter1c alter1d seltype1 seltype2 drop1
-step create1: CREATE GLOBAL TEMP TABLE tmp2 (key int, val text);
+starting permutation: create1 ins1_2 ins2_2 alter1a alter1b alter1c alter1d alter1e alter1f alter1g alter1h uniq_idx1 seltype1 seltype2 drop1
+step create1: CREATE GLOBAL TEMP TABLE tmp2 (key int, val text, icol int, bcol box);
 step ins1_2: INSERT INTO tmp2 VALUES (1, 's1');
 step ins2_2: INSERT INTO tmp2 VALUES (1, 's2');
 step alter1a: ALTER TABLE tmp2 ALTER COLUMN key SET DATA TYPE numeric;
@@ -233,6 +237,15 @@ step alter1c: ALTER TABLE tmp2 ADD CONSTRAINT tmp2_nn NOT NULL key;
 ERROR:  cannot add or alter constraints of global temporary table "tmp2" because it is being used in another session
 step alter1d: ALTER TABLE tmp2 ADD CONSTRAINT tmp2_chk CHECK (key > 0);
 ERROR:  cannot add or alter constraints of global temporary table "tmp2" because it is being used in another session
+step alter1e: ALTER TABLE tmp2 ADD CONSTRAINT tmp2_pk PRIMARY KEY (key);
+ERROR:  cannot add or alter constraints of global temporary table "tmp2" because it is being used in another session
+step alter1f: ALTER TABLE tmp2 ADD CONSTRAINT tmp2_un UNIQUE (val);
+ERROR:  cannot add or alter constraints of global temporary table "tmp2" because it is being used in another session
+step alter1g: ALTER TABLE tmp2 ADD CONSTRAINT tmp2_fk FOREIGN KEY (icol) REFERENCES tmp;
+ERROR:  cannot add or alter constraints of global temporary table "tmp2" because it is being used in another session
+step alter1h: ALTER TABLE tmp2 ADD CONSTRAINT tmp2_ex EXCLUDE USING gist (bcol WITH &&);
+ERROR:  cannot add or alter constraints of global temporary table "tmp2" because it is being used in another session
+step uniq_idx1: CREATE UNIQUE INDEX tmp2_un ON tmp2(val);
 step seltype1: SELECT key, pg_typeof(key), val FROM tmp2;
 key|pg_typeof|val
 ---+---------+---
@@ -258,14 +271,14 @@ step ins2_2: INSERT INTO tmp2 VALUES (1, 's2');
 step drop1: DROP TABLE tmp2;
 
 starting permutation: create1 drop2 b1 prep1 cprep1
-step create1: CREATE GLOBAL TEMP TABLE tmp2 (key int, val text);
+step create1: CREATE GLOBAL TEMP TABLE tmp2 (key int, val text, icol int, bcol box);
 step drop2: DROP TABLE tmp2;
 step b1: BEGIN;
 step prep1: PREPARE TRANSACTION 'tx';
 step cprep1: COMMIT PREPARED 'tx';
 
 starting permutation: create1 ins1_2 b1 drop2 prep1 cprep1
-step create1: CREATE GLOBAL TEMP TABLE tmp2 (key int, val text);
+step create1: CREATE GLOBAL TEMP TABLE tmp2 (key int, val text, icol int, bcol box);
 step ins1_2: INSERT INTO tmp2 VALUES (1, 's1');
 step b1: BEGIN;
 step drop2: DROP TABLE tmp2;
@@ -273,10 +286,154 @@ step prep1: PREPARE TRANSACTION 'tx';
 step cprep1: COMMIT PREPARED 'tx';
 
 starting permutation: create1 b1 ins1_2 drop2 prep1
-step create1: CREATE GLOBAL TEMP TABLE tmp2 (key int, val text);
+step create1: CREATE GLOBAL TEMP TABLE tmp2 (key int, val text, icol int, bcol box);
 step b1: BEGIN;
 step ins1_2: INSERT INTO tmp2 VALUES (1, 's1');
 step drop2: DROP TABLE tmp2; <waiting ...>
 step prep1: PREPARE TRANSACTION 'tx';
 ERROR:  cannot PREPARE a transaction that has operated on temporary objects
 step drop2: <... completed>
+
+starting permutation: ins1 idx1 sel1_idx ins2 sel2_idx
+step ins1: INSERT INTO tmp VALUES (1, 's1');
+step idx1: CREATE INDEX tmp_val_idx ON tmp(val);
+step sel1_idx: 
+  SET enable_seqscan = off;
+  SET enable_bitmapscan = off;
+  EXPLAIN (COSTS OFF)
+  SELECT * FROM tmp WHERE val = 's1';
+  SELECT * FROM tmp WHERE val = 's1';
+
+QUERY PLAN                         
+-----------------------------------
+Index Scan using tmp_val_idx on tmp
+  Index Cond: (val = 's1'::text)   
+(2 rows)
+
+key|val
+---+---
+  1|s1 
+(1 row)
+
+step ins2: INSERT INTO tmp VALUES (1, 's2');
+step sel2_idx: 
+  SET enable_seqscan = off;
+  SET enable_bitmapscan = off;
+  EXPLAIN (COSTS OFF)
+  SELECT * FROM tmp WHERE val = 's2';
+  SELECT * FROM tmp WHERE val = 's2';
+
+QUERY PLAN                         
+-----------------------------------
+Index Scan using tmp_val_idx on tmp
+  Index Cond: (val = 's2'::text)   
+(2 rows)
+
+key|val
+---+---
+  1|s2 
+(1 row)
+
+
+starting permutation: ins1 ins2 idx1 sel1_idx sel2_idx
+step ins1: INSERT INTO tmp VALUES (1, 's1');
+step ins2: INSERT INTO tmp VALUES (1, 's2');
+step idx1: CREATE INDEX tmp_val_idx ON tmp(val);
+step sel1_idx: 
+  SET enable_seqscan = off;
+  SET enable_bitmapscan = off;
+  EXPLAIN (COSTS OFF)
+  SELECT * FROM tmp WHERE val = 's1';
+  SELECT * FROM tmp WHERE val = 's1';
+
+QUERY PLAN                         
+-----------------------------------
+Index Scan using tmp_val_idx on tmp
+  Index Cond: (val = 's1'::text)   
+(2 rows)
+
+key|val
+---+---
+  1|s1 
+(1 row)
+
+step sel2_idx: 
+  SET enable_seqscan = off;
+  SET enable_bitmapscan = off;
+  EXPLAIN (COSTS OFF)
+  SELECT * FROM tmp WHERE val = 's2';
+  SELECT * FROM tmp WHERE val = 's2';
+
+QUERY PLAN                  
+----------------------------
+Seq Scan on tmp             
+  Disabled: true            
+  Filter: (val = 's2'::text)
+(3 rows)
+
+key|val
+---+---
+  1|s2 
+(1 row)
+
+
+starting permutation: ins1 ins2 idx1 sel1_idx sel2_idx reidx2 sel2_idx
+step ins1: INSERT INTO tmp VALUES (1, 's1');
+step ins2: INSERT INTO tmp VALUES (1, 's2');
+step idx1: CREATE INDEX tmp_val_idx ON tmp(val);
+step sel1_idx: 
+  SET enable_seqscan = off;
+  SET enable_bitmapscan = off;
+  EXPLAIN (COSTS OFF)
+  SELECT * FROM tmp WHERE val = 's1';
+  SELECT * FROM tmp WHERE val = 's1';
+
+QUERY PLAN                         
+-----------------------------------
+Index Scan using tmp_val_idx on tmp
+  Index Cond: (val = 's1'::text)   
+(2 rows)
+
+key|val
+---+---
+  1|s1 
+(1 row)
+
+step sel2_idx: 
+  SET enable_seqscan = off;
+  SET enable_bitmapscan = off;
+  EXPLAIN (COSTS OFF)
+  SELECT * FROM tmp WHERE val = 's2';
+  SELECT * FROM tmp WHERE val = 's2';
+
+QUERY PLAN                  
+----------------------------
+Seq Scan on tmp             
+  Disabled: true            
+  Filter: (val = 's2'::text)
+(3 rows)
+
+key|val
+---+---
+  1|s2 
+(1 row)
+
+step reidx2: REINDEX INDEX tmp_val_idx;
+step sel2_idx: 
+  SET enable_seqscan = off;
+  SET enable_bitmapscan = off;
+  EXPLAIN (COSTS OFF)
+  SELECT * FROM tmp WHERE val = 's2';
+  SELECT * FROM tmp WHERE val = 's2';
+
+QUERY PLAN                         
+-----------------------------------
+Index Scan using tmp_val_idx on tmp
+  Index Cond: (val = 's2'::text)   
+(2 rows)
+
+key|val
+---+---
+  1|s2 
+(1 row)
+
diff --git a/src/test/isolation/specs/global-temp.spec b/src/test/isolation/specs/global-temp.spec
index 7ada392f96f..1133c0a5374 100644
--- a/src/test/isolation/specs/global-temp.spec
+++ b/src/test/isolation/specs/global-temp.spec
@@ -1,9 +1,9 @@
 # Test global temporary relations
 
 setup {
-  CREATE GLOBAL TEMP TABLE tmp (key int, val text);
+  CREATE GLOBAL TEMP TABLE tmp (key int PRIMARY KEY, val text);
 
-  CREATE GLOBAL TEMP TABLE tmp_parted (key int, val text) PARTITION BY LIST (key);
+  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);
   CREATE GLOBAL TEMP TABLE tmp_p2 PARTITION OF tmp_parted FOR VALUES IN ((2), (3));
 }
@@ -19,17 +19,30 @@ step ins1p1 { INSERT INTO tmp_parted VALUES (1, 's1 p1'); }
 step ins1p2 { INSERT INTO tmp_parted VALUES (2, 's1 p2'); }
 step sel1 { SELECT * FROM tmp; }
 step sel1p { SELECT tableoid::regclass, * FROM tmp_parted; }
-step create1 { CREATE GLOBAL TEMP TABLE tmp2 (key int, val text); }
+step create1 { CREATE GLOBAL TEMP TABLE tmp2 (key int, val text, icol int, bcol box); }
 step create1dr { CREATE GLOBAL TEMP TABLE tmp2 (key int, val text) ON COMMIT DELETE ROWS; }
 step ins1_2 { INSERT INTO tmp2 VALUES (1, 's1'); }
 step alter1a { ALTER TABLE tmp2 ALTER COLUMN key SET DATA TYPE numeric; }
 step alter1b { ALTER TABLE tmp2 ALTER COLUMN val SET NOT NULL; }
 step alter1c { ALTER TABLE tmp2 ADD CONSTRAINT tmp2_nn NOT NULL key; }
 step alter1d { ALTER TABLE tmp2 ADD CONSTRAINT tmp2_chk CHECK (key > 0); }
+step alter1e { ALTER TABLE tmp2 ADD CONSTRAINT tmp2_pk PRIMARY KEY (key); }
+step alter1f { ALTER TABLE tmp2 ADD CONSTRAINT tmp2_un UNIQUE (val); }
+step alter1g { ALTER TABLE tmp2 ADD CONSTRAINT tmp2_fk FOREIGN KEY (icol) REFERENCES tmp; }
+step alter1h { ALTER TABLE tmp2 ADD CONSTRAINT tmp2_ex EXCLUDE USING gist (bcol WITH &&); }
+step uniq_idx1 { CREATE UNIQUE INDEX tmp2_un ON tmp2(val); }
 step seltype1 { SELECT key, pg_typeof(key), val FROM tmp2; }
 step drop1 { DROP TABLE tmp2; }
 step prep1 { PREPARE TRANSACTION 'tx'; }
 step cprep1 { COMMIT PREPARED 'tx'; }
+step idx1 { CREATE INDEX tmp_val_idx ON tmp(val); }
+step sel1_idx {
+  SET enable_seqscan = off;
+  SET enable_bitmapscan = off;
+  EXPLAIN (COSTS OFF)
+  SELECT * FROM tmp WHERE val = 's1';
+  SELECT * FROM tmp WHERE val = 's1';
+}
 
 session s2
 step b2 { BEGIN; }
@@ -46,6 +59,14 @@ step rsp2 { ROLLBACK TO SAVEPOINT sp; }
 step ins2_2 { INSERT INTO tmp2 VALUES (1, 's2'); }
 step seltype2 { SELECT key, pg_typeof(key), val FROM tmp2; }
 step drop2 { DROP TABLE tmp2; }
+step sel2_idx {
+  SET enable_seqscan = off;
+  SET enable_bitmapscan = off;
+  EXPLAIN (COSTS OFF)
+  SELECT * FROM tmp WHERE val = 's2';
+  SELECT * FROM tmp WHERE val = 's2';
+}
+step reidx2 { REINDEX INDEX tmp_val_idx; }
 
 # Basic effects
 permutation ins1 ins2 sel1 sel2
@@ -60,11 +81,11 @@ permutation ins1 b2 ins2 sp2 t2 rsp2 sel1 sel2 r2 sel1 sel2
 
 # Test prevention of ALTER TABLE with rewrite, if in use
 permutation create1 ins1_2
-            alter1a alter1b alter1c alter1d
+            alter1a alter1b alter1c alter1d alter1e alter1f alter1g alter1h
             ins2_2 seltype1 seltype2 drop1
 permutation create1 ins1_2 ins2_2
-            alter1a alter1b alter1c alter1d
-            seltype1 seltype2 drop1
+            alter1a alter1b alter1c alter1d alter1e alter1f alter1g alter1h
+            uniq_idx1 seltype1 seltype2 drop1
 
 # Test DROP with ON COMMIT DELETE ROWS
 permutation create1dr ins1_2 ins2_2 drop1 create1dr ins1_2 ins2_2 drop1
@@ -73,3 +94,8 @@ permutation create1dr ins1_2 ins2_2 drop1 create1dr ins1_2 ins2_2 drop1
 permutation create1 drop2 b1 prep1 cprep1
 permutation create1 ins1_2 b1 drop2 prep1 cprep1
 permutation create1 b1 ins1_2 drop2 prep1
+
+# Test val index
+permutation ins1 idx1 sel1_idx ins2 sel2_idx
+permutation ins1 ins2 idx1 sel1_idx sel2_idx
+permutation ins1 ins2 idx1 sel1_idx sel2_idx reidx2 sel2_idx
diff --git a/src/test/modules/dummy_index_am/dummy_index_am.c b/src/test/modules/dummy_index_am/dummy_index_am.c
index 31f8d2b8161..65b116280bf 100644
--- a/src/test/modules/dummy_index_am/dummy_index_am.c
+++ b/src/test/modules/dummy_index_am/dummy_index_am.c
@@ -166,10 +166,10 @@ dibuild(Relation heap, Relation index, IndexInfo *indexInfo)
 }
 
 /*
- * Build an empty index for the initialization fork.
+ * Build an empty index for the specified fork.
  */
 static void
-dibuildempty(Relation index)
+dibuildempty(Relation index, ForkNumber forknum)
 {
 	/* No need to build an init fork for a dummy index */
 }
diff --git a/src/test/regress/expected/global_temp.out b/src/test/regress/expected/global_temp.out
index a37d01056ab..e3749374dca 100644
--- a/src/test/regress/expected/global_temp.out
+++ b/src/test/regress/expected/global_temp.out
@@ -13,7 +13,7 @@ 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);
+CREATE GLOBAL TEMP TABLE tmp1 (a int PRIMARY KEY, b text);
 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);
@@ -21,15 +21,18 @@ CREATE GLOBAL TEMP TABLE global_temp_yyy.tmp3 (a int);
   Global temporary table "global_temp_tests.tmp1"
  Column |  Type   | Collation | Nullable | Default 
 --------+---------+-----------+----------+---------
- a      | integer |           |          | 
+ a      | integer |           | not null | 
+ b      | text    |           |          | 
+Indexes:
+    "tmp1_pkey" PRIMARY KEY, btree (a)
 
 \dt+ global_temp_*.tmp*
-                                             List of tables
-      Schema       | Name | Type  |          Owner           |   Persistence    |  Size   | Description 
--------------------+------+-------+--------------------------+------------------+---------+-------------
- global_temp_tests | tmp1 | table | regress_global_temp_user | global temporary | 0 bytes | 
- global_temp_xxx   | tmp2 | table | regress_global_temp_user | global temporary | 0 bytes | 
- global_temp_yyy   | tmp3 | table | regress_global_temp_user | global temporary | 0 bytes | 
+                                              List of tables
+      Schema       | Name | Type  |          Owner           |   Persistence    |    Size    | Description 
+-------------------+------+-------+--------------------------+------------------+------------+-------------
+ global_temp_tests | tmp1 | table | regress_global_temp_user | global temporary | 8192 bytes | 
+ global_temp_xxx   | tmp2 | table | regress_global_temp_user | global temporary | 0 bytes    | 
+ global_temp_yyy   | tmp3 | table | regress_global_temp_user | global temporary | 0 bytes    | 
 (3 rows)
 
 -- Information schema
@@ -49,20 +52,59 @@ NOTICE:  drop cascades to table global_temp_xxx.tmp2
 DROP SCHEMA global_temp_yyy CASCADE;
 NOTICE:  drop cascades to table global_temp_yyy.tmp3
 -- Basic tests
-INSERT INTO tmp1 VALUES (1);
+INSERT INTO tmp1 VALUES (1, 'xxx');
 SELECT * FROM tmp1;
- a 
----
- 1
+ a |  b  
+---+-----
+ 1 | xxx
 (1 row)
 
 \c
 SET search_path = global_temp_tests;
 SELECT * FROM tmp1;
- a 
----
+ a | b 
+---+---
 (0 rows)
 
+-- Test index
+INSERT INTO tmp1 VALUES (1, 'xxx');
+SET enable_seqscan = off;
+EXPLAIN (COSTS OFF)
+SELECT * FROM tmp1 WHERE a = 1;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using tmp1_pkey on tmp1
+   Index Cond: (a = 1)
+(2 rows)
+
+SELECT * FROM tmp1 WHERE a = 1;
+ a |  b  
+---+-----
+ 1 | xxx
+(1 row)
+
+RESET enable_seqscan;
+-- Test concurrent index build -- CONCURRENTLY is ignored with temp tables
+CREATE INDEX CONCURRENTLY tmp1_b_idx ON tmp1(b);
+SET enable_seqscan = off;
+EXPLAIN (COSTS OFF)
+SELECT * FROM tmp1 WHERE b = 'xxx';
+             QUERY PLAN              
+-------------------------------------
+ Index Scan using tmp1_b_idx on tmp1
+   Index Cond: (b = 'xxx'::text)
+(2 rows)
+
+SELECT * FROM tmp1 WHERE b = 'xxx';
+ a |  b  
+---+-----
+ 1 | xxx
+(1 row)
+
+RESET enable_seqscan;
+REINDEX INDEX CONCURRENTLY tmp1_b_idx;
+REINDEX TABLE CONCURRENTLY tmp1;
+DROP INDEX CONCURRENTLY tmp1_b_idx;
 -- Test ON COMMIT DELETE ROWS
 CREATE GLOBAL TEMP TABLE tmp2 (a int) ON COMMIT DELETE ROWS;
 BEGIN;
@@ -103,8 +145,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 
----
+ a | b 
+---+---
 (0 rows)
 
 PREPARE TRANSACTION 'twophase'; -- fail
@@ -154,11 +196,33 @@ DROP TABLE tmp2;
 -- Test foreign keys
 CREATE TABLE perm_pk_rel (a int PRIMARY KEY);
 CREATE TEMP TABLE temp_pk_rel (a int PRIMARY KEY);
+CREATE GLOBAL TEMP TABLE gtemp_pk_rel (a int PRIMARY KEY);
+CREATE TABLE tmp2 (a int REFERENCES gtemp_pk_rel); -- fail
+ERROR:  constraints on permanent tables may reference only permanent tables
+CREATE TEMP TABLE tmp2 (a int REFERENCES gtemp_pk_rel); -- fail
+ERROR:  constraints on local temporary tables may reference only local temporary tables
 CREATE GLOBAL TEMP TABLE tmp2 (a int REFERENCES perm_pk_rel); -- fail
 ERROR:  constraints on global temporary tables may reference only global temporary tables
 CREATE GLOBAL TEMP TABLE tmp2 (a int REFERENCES temp_pk_rel); -- fail
 ERROR:  constraints on global temporary tables may reference only global temporary tables
-DROP TABLE perm_pk_rel, temp_pk_rel;
+CREATE GLOBAL TEMP TABLE tmp2 (a int REFERENCES gtemp_pk_rel);
+INSERT INTO gtemp_pk_rel VALUES (1);
+INSERT INTO tmp2 VALUES (1);
+INSERT INTO tmp2 VALUES (2); -- fail
+ERROR:  insert or update on table "tmp2" violates foreign key constraint "tmp2_a_fkey"
+DETAIL:  Key (a)=(2) is not present in table "gtemp_pk_rel".
+DELETE FROM gtemp_pk_rel WHERE a = 1; -- fail
+ERROR:  update or delete on table "gtemp_pk_rel" violates foreign key constraint "tmp2_a_fkey" on table "tmp2"
+DETAIL:  Key (a)=(1) is still referenced from table "tmp2".
+ALTER TABLE tmp2 DROP CONSTRAINT tmp2_a_fkey;
+ALTER TABLE tmp2 ADD FOREIGN KEY (a) REFERENCES gtemp_pk_rel ON DELETE CASCADE;
+DELETE FROM gtemp_pk_rel WHERE a = 1;
+SELECT * FROM tmp2;
+ a 
+---
+(0 rows)
+
+DROP TABLE perm_pk_rel, temp_pk_rel, tmp2;
 -- Test ALTER TABLE ... SET TABLESPACE
 CREATE GLOBAL TEMP TABLE tmp2 (a int);
 INSERT INTO tmp2 VALUES (1);
@@ -211,85 +275,85 @@ DETAIL:  tablespace for table tmp2
 DROP TABLE tmp2;
 DROP TABLESPACE regress_temp_test_tablespace;
 -- Test TRUNCATE
-INSERT INTO tmp1 VALUES (1);
+INSERT INTO tmp1 VALUES (1, 'xxx');
 BEGIN;
 TRUNCATE tmp1;
 SELECT * FROM tmp1;
- a 
----
+ a | b 
+---+---
 (0 rows)
 
 ROLLBACK;
 SELECT * FROM tmp1;
- a 
----
- 1
+ a |  b  
+---+-----
+ 1 | xxx
 (1 row)
 
 BEGIN;
 SAVEPOINT sp1;
 TRUNCATE tmp1;
 SELECT * FROM tmp1;
- a 
----
+ a | b 
+---+---
 (0 rows)
 
 RELEASE sp1;
 SELECT * FROM tmp1;
- a 
----
+ a | b 
+---+---
 (0 rows)
 
 ROLLBACK;
 SELECT * FROM tmp1;
- a 
----
- 1
+ a |  b  
+---+-----
+ 1 | xxx
 (1 row)
 
 BEGIN;
 SAVEPOINT sp1;
 TRUNCATE tmp1;
 SELECT * FROM tmp1;
- a 
----
+ a | b 
+---+---
 (0 rows)
 
 ROLLBACK TO sp1;
 SELECT * FROM tmp1;
- a 
----
- 1
+ a |  b  
+---+-----
+ 1 | xxx
 (1 row)
 
 COMMIT;
 SELECT * FROM tmp1;
- a 
----
- 1
+ a |  b  
+---+-----
+ 1 | xxx
 (1 row)
 
 TRUNCATE tmp1;
 SELECT * FROM tmp1;
- a 
----
+ a | b 
+---+---
 (0 rows)
 
 -- Test view creation
-INSERT INTO tmp1 VALUES (1);
+INSERT INTO tmp1 VALUES (1, 'xxx');
 CREATE VIEW v AS SELECT * FROM tmp1;
 SELECT * FROM v;
- a 
----
- 1
+ a |  b  
+---+-----
+ 1 | xxx
 (1 row)
 
 DROP VIEW v;
 CREATE TEMP VIEW v AS SELECT * FROM tmp1;
 SELECT * FROM v;
- a 
----
- 1
+ a |  b  
+---+-----
+ 1 | xxx
 (1 row)
 
 DROP VIEW v;
diff --git a/src/test/regress/sql/global_temp.sql b/src/test/regress/sql/global_temp.sql
index 431d06f768d..dc9d1fb392a 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);
+CREATE GLOBAL TEMP TABLE tmp1 (a int PRIMARY KEY, b text);
 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);
@@ -29,12 +29,31 @@ DROP SCHEMA global_temp_xxx CASCADE;
 DROP SCHEMA global_temp_yyy CASCADE;
 
 -- Basic tests
-INSERT INTO tmp1 VALUES (1);
+INSERT INTO tmp1 VALUES (1, 'xxx');
 SELECT * FROM tmp1;
 \c
 SET search_path = global_temp_tests;
 SELECT * FROM tmp1;
 
+-- Test index
+INSERT INTO tmp1 VALUES (1, 'xxx');
+SET enable_seqscan = off;
+EXPLAIN (COSTS OFF)
+SELECT * FROM tmp1 WHERE a = 1;
+SELECT * FROM tmp1 WHERE a = 1;
+RESET enable_seqscan;
+
+-- Test concurrent index build -- CONCURRENTLY is ignored with temp tables
+CREATE INDEX CONCURRENTLY tmp1_b_idx ON tmp1(b);
+SET enable_seqscan = off;
+EXPLAIN (COSTS OFF)
+SELECT * FROM tmp1 WHERE b = 'xxx';
+SELECT * FROM tmp1 WHERE b = 'xxx';
+RESET enable_seqscan;
+REINDEX INDEX CONCURRENTLY tmp1_b_idx;
+REINDEX TABLE CONCURRENTLY tmp1;
+DROP INDEX CONCURRENTLY tmp1_b_idx;
+
 -- Test ON COMMIT DELETE ROWS
 CREATE GLOBAL TEMP TABLE tmp2 (a int) ON COMMIT DELETE ROWS;
 BEGIN;
@@ -92,9 +111,21 @@ DROP TABLE tmp2;
 -- Test foreign keys
 CREATE TABLE perm_pk_rel (a int PRIMARY KEY);
 CREATE TEMP TABLE temp_pk_rel (a int PRIMARY KEY);
+CREATE GLOBAL TEMP TABLE gtemp_pk_rel (a int PRIMARY KEY);
+CREATE TABLE tmp2 (a int REFERENCES gtemp_pk_rel); -- fail
+CREATE TEMP TABLE tmp2 (a int REFERENCES gtemp_pk_rel); -- fail
 CREATE GLOBAL TEMP TABLE tmp2 (a int REFERENCES perm_pk_rel); -- fail
 CREATE GLOBAL TEMP TABLE tmp2 (a int REFERENCES temp_pk_rel); -- fail
-DROP TABLE perm_pk_rel, temp_pk_rel;
+CREATE GLOBAL TEMP TABLE tmp2 (a int REFERENCES gtemp_pk_rel);
+INSERT INTO gtemp_pk_rel VALUES (1);
+INSERT INTO tmp2 VALUES (1);
+INSERT INTO tmp2 VALUES (2); -- fail
+DELETE FROM gtemp_pk_rel WHERE a = 1; -- fail
+ALTER TABLE tmp2 DROP CONSTRAINT tmp2_a_fkey;
+ALTER TABLE tmp2 ADD FOREIGN KEY (a) REFERENCES gtemp_pk_rel ON DELETE CASCADE;
+DELETE FROM gtemp_pk_rel WHERE a = 1;
+SELECT * FROM tmp2;
+DROP TABLE perm_pk_rel, temp_pk_rel, tmp2;
 
 -- Test ALTER TABLE ... SET TABLESPACE
 CREATE GLOBAL TEMP TABLE tmp2 (a int);
@@ -127,7 +158,7 @@ DROP TABLE tmp2;
 DROP TABLESPACE regress_temp_test_tablespace;
 
 -- Test TRUNCATE
-INSERT INTO tmp1 VALUES (1);
+INSERT INTO tmp1 VALUES (1, 'xxx');
 BEGIN;
 TRUNCATE tmp1;
 SELECT * FROM tmp1;
@@ -156,7 +187,7 @@ TRUNCATE tmp1;
 SELECT * FROM tmp1;
 
 -- Test view creation
-INSERT INTO tmp1 VALUES (1);
+INSERT INTO tmp1 VALUES (1, 'xxx');
 CREATE VIEW v AS SELECT * FROM tmp1;
 SELECT * FROM v;
 DROP VIEW v;
-- 
2.43.0

