From c05e22f6f1a86b4931a715def5f2057cfb680145 Mon Sep 17 00:00:00 2001
From: Dean Rasheed <dean.a.rasheed@gmail.com>
Date: Tue, 4 Aug 2026 19:28:01 +0100
Subject: [PATCH v11 9/9] Add DISCARD GLOBAL TEMP.

DISCARD GLOBAL TEMP deletes all storage created for global temporary
relations used in the current session, and removes all usage records.
This restores all global temporary relations back to their original
uninitialized state, as they were at the start of the session.
---
 doc/src/sgml/ref/discard.sgml             |  16 +-
 src/backend/catalog/global_temp.c         | 174 ++++++++++++++++++++++
 src/backend/commands/discard.c            |   8 +-
 src/backend/parser/gram.y                 |  16 +-
 src/backend/tcop/utility.c                |   3 +
 src/bin/psql/tab-complete.in.c            |   2 +-
 src/include/catalog/global_temp.h         |   1 +
 src/include/nodes/parsenodes.h            |   1 +
 src/include/tcop/cmdtaglist.h             |   1 +
 src/test/regress/expected/global_temp.out | 161 ++++++++++++++++++++
 src/test/regress/sql/global_temp.sql      |  69 +++++++++
 11 files changed, 448 insertions(+), 4 deletions(-)

diff --git a/doc/src/sgml/ref/discard.sgml b/doc/src/sgml/ref/discard.sgml
index bf44c523cac..0e37deb3530 100644
--- a/doc/src/sgml/ref/discard.sgml
+++ b/doc/src/sgml/ref/discard.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-DISCARD { ALL | PLANS | SEQUENCES | TEMPORARY | TEMP }
+DISCARD { ALL | GLOBAL TEMPORARY | GLOBAL TEMP | PLANS | SEQUENCES | TEMPORARY | TEMP }
 </synopsis>
  </refsynopsisdiv>
 
@@ -42,6 +42,19 @@ DISCARD { ALL | PLANS | SEQUENCES | TEMPORARY | TEMP }
 
   <variablelist>
 
+   <varlistentry>
+    <term><literal>GLOBAL TEMPORARY</literal> or <literal>GLOBAL TEMP</literal></term>
+    <listitem>
+     <para>
+      Deletes all data from global temporary tables used in the current
+      session, and resets all global temporary sequences to their initial
+      state.  When the enclosing transaction is committed, all physical
+      storage created for global temporary relations used in the session
+      is deleted.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>PLANS</literal></term>
     <listitem>
@@ -90,6 +103,7 @@ RESET ALL;
 DEALLOCATE ALL;
 UNLISTEN *;
 SELECT pg_advisory_unlock_all();
+DISCARD GLOBAL TEMP;
 DISCARD PLANS;
 DISCARD TEMP;
 DISCARD SEQUENCES;
diff --git a/src/backend/catalog/global_temp.c b/src/backend/catalog/global_temp.c
index 08b1b359086..7a86c8e3752 100644
--- a/src/backend/catalog/global_temp.c
+++ b/src/backend/catalog/global_temp.c
@@ -55,6 +55,7 @@
 #include "access/genam.h"
 #include "access/multixact.h"
 #include "access/parallel.h"
+#include "access/relation.h"
 #include "access/table.h"
 #include "access/tableam.h"
 #include "access/xact.h"
@@ -202,6 +203,11 @@ static SubTransactionId processed_dropped_subid = InvalidSubTransactionId;
  */
 static bool update_tempfrozenxids = false;
 
