From c5cc76dfce0c8858f536e3620cc3fd60d34df565 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 v1 2/9] Support indexes on global temporary tables.

As with local temporary tables, concurrent index build/reindex/drop is
not supported.
---
 contrib/bloom/blinsert.c                      |   6 +-
 contrib/bloom/bloom.h                         |   2 +-
 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             |  24 +++
 src/backend/catalog/index.c                   |   5 +-
 src/backend/commands/indexcmds.c              |  27 ++-
 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/test/isolation/expected/global-temp.out   | 144 ++++++++++++++++
 src/test/isolation/specs/global-temp.spec     |  25 ++-
 .../modules/dummy_index_am/dummy_index_am.c   |   4 +-
 src/test/regress/expected/global_temp.out     | 161 ++++++++++++------
 src/test/regress/sql/global_temp.sql          |  41 ++++-
 26 files changed, 407 insertions(+), 98 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/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 cb9ed3b563c..e9c2d1eedf1 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 8d8cd30dc38..cdcbb9efae0 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 3df2c752ead..be1b5ce51f9 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 f2e10b82b7d..9b099d87f35 100644
--- a/src/backend/access/transam/xloginsert.c
+++ b/src/backend/access/transam/xloginsert.c
@@ -561,7 +561,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 a3ee3d424ca..8c105f71744 100644
--- a/src/backend/catalog/global_temp.c
+++ b/src/backend/catalog/global_temp.c
@@ -53,7 +53,9 @@
  */
 #include "postgres.h"
 
+#include "access/amapi.h"
 #include "access/genam.h"
+#include "access/table.h"
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "access/xlogutils.h"
@@ -832,6 +834,28 @@ InitGlobalTempRelation(Relation relation)
 								  relation->rd_locator,
 								  relation->rd_rel->relpersistence,
 								  true);
+
+		/*
+		 * 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.
+		 */
+		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;
+		}
 	}
 
 	/* The remaining initialization works as if we had created it locally */
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..9d37a773b46 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2156,7 +2156,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));
 
 	/*
@@ -3112,7 +3113,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 9ab74c8df0a..25d828f3a2b 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -616,8 +616,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;
 
@@ -2981,7 +2989,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
 	{
@@ -3077,6 +3086,7 @@ static Oid
 ReindexTable(const ReindexStmt *stmt, const ReindexParams *params, bool isTopLevel)
 {
 	Oid			heapOid;
+	char		persistence;
 	bool		result;
 	const RangeVar *relation = stmt->relation;
 
@@ -3094,10 +3104,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);
 
@@ -3521,7 +3534,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;
 
@@ -3963,7 +3977,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/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index f4689e7c9f8..12c616dc6cd 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -7352,6 +7352,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 6725ee2839f..1ce7bffbe70 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 44514f1cb8d..334c3de4541 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/test/isolation/expected/global-temp.out b/src/test/isolation/expected/global-temp.out
index 7951c3c4910..78d944d34f8 100644
--- a/src/test/isolation/expected/global-temp.out
+++ b/src/test/isolation/expected/global-temp.out
@@ -240,3 +240,147 @@ key|pg_typeof|val
 (1 row)
 
 step drop1: DROP TABLE tmp2;
+
+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 32e6e93df07..f33170260ce 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));
 }
@@ -24,6 +24,14 @@ step alter1a { ALTER TABLE tmp2 ALTER COLUMN key SET DATA TYPE numeric; }
 step alter1b { ALTER TABLE tmp2 ALTER COLUMN val SET NOT NULL; }
 step seltype1 { SELECT key, pg_typeof(key), val FROM tmp2; }
 step drop1 { DROP TABLE tmp2; }
+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; }
@@ -39,6 +47,14 @@ step sp2 { SAVEPOINT sp; }
 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 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
@@ -54,3 +70,8 @@ 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 ins2_2 seltype1 seltype2 drop1
 permutation create1 ins1_2 ins2_2 alter1a alter1b seltype1 seltype2 drop1
+
+# 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 f34bd83b0cb..ee10cb7b5d0 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;
@@ -86,9 +128,10 @@ 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 
----
-(0 rows)
+ a |  b  
+---+-----
+ 1 | xxx
+(1 row)
 
 PREPARE TRANSACTION 'twophase'; -- fail
 ERROR:  cannot PREPARE a transaction that has operated on temporary objects
@@ -130,11 +173,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);
@@ -187,85 +252,85 @@ DETAIL:  tablespace for table tmp2
 DROP TABLE tmp2;
 DROP TABLESPACE 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 ceb6c0ac7b7..d0f68d130a2 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;
@@ -75,9 +94,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);
@@ -110,7 +141,7 @@ DROP TABLE tmp2;
 DROP TABLESPACE temp_test_tablespace;
 
 -- Test TRUNCATE
-INSERT INTO tmp1 VALUES (1);
+INSERT INTO tmp1 VALUES (1, 'xxx');
 BEGIN;
 TRUNCATE tmp1;
 SELECT * FROM tmp1;
@@ -139,7 +170,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.51.0