+/*
+ * Subtransaction ID in which we executed DISCARD GLOBAL TEMP.
+ */
+static SubTransactionId discard_subid = InvalidSubTransactionId;
+
 /*
  * gtr_shared_usage
  *
@@ -1456,6 +1462,70 @@ AtEOXact_GlobalTempRelation(bool isCommit)
 	}
 	processed_dropped_subid = InvalidSubTransactionId;
 
+	/*
+	 * Are we committing a DISCARD GLOBAL TEMP?
+	 *
+	 * DiscardGlobalTempRelations() scheduled all storage for user-defined
+	 * relations to be deleted, and by this point, all hash table entries for
+	 * that storage will have been removed.  Now remove all usage records for
+	 * those relations, if they still have no storage (they may have new
+	 * storage, if they were reopened after the DISCARD).
+	 */
+	if (discard_subid != InvalidSubTransactionId && isCommit &&
+		gtr_local_usage != NULL)
+	{
+		hash_seq_init(&status, gtr_local_usage);
+		while ((usage_entry = hash_seq_search(&status)) != NULL)
+		{
+			GtrInfo    *gtr_info = &usage_entry->history.info;
+
+			/* Skip relations that have storage (new or reopened relations) */
+			if (RELKIND_HAS_STORAGE(usage_entry->relkind))
+			{
+				RelFileLocator rlocator;
+
+				if (gtr_info->reltablespace != 0)
+					rlocator.spcOid = gtr_info->reltablespace;
+				else
+					rlocator.spcOid = MyDatabaseTableSpace;
+				rlocator.dbOid = MyDatabaseId;
+				rlocator.relNumber = gtr_info->relfilenode;
+
+				if (FIND_LOCAL_STORAGE_ENTRY(rlocator) != NULL)
+					continue;
+			}
+
+			/*
+			 * Also skip relations with reltuples > 0.
+			 *
+			 * Since DiscardGlobalTempRelations() sets reltuples to 0, this
+			 * can only happen if the relation was created or reopened after
+			 * the DISCARD, and then analyzed.  Since it has no storage, it
+			 * must be a partitioned relation, and the updated reltuples value
+			 * is worth keeping.  Otherwise, if reltuples is 0 or -1 (the
+			 * initial defaults), then we can safely remove the usage record,
+			 * since it serves no other useful purpose for a partitioned
+			 * relation.
+			 */
+			if (gtr_info->reltuples > 0)
+			{
+				Assert(!RELKIND_HAS_STORAGE(usage_entry->relkind));
+				continue;
+			}
+
+			/*
+			 * Remove the usage record, and mark the relation as invalid in
+			 * the relcache, to force it to be reinitialized if it's reopened.
+			 */
+			gtr_remove_usage(usage_entry->relid);
+			RelationMarkInvalid(usage_entry->relid);
+		}
+
+		/* Trigger a recompute of tempfrozenxid and tempminmxid */
+		update_tempfrozenxids = true;
+	}
+	discard_subid = InvalidSubTransactionId;
+
 	/*
 	 * Finally, on commit, update tempfrozenxid and tempminmxid, if requested.
 	 *
@@ -1572,6 +1642,15 @@ AtEOSubXact_GlobalTempRelation(bool isCommit, SubTransactionId mySubid,
 			processed_dropped_subid = InvalidSubTransactionId;
 	}
 
+	/* Update discard_subid */
+	if (discard_subid == mySubid)
+	{
+		if (isCommit)
+			discard_subid = parentSubid;
+		else
+			discard_subid = InvalidSubTransactionId;
+	}
+
 	/* Don't reset the lists; we still need more cleanup later */
 }
 
@@ -1822,6 +1901,101 @@ GetEffectivePgIndexTuple(Oid indexrelid)
 	return tuple;
 }
 
+/*
+ * DiscardGlobalTempRelations
+ *
+ *	DISCARD GLOBAL TEMP/TEMPORARY --- delete all storage created for global
+ *	temporary relations and remove all usage records, restoring the session to
+ *	the state it had before any global temporary relations were opened.
+ */
+void
+DiscardGlobalTempRelations(void)
+{
+	/*
+	 * This is a two stage process.  In the first stage (here), we remove all
+	 * storage associated with global temporary relations, but we keep their
+	 * usage records and associated relation information.  In the second stage
+	 * (on main transaction commit), if the DISCARD has survived without
+	 * (sub)transaction rollback, the associated usage records are deleted.
+	 * This approach allows for rollback of the DISCARD and also reopening
+	 * (and hence reinitialization) of relations in the same transaction,
+	 * which then creates new storage and prevents the usage records from
+	 * being discarded.
+	 */
+	if (gtr_local_usage != NULL)
+	{
+		HASH_SEQ_STATUS status;
+		GtrUsageEntry *entry;
+
+		hash_seq_init(&status, gtr_local_usage);
+		while ((entry = hash_seq_search(&status)) != NULL)
+		{
+			Oid			relid = entry->relid;
+			Relation	rel;
+			RelFileNumber newrelfilenumber;
+			GtrInfo    *gtr_info;
+
+			/* Skip dropped relations */
+			if (entry->stopped_subid != InvalidSubTransactionId)
+				continue;
+
+			/* Skip relations that don't have storage */
+			if (!RELKIND_HAS_STORAGE(entry->relkind))
+				continue;
+
+			/*
+			 * Schedule the relation's current storage for deletion and
+			 * allocate a new relfilenumber, but don't actually create new
+			 * storage.  The new storage will be created if it is reopened.
+			 */
+			rel = relation_open(relid, AccessExclusiveLock);
+
+			newrelfilenumber = GetNewRelFileNumber(rel->rd_rel->reltablespace,
+												   NULL,
+												   rel->rd_rel->relpersistence);
+			RelationDropStorage(rel);
+
+			RelationAssumeNewRelfilelocator(rel);
+
+			relation_close(rel, NoLock);
+
+			/*
+			 * Update the session-local information for the relation to point
+			 * to the new storage, and reset all the other fields.
+			 */
+			gtr_info = GetGlobalTempRelationInfoForUpdate(relid);
+
+			gtr_info->relfilenode = newrelfilenumber;
+			gtr_info->relpages = 0;
+			gtr_info->reltuples = 0;
+			gtr_info->relallvisible = 0;
+			gtr_info->relallfrozen = 0;
+			gtr_info->relfrozenxid = InvalidTransactionId;
+			gtr_info->relminmxid = InvalidMultiXactId;
+
+			/*
+			 * Mark the relcache entry as invalid.  This will force a reload
+			 * and reinitialize with the new storage, if it is reopened in the
+			 * same transaction.  (If it is not reopened until after this
+			 * transaction commits, the above information will have been
+			 * deleted, along with the usage record, so it will reset back to
+			 * its original default relfilenode for reinitialization.)
+			 */
+			RelationMarkInvalid(relid);
+
+			/* Forget its ON COMMIT action */
+			remove_on_commit_action(relid);
+		}
+
+		/*
+		 * Make note of the subtransaction ID in which we did this, so we can
+		 * track whether it survives to the end of the main transaction.
+		 */
+		if (discard_subid == InvalidSubTransactionId)
+			discard_subid = GetCurrentSubTransactionId();
+	}
+}
+
 /*
  * pg_gtr_info
  *
diff --git a/src/backend/commands/discard.c b/src/backend/commands/discard.c
index 17d172df076..b4541ae2d1a 100644
--- a/src/backend/commands/discard.c
+++ b/src/backend/commands/discard.c
@@ -14,6 +14,7 @@
 #include "postgres.h"
 
 #include "access/xact.h"
+#include "catalog/global_temp.h"
 #include "catalog/namespace.h"
 #include "commands/async.h"
 #include "commands/discard.h"
@@ -26,7 +27,7 @@
 static void DiscardAll(bool isTopLevel);
 
 /*
- * DISCARD { ALL | SEQUENCES | TEMP | PLANS }
+ * DISCARD { ALL | SEQUENCES | TEMP | PLANS | GLOBAL TEMP }
  */
 void
 DiscardCommand(DiscardStmt *stmt, bool isTopLevel)
@@ -49,6 +50,10 @@ DiscardCommand(DiscardStmt *stmt, bool isTopLevel)
 			ResetTempTableNamespace();
 			break;
 
+		case DISCARD_GLOBAL_TEMP:
+			DiscardGlobalTempRelations();
+			break;
+
 		default:
 			elog(ERROR, "unrecognized DISCARD target: %d", stmt->target);
 	}
@@ -76,4 +81,5 @@ DiscardAll(bool isTopLevel)
 	ResetPlanCache();
 	ResetTempTableNamespace();
 	ResetSequenceCaches();
+	DiscardGlobalTempRelations();
 }
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 08ede85e37c..5b47e84e044 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2121,7 +2121,7 @@ CheckPointStmt:
 
 /*****************************************************************************
  *
- * DISCARD { ALL | TEMP | PLANS | SEQUENCES }
+ * DISCARD { ALL | TEMP | PLANS | SEQUENCES | GLOBAL TEMP }
  *
  *****************************************************************************/
 
@@ -2161,6 +2161,20 @@ DiscardStmt:
 					n->target = DISCARD_SEQUENCES;
 					$$ = (Node *) n;
 				}
+			| DISCARD GLOBAL TEMP
+				{
+					DiscardStmt *n = makeNode(DiscardStmt);
+
+					n->target = DISCARD_GLOBAL_TEMP;
+					$$ = (Node *) n;
+				}
+			| DISCARD GLOBAL TEMPORARY
+				{
+					DiscardStmt *n = makeNode(DiscardStmt);
+
+					n->target = DISCARD_GLOBAL_TEMP;
+					$$ = (Node *) n;
+				}
 
 		;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 1512d2df196..5847cf26151 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -2962,6 +2962,9 @@ CreateCommandTag(Node *parsetree)
 				case DISCARD_SEQUENCES:
 					tag = CMDTAG_DISCARD_SEQUENCES;
 					break;
+				case DISCARD_GLOBAL_TEMP:
+					tag = CMDTAG_DISCARD_GLOBAL_TEMP;
+					break;
 				default:
 					tag = CMDTAG_UNKNOWN;
 			}
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 0665692d069..f35bc245900 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -4331,7 +4331,7 @@ match_previous_words(int pattern_id,
 
 /* DISCARD */
 	else if (Matches("DISCARD"))
-		COMPLETE_WITH("ALL", "PLANS", "SEQUENCES", "TEMP");
+		COMPLETE_WITH("ALL", "GLOBAL TEMP", "PLANS", "SEQUENCES", "TEMP");
 
 /* DO */
 	else if (Matches("DO"))
diff --git a/src/include/catalog/global_temp.h b/src/include/catalog/global_temp.h
index 7030763fb51..44f4dd619e9 100644
--- a/src/include/catalog/global_temp.h
+++ b/src/include/catalog/global_temp.h
@@ -82,6 +82,7 @@ extern GtrInfo *GetGlobalTempRelationInfoForUpdate(Oid relid);
 extern GtrInfo *GetGlobalTempRelationInfoForInPlaceUpdate(Oid relid);
 extern HeapTuple GetEffectivePgClassTuple(Oid relid);
 extern HeapTuple GetEffectivePgIndexTuple(Oid indexrelid);
+extern void DiscardGlobalTempRelations(void);
 
 /*
  * Get the effective value of relfilenode for a relation.  For a global
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b216956f193..ba7e0bde311 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4115,6 +4115,7 @@ typedef enum DiscardMode
 	DISCARD_PLANS,
 	DISCARD_SEQUENCES,
 	DISCARD_TEMP,
+	DISCARD_GLOBAL_TEMP,
 } DiscardMode;
 
 typedef struct DiscardStmt
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index 652dc61b834..4e232edc40e 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -130,6 +130,7 @@ PG_CMDTAG(CMDTAG_DECLARE_CURSOR, "DECLARE CURSOR", false, false, false)
 PG_CMDTAG(CMDTAG_DELETE, "DELETE", false, false, true)
 PG_CMDTAG(CMDTAG_DISCARD, "DISCARD", false, false, false)
 PG_CMDTAG(CMDTAG_DISCARD_ALL, "DISCARD ALL", false, false, false)
+PG_CMDTAG(CMDTAG_DISCARD_GLOBAL_TEMP, "DISCARD GLOBAL TEMP", false, false, false)
 PG_CMDTAG(CMDTAG_DISCARD_PLANS, "DISCARD PLANS", false, false, false)
 PG_CMDTAG(CMDTAG_DISCARD_SEQUENCES, "DISCARD SEQUENCES", false, false, false)
 PG_CMDTAG(CMDTAG_DISCARD_TEMP, "DISCARD TEMP", false, false, false)
diff --git a/src/test/regress/expected/global_temp.out b/src/test/regress/expected/global_temp.out
index 6a9dee45819..401538565e2 100644
--- a/src/test/regress/expected/global_temp.out
+++ b/src/test/regress/expected/global_temp.out
@@ -1221,3 +1221,164 @@ SELECT row_estimate('SELECT * FROM tmp2 WHERE b = 8 AND c = 108');
 (1 row)
 
 DROP TABLE tmp2;
+-- Test DISCARD GLOBAL TEMP
+\c
+SET search_path = global_temp_tests;
+INSERT INTO tmp1 VALUES (1, 'xxx'), (10, 'yyy');
+SELECT * FROM tmp1;
+ a  |  b  | c 
+----+-----+---
+  1 | xxx | 1
+ 10 | yyy | 2
+(2 rows)
+
+CREATE GLOBAL TEMP TABLE tmp2 (a int PRIMARY KEY, b text) PARTITION BY LIST (a);
+CREATE GLOBAL TEMP TABLE tmp2_p1 PARTITION OF tmp2 FOR VALUES IN (1);
+CREATE GLOBAL TEMP TABLE tmp2_p2 PARTITION OF tmp2 FOR VALUES IN (2);
+INSERT INTO tmp2 VALUES (1, 'Row 1'), (2, 'Row 2');
+SELECT tableoid::regclass, * FROM tmp2;
+ tableoid | a |   b   
+----------+---+-------
+ tmp2_p1  | 1 | Row 1
+ tmp2_p2  | 2 | Row 2
+(2 rows)
+
+DISCARD GLOBAL TEMP;
+SELECT tempfrozenxid, tempminmxid
+  FROM pg_stat_activity
+ WHERE pid = pg_backend_pid();
+ tempfrozenxid | tempminmxid 
+---------------+-------------
+               |            
+(1 row)
+
+SELECT oid::regclass FROM pg_gtrs_in_use();
+ oid 
+-----
+(0 rows)
+
+SELECT relname, pg_relation_size(oid)
+  FROM pg_class
+ WHERE (relname ~ 'tmp1' OR relname ~ 'tmp2' OR relname ~ 'pg_temp_')
+   AND relpersistence = 'g' AND relkind IN ('r', 'p')
+ ORDER BY relname;
+          relname           | pg_relation_size 
+----------------------------+------------------
+ pg_temp_statistic          |                0
+ pg_temp_statistic_ext_data |                0
+ tmp1                       |                0
+ tmp2                       |                0
+ tmp2_p1                    |                0
+ tmp2_p2                    |                0
+(6 rows)
+
+-- Relfilenodes should revert back to their defaults after reopening
+SELECT * FROM tmp1;
+ a | b | c 
+---+---+---
+(0 rows)
+
+SELECT * FROM tmp2;
+ a | b 
+---+---
+(0 rows)
+
+SELECT c.relname,
+       t.relfilenode = c.relfilenode,
+       t.reltablespace = c.reltablespace
+  FROM pg_gtrs_in_use() t LEFT JOIN pg_class c ON c.oid = t.oid
+ WHERE c.relname ~ 'tmp1' OR c.relname ~ 'tmp2'
+ ORDER BY c.relname;
+   relname    | ?column? | ?column? 
+--------------+----------+----------
+ tmp1         | t        | t
+ tmp1_pkey    | t        | t
+ tmp2         | t        | t
+ tmp2_p1      | t        | t
+ tmp2_p1_pkey | t        | t
+ tmp2_p2      | t        | t
+ tmp2_p2_pkey | t        | t
+ tmp2_pkey    | t        | t
+(8 rows)
+
+-- Reopening in same transaction should give new relfilenodes
+INSERT INTO tmp1 VALUES (1, 'xxx'), (10, 'yyy');
+BEGIN;
+DISCARD GLOBAL TEMP;
+SELECT * FROM tmp1;
+ a | b | c 
+---+---+---
+(0 rows)
+
+INSERT INTO tmp1 VALUES (1, 'xxx'), (10, 'yyy');
+COMMIT;
+SELECT * FROM tmp1;
+ a  |  b  | c 
+----+-----+---
+  1 | xxx | 1
+ 10 | yyy | 2
+(2 rows)
+
+SELECT c.relname,
+       t.relfilenode = c.relfilenode,
+       t.reltablespace = c.reltablespace
+  FROM pg_gtrs_in_use() t LEFT JOIN pg_class c ON c.oid = t.oid
+ WHERE c.relname ~ 'tmp1' OR c.relname ~ 'tmp2'
+ ORDER BY c.relname;
+  relname   | ?column? | ?column? 
+------------+----------+----------
+ tmp1       | f        | t
+ tmp1_c_seq | f        | t
+ tmp1_pkey  | f        | t
+(3 rows)
+
+-- Test rollback of DISCARD GLOBAL TEMP
+BEGIN;
+DISCARD GLOBAL TEMP;
+ROLLBACK;
+SELECT * FROM tmp1;
+ a  |  b  | c 
+----+-----+---
+  1 | xxx | 1
+ 10 | yyy | 2
+(2 rows)
+
+BEGIN;
+DISCARD GLOBAL TEMP;
+SELECT * FROM tmp1;
+ a | b | c 
+---+---+---
+(0 rows)
+
+ROLLBACK;
+SELECT * FROM tmp1;
+ a  |  b  | c 
+----+-----+---
+  1 | xxx | 1
+ 10 | yyy | 2
+(2 rows)
+
+BEGIN;
+SAVEPOINT sp;
+DISCARD GLOBAL TEMP;
+SELECT * FROM tmp1;
+ a | b | c 
+---+---+---
+(0 rows)
+
+ROLLBACK TO sp;
+SELECT * FROM tmp1;
+ a  |  b  | c 
+----+-----+---
+  1 | xxx | 1
+ 10 | yyy | 2
+(2 rows)
+
+COMMIT;
+SELECT * FROM tmp1;
+ a  |  b  | c 
+----+-----+---
+  1 | xxx | 1
+ 10 | yyy | 2
+(2 rows)
+
diff --git a/src/test/regress/sql/global_temp.sql b/src/test/regress/sql/global_temp.sql
index 663e34a59c5..1e93e2279d2 100644
--- a/src/test/regress/sql/global_temp.sql
+++ b/src/test/regress/sql/global_temp.sql
@@ -655,3 +655,72 @@ SELECT COUNT(*) FROM tmp2 WHERE b = 8 AND c = 108;
 SELECT row_estimate('SELECT * FROM tmp2 WHERE b = 8 AND c = 108');
 
 DROP TABLE tmp2;
+
+-- Test DISCARD GLOBAL TEMP
+\c
+SET search_path = global_temp_tests;
+INSERT INTO tmp1 VALUES (1, 'xxx'), (10, 'yyy');
+SELECT * FROM tmp1;
+CREATE GLOBAL TEMP TABLE tmp2 (a int PRIMARY KEY, b text) PARTITION BY LIST (a);
+CREATE GLOBAL TEMP TABLE tmp2_p1 PARTITION OF tmp2 FOR VALUES IN (1);
+CREATE GLOBAL TEMP TABLE tmp2_p2 PARTITION OF tmp2 FOR VALUES IN (2);
+INSERT INTO tmp2 VALUES (1, 'Row 1'), (2, 'Row 2');
+SELECT tableoid::regclass, * FROM tmp2;
+
+DISCARD GLOBAL TEMP;
+SELECT tempfrozenxid, tempminmxid
+  FROM pg_stat_activity
+ WHERE pid = pg_backend_pid();
+SELECT oid::regclass FROM pg_gtrs_in_use();
+SELECT relname, pg_relation_size(oid)
+  FROM pg_class
+ WHERE (relname ~ 'tmp1' OR relname ~ 'tmp2' OR relname ~ 'pg_temp_')
+   AND relpersistence = 'g' AND relkind IN ('r', 'p')
+ ORDER BY relname;
+
+-- Relfilenodes should revert back to their defaults after reopening
+SELECT * FROM tmp1;
+SELECT * FROM tmp2;
+SELECT c.relname,
+       t.relfilenode = c.relfilenode,
+       t.reltablespace = c.reltablespace
+  FROM pg_gtrs_in_use() t LEFT JOIN pg_class c ON c.oid = t.oid
+ WHERE c.relname ~ 'tmp1' OR c.relname ~ 'tmp2'
+ ORDER BY c.relname;
+
+-- Reopening in same transaction should give new relfilenodes
+INSERT INTO tmp1 VALUES (1, 'xxx'), (10, 'yyy');
+BEGIN;
+DISCARD GLOBAL TEMP;
+SELECT * FROM tmp1;
+INSERT INTO tmp1 VALUES (1, 'xxx'), (10, 'yyy');
+COMMIT;
+SELECT * FROM tmp1;
+
+SELECT c.relname,
+       t.relfilenode = c.relfilenode,
+       t.reltablespace = c.reltablespace
+  FROM pg_gtrs_in_use() t LEFT JOIN pg_class c ON c.oid = t.oid
+ WHERE c.relname ~ 'tmp1' OR c.relname ~ 'tmp2'
+ ORDER BY c.relname;
+
+-- Test rollback of DISCARD GLOBAL TEMP
+BEGIN;
+DISCARD GLOBAL TEMP;
+ROLLBACK;
+SELECT * FROM tmp1;
+
+BEGIN;
+DISCARD GLOBAL TEMP;
+SELECT * FROM tmp1;
+ROLLBACK;
+SELECT * FROM tmp1;
+
+BEGIN;
+SAVEPOINT sp;
+DISCARD GLOBAL TEMP;
+SELECT * FROM tmp1;
+ROLLBACK TO sp;
+SELECT * FROM tmp1;
+COMMIT;
+SELECT * FROM tmp1;
-- 
2.51.0

