From 46c372b68317602cf75a75c2a9554d2ab4e5ff8b Mon Sep 17 00:00:00 2001
From: Dean Rasheed <dean.a.rasheed@gmail.com>
Date: Wed, 17 Jun 2026 02:04:59 +0100
Subject: [PATCH v2 5/9] Add relation statistics columns to pg_temp_class.

This adds relpages, reltuples, relallvisible, and relallfrozen columns
to pg_temp_class, and updates ANALYZE, CREATE INDEX, REPACK, VACUUM,
and pg_clear/restore_relation_stats() to apply statistics updates to
pg_temp_class instead of pg_class for global temporary relations. Each
session is then able to use its own local statistics when planning
queries.
---
 src/backend/access/heap/heapam.c          |   4 +
 src/backend/catalog/catalog.c             |   2 +
 src/backend/catalog/index.c               |  93 ++++++++----
 src/backend/catalog/pg_temp_class.c       |  77 ++++++++++
 src/backend/commands/repack.c             |  61 +++++---
 src/backend/commands/vacuum.c             |  89 ++++++++----
 src/backend/statistics/relation_stats.c   |  82 ++++++-----
 src/backend/utils/cache/inval.c           |   7 +-
 src/include/catalog/pg_temp_class.h       | 166 ++++++++++++++++++++++
 src/test/regress/expected/global_temp.out | 136 ++++++++++++++++++
 src/test/regress/sql/global_temp.sql      |  82 +++++++++++
 11 files changed, 681 insertions(+), 118 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index abfd8e8970a..d2588779cb8 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -42,6 +42,7 @@
 #include "access/xloginsert.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_database_d.h"
+#include "catalog/pg_temp_class.h"
 #include "commands/vacuum.h"
 #include "executor/instrument_node.h"
 #include "pgstat.h"
@@ -4206,6 +4207,9 @@ check_lock_if_inplace_updateable_rel(Relation relation,
 					return;
 			}
 			break;
+		case TempRelationRelationId:
+			/* No lock required -- temp tables are only accessible by us */
+			return;
 		default:
 			Assert(!IsInplaceUpdateRelation(relation));
 			return;
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index fed69d36939..f764086f005 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -40,6 +40,7 @@
 #include "catalog/pg_shseclabel.h"
 #include "catalog/pg_subscription.h"
 #include "catalog/pg_tablespace.h"
+#include "catalog/pg_temp_class.h"
 #include "catalog/pg_type.h"
 #include "miscadmin.h"
 #include "utils/fmgroids.h"
@@ -193,6 +194,7 @@ bool
 IsInplaceUpdateOid(Oid relid)
 {
 	return (relid == RelationRelationId ||
+			relid == TempRelationRelationId ||
 			relid == DatabaseRelationId);
 }
 
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 28646029f16..5b2a72a2029 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2805,11 +2805,14 @@ FormIndexDatum(IndexInfo *indexInfo,
 
 
 /*
- * index_update_stats --- update pg_class entry after CREATE INDEX or REINDEX
+ * index_update_stats --- update effective pg_class entry after CREATE INDEX
+ * or REINDEX
  *
- * This routine updates the pg_class row of either an index or its parent
- * relation after CREATE INDEX or REINDEX.  Its rather bizarre API is designed
- * to ensure we can do all the necessary work in just one update.
+ * This routine updates the effective pg_class row of either an index or its
+ * parent relation after CREATE INDEX or REINDEX.  Its rather bizarre API is
+ * designed to ensure we can do all the necessary work in just one update
+ * (except for a global temporary relation, which requires both pg_class and
+ * pg_temp_class to be updated).
  *
  * hasindex: set relhasindex to this value
  * reltuples: if >= 0, set reltuples to this value; else no change
@@ -2817,13 +2820,17 @@ FormIndexDatum(IndexInfo *indexInfo,
  * If reltuples >= 0, relpages, relallvisible, and relallfrozen are also
  * updated (using RelationGetNumberOfBlocks() and visibilitymap_count()).
  *
+ * For a global temporary relation, relhasindex is set in pg_class and all the
+ * other fields are set in pg_temp_class.  For any other type of relation, all
+ * the fields are set in pg_class.
+ *
  * NOTE: an important side-effect of this operation is that an SI invalidation
  * message is sent out to all backends --- including me --- causing relcache
  * entries to be flushed or updated with the new data.  This must happen even
- * if we find that no change is needed in the pg_class row.  When updating
- * a heap entry, this ensures that other backends find out about the new
- * index.  When updating an index, it's important because some index AMs
- * expect a relcache flush to occur after REINDEX.
+ * if we find that no change is needed in the pg_class or pg_temp_class rows.
+ * When updating a heap entry, this ensures that other backends find out about
+ * the new index.  When updating an index, it's important because some index
+ * AMs expect a relcache flush to occur after REINDEX.
  */
 static void
 index_update_stats(Relation rel,
@@ -2838,9 +2845,12 @@ index_update_stats(Relation rel,
 	Relation	pg_class;
 	ScanKeyData key[1];
 	HeapTuple	tuple;
+	HeapTuple	temp_tuple;
 	void	   *state;
 	Form_pg_class rd_rel;
+	Form_pg_temp_class temp_rd_rel;
 	bool		dirty;
+	bool		temp_dirty;
 
 	/*
 	 * As a special hack, if we are dealing with an empty table and the
@@ -2923,8 +2933,34 @@ index_update_stats(Relation rel,
 	 * relallvisible) if the caller isn't providing an updated reltuples
 	 * count, because that would bollix the reltuples/relpages ratio which is
 	 * what's really important.
+	 *
+	 * If not for (1) above, pg_temp_class could be updated normally, and in
+	 * fact we could work round (1) by simply not updating pg_temp_class in
+	 * bootstrap mode, since its value just gets thrown away when initdb
+	 * finishes.  However, for consistency, we update it in-place, like
+	 * pg_class --- see also vac_update_relstats().
 	 */
 
+	/*
+	 * For a global temporary relation, need a writable copy of its
+	 * pg_temp_class tuple.  Note: can't use systable_inplace_update_begin()
+	 * here because the tuple might be a pending insert --- see header
+	 * comments in pg_temp_class.c.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		temp_tuple = GetPgTempClassTuple(relid);
+		if (!HeapTupleIsValid(temp_tuple))
+			elog(ERROR, "cache lookup failed for global temp relation %u", relid);
+		temp_rd_rel = (Form_pg_temp_class) GETSTRUCT(temp_tuple);
+	}
+	else
+	{
+		temp_tuple = NULL;
+		temp_rd_rel = NULL;
+	}
+
+	/* Fetch a copy of the pg_class tuple to scribble on */
 	pg_class = table_open(RelationRelationId, RowExclusiveLock);
 
 	ScanKeyInit(&key[0],
@@ -2941,9 +2977,10 @@ index_update_stats(Relation rel,
 	/* Should this be a more comprehensive test? */
 	Assert(rd_rel->relkind != RELKIND_PARTITIONED_INDEX);
 
-	/* Apply required updates, if any, to copied tuple */
+	/* Apply required updates, if any, to copied tuple(s) */
 
 	dirty = false;
+	temp_dirty = false;
 	if (rd_rel->relhasindex != hasindex)
 	{
 		rd_rel->relhasindex = hasindex;
@@ -2952,30 +2989,18 @@ index_update_stats(Relation rel,
 
 	if (update_stats)
 	{
-		if (rd_rel->relpages != (int32) relpages)
-		{
-			rd_rel->relpages = (int32) relpages;
-			dirty = true;
-		}
-		if (rd_rel->reltuples != (float4) reltuples)
-		{
-			rd_rel->reltuples = (float4) reltuples;
-			dirty = true;
-		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
-		{
-			rd_rel->relallvisible = (int32) relallvisible;
-			dirty = true;
-		}
-		if (rd_rel->relallfrozen != (int32) relallfrozen)
-		{
-			rd_rel->relallfrozen = (int32) relallfrozen;
-			dirty = true;
-		}
+		SetEffective_relpages(rd_rel, temp_rd_rel, (int32) relpages,
+							  &dirty, &temp_dirty);
+		SetEffective_reltuples(rd_rel, temp_rd_rel, (float4) reltuples,
+							   &dirty, &temp_dirty);
+		SetEffective_relallvisible(rd_rel, temp_rd_rel, (int32) relallvisible,
+								   &dirty, &temp_dirty);
+		SetEffective_relallfrozen(rd_rel, temp_rd_rel, (int32) relallfrozen,
+								  &dirty, &temp_dirty);
 	}
 
 	/*
-	 * If anything changed, write out the tuple
+	 * If anything changed, write out the tuple(s)
 	 */
 	if (dirty)
 	{
@@ -2996,6 +3021,14 @@ index_update_stats(Relation rel,
 		CacheInvalidateRelcacheByTuple(tuple);
 	}
 
+	if (HeapTupleIsValid(temp_tuple))
+	{
+		if (temp_dirty)
+			UpdatePgTempClassTupleInPlace(relid, temp_tuple);
+
+		heap_freetuple(temp_tuple);
+	}
+
 	heap_freetuple(tuple);
 
 	table_close(pg_class, RowExclusiveLock);
diff --git a/src/backend/catalog/pg_temp_class.c b/src/backend/catalog/pg_temp_class.c
index 265018c2832..e26b193c772 100644
--- a/src/backend/catalog/pg_temp_class.c
+++ b/src/backend/catalog/pg_temp_class.c
@@ -52,6 +52,7 @@
 #include "catalog/indexing.h"
 #include "catalog/pg_temp_class.h"
 #include "miscadmin.h"
+#include "utils/fmgroids.h"
 #include "utils/hsearch.h"
 #include "utils/memutils.h"
 #include "utils/syscache.h"
@@ -138,6 +139,18 @@ get_pg_temp_class_tupdesc(void)
 		TupleDescInitEntry(tupdesc,
 						   (AttrNumber) Anum_pg_temp_class_reltablespace,
 						   "reltablespace", OIDOID, -1, 0);
+		TupleDescInitEntry(tupdesc,
+						   (AttrNumber) Anum_pg_temp_class_relpages,
+						   "relpages", INT4OID, -1, 0);
+		TupleDescInitEntry(tupdesc,
+						   (AttrNumber) Anum_pg_temp_class_reltuples,
+						   "reltuples", FLOAT4OID, -1, 0);
+		TupleDescInitEntry(tupdesc,
+						   (AttrNumber) Anum_pg_temp_class_relallvisible,
+						   "relallvisible", INT4OID, -1, 0);
+		TupleDescInitEntry(tupdesc,
+						   (AttrNumber) Anum_pg_temp_class_relallfrozen,
+						   "relallfrozen", INT4OID, -1, 0);
 		TupleDescFinalize(tupdesc);
 
 		MemoryContextSwitchTo(oldcontext);
@@ -164,6 +177,10 @@ heap_form_pg_temp_class_tuple(Relation rel)
 	values[Anum_pg_temp_class_oid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
 	values[Anum_pg_temp_class_relfilenode - 1] = ObjectIdGetDatum(form->relfilenode);
 	values[Anum_pg_temp_class_reltablespace - 1] = ObjectIdGetDatum(form->reltablespace);
+	values[Anum_pg_temp_class_relpages - 1] = Int32GetDatum(form->relpages);
+	values[Anum_pg_temp_class_reltuples - 1] = Float4GetDatum(form->reltuples);
+	values[Anum_pg_temp_class_relallvisible - 1] = Int32GetDatum(form->relallvisible);
+	values[Anum_pg_temp_class_relallfrozen - 1] = Int32GetDatum(form->relallfrozen);
 
 	return heap_form_tuple(get_pg_temp_class_tupdesc(), values, nulls);
 }
@@ -362,6 +379,66 @@ UpdatePgTempClassTuple(Oid relid, HeapTuple newtuple)
 	table_close(pg_temp_class, RowExclusiveLock);
 }
 
+/*
+ * UpdatePgTempClassTupleInPlace
+ *
+ *	Do an in-place update of the pg_temp_class tuple for a global temporary
+ *	relation.
+ */
+void
+UpdatePgTempClassTupleInPlace(Oid relid, HeapTuple newtuple)
+{
+	Relation	pg_temp_class;
+	ScanKeyData key[1];
+	HeapTuple	oldtuple;
+	void	   *inplace_state;
+
+	/* If there is a pending insert for this relation, just update that */
+	if (have_pending_inserts)
+	{
+		PendingInsert *entry;
+		Form_pg_temp_class old_form;
+		Form_pg_temp_class new_form;
+
+		entry = hash_search(pending_inserts, &relid, HASH_FIND, NULL);
+		if (entry != NULL)
+		{
+			old_form = (Form_pg_temp_class) GETSTRUCT(entry->tuple);
+			new_form = (Form_pg_temp_class) GETSTRUCT(newtuple);
+			COPY_PG_TEMP_CLASS_ATTRS(new_form, old_form);
+
+			return;
+		}
+	}
+
+	/*
+	 * Otherwise, update pg_temp_class directly, making note of the
+	 * subtransaction ID, if this is the first time opening pg_temp_class.
+	 */
+	pg_temp_class = table_open(TempRelationRelationId, RowExclusiveLock);
+	if (!pg_temp_class_opened)
+	{
+		pg_temp_class_opened = true;
+		pg_temp_class_subid = GetCurrentSubTransactionId();
+	}
+
+	ScanKeyInit(&key[0],
+				Anum_pg_temp_class_oid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relid));
+
+	systable_inplace_update_begin(pg_temp_class, TempClassOidIndexId, true,
+								  NULL, 1, key, &oldtuple, &inplace_state);
+	if (!HeapTupleIsValid(oldtuple))
+		elog(ERROR, "cache lookup failed for global temp relation %u", relid);
+
+	systable_inplace_update_finish(inplace_state, newtuple);
+
+	heap_freetuple(oldtuple);
+
+	table_close(pg_temp_class, RowExclusiveLock);
+}
+
 /*
  * DeletePgTempClassTuple
  *
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index d3d2b55b48b..5c55a071c89 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,7 +1288,9 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex,
 {
 	Relation	relRelation;
 	HeapTuple	reltup;
+	HeapTuple	temp_reltup;
 	Form_pg_class relform;
+	Form_pg_temp_class temp_relform;
 	TupleDesc	oldTupDesc PG_USED_FOR_ASSERTS_ONLY;
 	TupleDesc	newTupDesc PG_USED_FOR_ASSERTS_ONLY;
 	VacuumParams params;
@@ -1480,21 +1482,30 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex,
 					   tups_recently_dead,
 					   pg_rusage_show(&ru0))));
 
-	/* Update pg_class to reflect the correct values of pages and tuples. */
+	/*
+	 * Update pg_class / pg_temp_class to reflect the correct values of pages
+	 * and tuples.
+	 */
 	relRelation = table_open(RelationRelationId, RowExclusiveLock);
 
-	reltup = SearchSysCacheCopy1(RELOID,
-								 ObjectIdGetDatum(RelationGetRelid(NewHeap)));
+	reltup = GetPgClassAndPgTempClassTuples(RelationGetRelid(NewHeap), false,
+											&temp_reltup, true);
 	if (!HeapTupleIsValid(reltup))
 		elog(ERROR, "cache lookup failed for relation %u",
 			 RelationGetRelid(NewHeap));
 	relform = (Form_pg_class) GETSTRUCT(reltup);
+	temp_relform = (Form_pg_temp_class) GETSTRUCT_SAFE(temp_reltup);
 
-	relform->relpages = num_pages;
-	relform->reltuples = num_tuples;
+	SetEffective_relpages(relform, temp_relform, num_pages, NULL, NULL);
+	SetEffective_reltuples(relform, temp_relform, num_tuples, NULL, NULL);
 
 	/* Don't update the stats for pg_class.  See swap_relation_files. */
-	if (RelationGetRelid(OldHeap) != RelationRelationId)
+	if (HeapTupleIsValid(temp_reltup))
+	{
+		UpdatePgTempClassTuple(RelationGetRelid(NewHeap), temp_reltup);
+		heap_freetuple(temp_reltup);
+	}
+	else if (RelationGetRelid(OldHeap) != RelationRelationId)
 		CatalogTupleUpdate(relRelation, &reltup->t_self, reltup);
 	else
 		CacheInvalidateRelcacheByTuple(reltup);
@@ -1723,21 +1734,29 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
 		int32		swap_allvisible;
 		int32		swap_allfrozen;
 
-		swap_pages = relform1->relpages;
-		relform1->relpages = relform2->relpages;
-		relform2->relpages = swap_pages;
-
-		swap_tuples = relform1->reltuples;
-		relform1->reltuples = relform2->reltuples;
-		relform2->reltuples = swap_tuples;
-
-		swap_allvisible = relform1->relallvisible;
-		relform1->relallvisible = relform2->relallvisible;
-		relform2->relallvisible = swap_allvisible;
-
-		swap_allfrozen = relform1->relallfrozen;
-		relform1->relallfrozen = relform2->relallfrozen;
-		relform2->relallfrozen = swap_allfrozen;
+		swap_pages = GetEffective_relpages(relform1, temp_relform1);
+		SetEffective_relpages(relform1, temp_relform1,
+							  GetEffective_relpages(relform2, temp_relform2),
+							  NULL, NULL);
+		SetEffective_relpages(relform2, temp_relform2, swap_pages, NULL, NULL);
+
+		swap_tuples = GetEffective_reltuples(relform1, temp_relform1);
+		SetEffective_reltuples(relform1, temp_relform1,
+							   GetEffective_reltuples(relform2, temp_relform2),
+							   NULL, NULL);
+		SetEffective_reltuples(relform2, temp_relform2, swap_tuples, NULL, NULL);
+
+		swap_allvisible = GetEffective_relallvisible(relform1, temp_relform1);
+		SetEffective_relallvisible(relform1, temp_relform1,
+								   GetEffective_relallvisible(relform2, temp_relform2),
+								   NULL, NULL);
+		SetEffective_relallvisible(relform2, temp_relform2, swap_allvisible, NULL, NULL);
+
+		swap_allfrozen = GetEffective_relallfrozen(relform1, temp_relform1);
+		SetEffective_relallfrozen(relform1, temp_relform1,
+								  GetEffective_relallfrozen(relform2, temp_relform2),
+								  NULL, NULL);
+		SetEffective_relallfrozen(relform2, temp_relform2, swap_allfrozen, NULL, NULL);
 	}
 
 	/*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d76d51aaef5..20c8aafdb70 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1393,9 +1393,10 @@ vac_estimate_reltuples(Relation relation,
  *	vac_update_relstats() -- update statistics for one relation
  *
  *		Update the whole-relation statistics that are kept in its pg_class
- *		row.  There are additional stats that will be updated if we are
- *		doing ANALYZE, but we always update these stats.  This routine works
- *		for both index and heap relation entries in pg_class.
+ *		row (and its pg_temp_class row, for a global temporary relation).
+ *		There are additional stats that will be updated if we are doing
+ *		ANALYZE, but we always update these stats. This routine works for both
+ *		index and heap relation entries in pg_class and pg_temp_class.
  *
  *		We violate transaction semantics here by overwriting the rel's
  *		existing pg_class tuple with the new values.  This is reasonably
@@ -1404,12 +1405,17 @@ vac_estimate_reltuples(Relation relation,
  *		we updated these tuples in the usual way, vacuuming pg_class itself
  *		wouldn't work very well --- by the time we got done with a vacuum
  *		cycle, most of the tuples in pg_class would've been obsoleted.  Of
- *		course, this only works for fixed-size not-null columns, but these are.
+ *		course, this only works for fixed-size not-null columns, but these
+ *		are.  Likewise for pg_temp_class, which is also updated for a global
+ *		temporary relation.
  *
  *		Another reason for doing it this way is that when we are in a lazy
  *		VACUUM and have PROC_IN_VACUUM set, we mustn't do any regular updates.
  *		Somebody vacuuming pg_class might think they could delete a tuple
- *		marked with xmin = our xid.
+ *		marked with xmin = our xid.  This isn't a problem for pg_temp_class
+ *		because no other session can see our copy of its data, but it still
+ *		makes sense to do an in-place update to avoid vacuumed pg_temp_class
+ *		tuples being obsoleted.
  *
  *		In addition to fundamentally nontransactional statistics such as
  *		relpages and relallvisible, we try to maintain certain lazily-updated
@@ -1424,8 +1430,8 @@ vac_estimate_reltuples(Relation relation,
  *		transaction.  This is OK since postponing the flag maintenance is
  *		always allowable.
  *
- *		Note: num_tuples should count only *live* tuples, since
- *		pg_class.reltuples is defined that way.
+ *		Note: num_tuples should count only *live* tuples, since reltuples in
+ *		pg_class and pg_temp_class is defined that way.
  *
  *		This routine is shared by VACUUM and ANALYZE.
  */
@@ -1443,17 +1449,39 @@ vac_update_relstats(Relation relation,
 	Relation	rd;
 	ScanKeyData key[1];
 	HeapTuple	ctup;
+	HeapTuple	temp_ctup;
 	void	   *inplace_state;
 	Form_pg_class pgcform;
+	Form_pg_temp_class temp_pgcform;
 	bool		dirty,
+				temp_dirty,
 				futurexid,
 				futuremxid;
 	TransactionId oldfrozenxid;
 	MultiXactId oldminmulti;
 
+	/*
+	 * For a global temporary relation, need a writable copy of its
+	 * pg_temp_class tuple.  Note: can't use systable_inplace_update_begin()
+	 * here because the tuple might be a pending insert --- see header
+	 * comments in pg_temp_class.c.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		temp_ctup = GetPgTempClassTuple(relid);
+		if (!HeapTupleIsValid(temp_ctup))
+			elog(ERROR, "cache lookup failed for global temp relation %u", relid);
+		temp_pgcform = (Form_pg_temp_class) GETSTRUCT(temp_ctup);
+	}
+	else
+	{
+		temp_ctup = NULL;
+		temp_pgcform = NULL;
+	}
+
+	/* Fetch a copy of the pg_class tuple to scribble on */
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
-	/* Fetch a copy of the tuple to scribble on */
 	ScanKeyInit(&key[0],
 				Anum_pg_class_oid,
 				BTEqualStrategyNumber, F_OIDEQ,
@@ -1465,29 +1493,20 @@ vac_update_relstats(Relation relation,
 			 relid);
 	pgcform = (Form_pg_class) GETSTRUCT(ctup);
 
-	/* Apply statistical updates, if any, to copied tuple */
+	/* Apply statistical updates, if any, to copied tuple(s) */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
-	{
-		pgcform->relpages = (int32) num_pages;
-		dirty = true;
-	}
-	if (pgcform->reltuples != (float4) num_tuples)
-	{
-		pgcform->reltuples = (float4) num_tuples;
-		dirty = true;
-	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
-	{
-		pgcform->relallvisible = (int32) num_all_visible_pages;
-		dirty = true;
-	}
-	if (pgcform->relallfrozen != (int32) num_all_frozen_pages)
-	{
-		pgcform->relallfrozen = (int32) num_all_frozen_pages;
-		dirty = true;
-	}
+	temp_dirty = false;
+	SetEffective_relpages(pgcform, temp_pgcform, (int32) num_pages,
+						  &dirty, &temp_dirty);
+	SetEffective_reltuples(pgcform, temp_pgcform, (float4) num_tuples,
+						   &dirty, &temp_dirty);
+	SetEffective_relallvisible(pgcform, temp_pgcform,
+							   (int32) num_all_visible_pages,
+							   &dirty, &temp_dirty);
+	SetEffective_relallfrozen(pgcform, temp_pgcform,
+							  (int32) num_all_frozen_pages,
+							  &dirty, &temp_dirty);
 
 	/* Apply DDL updates, but not inside an outer transaction (see above) */
 
@@ -1570,12 +1589,22 @@ vac_update_relstats(Relation relation,
 		}
 	}
 
-	/* If anything changed, write out the tuple. */
+	/* If anything changed, write out the tuple(s) */
 	if (dirty)
 		systable_inplace_update_finish(inplace_state, ctup);
 	else
 		systable_inplace_update_cancel(inplace_state);
 
+	if (HeapTupleIsValid(temp_ctup))
+	{
+		if (temp_dirty)
+			UpdatePgTempClassTupleInPlace(relid, temp_ctup);
+
+		heap_freetuple(temp_ctup);
+	}
+
+	heap_freetuple(ctup);
+
 	table_close(rd, RowExclusiveLock);
 
 	if (futurexid)
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index d6631e9a9a4..26ec5b24692 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -20,6 +20,7 @@
 #include "access/heapam.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
+#include "catalog/pg_temp_class.h"
 #include "nodes/makefuncs.h"
 #include "statistics/stat_utils.h"
 #include "utils/builtins.h"
@@ -79,10 +80,11 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 	bool		update_relallfrozen = false;
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
-	int			replaces[4] = {0};
-	Datum		values[4] = {0};
-	bool		nulls[4] = {0};
-	int			nreplaces = 0;
+	Relation	rel;
+	HeapTuple	temp_ctup;
+	Form_pg_temp_class temp_pgcform;
+	bool		dirty;
+	bool		temp_dirty;
 	Oid			locked_table = InvalidOid;
 
 	stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG);
@@ -139,52 +141,64 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 	 */
 	crel = table_open(RelationRelationId, RowExclusiveLock);
 
-	ctup = SearchSysCache1(RELOID, ObjectIdGetDatum(reloid));
+	ctup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(reloid));
 	if (!HeapTupleIsValid(ctup))
 		elog(ERROR, "pg_class entry for relid %u not found", reloid);
 
 	pgcform = (Form_pg_class) GETSTRUCT(ctup);
 
-	if (update_relpages && relpages != pgcform->relpages)
+	/*
+	 * For a global temporary table, need to update the pg_temp_class tuple
+	 * instead.  Force it into existence by opening the relation.
+	 */
+	if (pgcform->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
-		replaces[nreplaces] = Anum_pg_class_relpages;
-		values[nreplaces] = UInt32GetDatum(relpages);
-		nreplaces++;
-	}
+		rel = relation_open(reloid, AccessShareLock);
+		relation_close(rel, AccessShareLock);
 
-	if (update_reltuples && reltuples != pgcform->reltuples)
-	{
-		replaces[nreplaces] = Anum_pg_class_reltuples;
-		values[nreplaces] = Float4GetDatum(reltuples);
-		nreplaces++;
-	}
+		temp_ctup = GetPgTempClassTuple(reloid);
+		if (!HeapTupleIsValid(temp_ctup))
+			elog(ERROR, "pg_temp_class entry for relid %u not found", reloid);
 
-	if (update_relallvisible && relallvisible != pgcform->relallvisible)
-	{
-		replaces[nreplaces] = Anum_pg_class_relallvisible;
-		values[nreplaces] = UInt32GetDatum(relallvisible);
-		nreplaces++;
+		temp_pgcform = (Form_pg_temp_class) GETSTRUCT(temp_ctup);
 	}
-
-	if (update_relallfrozen && relallfrozen != pgcform->relallfrozen)
+	else
 	{
-		replaces[nreplaces] = Anum_pg_class_relallfrozen;
-		values[nreplaces] = UInt32GetDatum(relallfrozen);
-		nreplaces++;
+		temp_ctup = NULL;
+		temp_pgcform = NULL;
 	}
 
-	if (nreplaces > 0)
+	dirty = false;
+	temp_dirty = false;
+
+	if (update_relpages)
+		SetEffective_relpages(pgcform, temp_pgcform, (int32) relpages,
+							  &dirty, &temp_dirty);
+
+	if (update_reltuples)
+		SetEffective_reltuples(pgcform, temp_pgcform, (float4) reltuples,
+							   &dirty, &temp_dirty);
+
+	if (update_relallvisible)
+		SetEffective_relallvisible(pgcform, temp_pgcform, (int32) relallvisible,
+								   &dirty, &temp_dirty);
+
+	if (update_relallfrozen)
+		SetEffective_relallfrozen(pgcform, temp_pgcform, (int32) relallfrozen,
+								  &dirty, &temp_dirty);
+
+	if (dirty)
+		CatalogTupleUpdate(crel, &ctup->t_self, ctup);
+
+	if (HeapTupleIsValid(temp_ctup))
 	{
-		TupleDesc	tupdesc = RelationGetDescr(crel);
-		HeapTuple	newtup;
+		if (temp_dirty)
+			UpdatePgTempClassTuple(reloid, temp_ctup);
 
-		newtup = heap_modify_tuple_by_cols(ctup, tupdesc, nreplaces,
-										   replaces, values, nulls);
-		CatalogTupleUpdate(crel, &newtup->t_self, newtup);
-		heap_freetuple(newtup);
+		heap_freetuple(temp_ctup);
 	}
 
-	ReleaseSysCache(ctup);
+	heap_freetuple(ctup);
 
 	/* release the lock, consistent with vac_update_relstats() */
 	table_close(crel, RowExclusiveLock);
diff --git a/src/backend/utils/cache/inval.c b/src/backend/utils/cache/inval.c
index f699fd4966f..f64b53e7f97 100644
--- a/src/backend/utils/cache/inval.c
+++ b/src/backend/utils/cache/inval.c
@@ -1435,6 +1435,7 @@ static void
 CacheInvalidateHeapTupleCommon(Relation relation,
 							   HeapTuple tuple,
 							   HeapTuple newtuple,
+							   bool inplace_update,
 							   InvalidationInfo *(*prepare_callback) (void))
 {
 	InvalidationInfo *info;
@@ -1511,7 +1512,7 @@ CacheInvalidateHeapTupleCommon(Relation relation,
 		 * happens when the relation is dropped, which already triggers a
 		 * relcache inval from pg_class.
 		 */
-		if (HeapTupleIsValid(newtuple))
+		if (inplace_update || HeapTupleIsValid(newtuple))
 		{
 			relationId = temp_classtup->oid;
 			databaseId = MyDatabaseId;
@@ -1592,7 +1593,7 @@ CacheInvalidateHeapTuple(Relation relation,
 						 HeapTuple tuple,
 						 HeapTuple newtuple)
 {
-	CacheInvalidateHeapTupleCommon(relation, tuple, newtuple,
+	CacheInvalidateHeapTupleCommon(relation, tuple, newtuple, false,
 								   PrepareInvalidationState);
 }
 
@@ -1613,7 +1614,7 @@ void
 CacheInvalidateHeapTupleInplace(Relation relation,
 								HeapTuple key_equivalent_tuple)
 {
-	CacheInvalidateHeapTupleCommon(relation, key_equivalent_tuple, NULL,
+	CacheInvalidateHeapTupleCommon(relation, key_equivalent_tuple, NULL, true,
 								   PrepareInplaceInvalidationState);
 }
 
diff --git a/src/include/catalog/pg_temp_class.h b/src/include/catalog/pg_temp_class.h
index 80011ea0954..e6b77b50d23 100644
--- a/src/include/catalog/pg_temp_class.h
+++ b/src/include/catalog/pg_temp_class.h
@@ -45,6 +45,18 @@ CATALOG(pg_temp_class,8082,TempRelationRelationId) BKI_TEMP_RELATION
 
 	/* identifier of table space for relation (0 means default for database) */
 	Oid			reltablespace BKI_DEFAULT(0) BKI_LOOKUP_OPT(pg_tablespace);
+
+	/* # of blocks (not always up-to-date) */
+	int32		relpages BKI_DEFAULT(0);
+
+	/* # of tuples (not always up-to-date; -1 means "unknown") */
+	float4		reltuples BKI_DEFAULT(-1);
+
+	/* # of all-visible blocks (not always up-to-date) */
+	int32		relallvisible BKI_DEFAULT(0);
+
+	/* # of all-frozen blocks (not always up-to-date) */
+	int32		relallfrozen BKI_DEFAULT(0);
 } FormData_pg_temp_class;
 
 END_CATALOG_STRUCT
@@ -71,6 +83,10 @@ MAKE_SYSCACHE(TEMPRELOID, pg_temp_class_oid_index, 128);
 		(target)->oid = (source)->oid; \
 		(target)->relfilenode = (source)->relfilenode; \
 		(target)->reltablespace = (source)->reltablespace; \
+		(target)->relpages = (source)->relpages; \
+		(target)->reltuples = (source)->reltuples; \
+		(target)->relallvisible = (source)->relallvisible; \
+		(target)->relallfrozen = (source)->relallfrozen; \
 	} while (0)
 
 /*
@@ -93,6 +109,46 @@ GetEffective_reltablespace(Form_pg_class cf, Form_pg_temp_class tf)
 	return tf != NULL ? tf->reltablespace : cf->reltablespace;
 }
 
+/*
+ * Get the effective value of relpages from pg_class and pg_temp_class tuple
+ * data.  The value from pg_temp_class (if present) takes precedence.
+ */
+static inline int32
+GetEffective_relpages(Form_pg_class cf, Form_pg_temp_class tf)
+{
+	return tf != NULL ? tf->relpages : cf->relpages;
+}
+
+/*
+ * Get the effective value of reltuples from pg_class and pg_temp_class tuple
+ * data.  The value from pg_temp_class (if present) takes precedence.
+ */
+static inline float4
+GetEffective_reltuples(Form_pg_class cf, Form_pg_temp_class tf)
+{
+	return tf != NULL ? tf->reltuples : cf->reltuples;
+}
+
+/*
+ * Get the effective value of relallvisible from pg_class and pg_temp_class
+ * tuple data.  The value from pg_temp_class (if present) takes precedence.
+ */
+static inline int32
+GetEffective_relallvisible(Form_pg_class cf, Form_pg_temp_class tf)
+{
+	return tf != NULL ? tf->relallvisible : cf->relallvisible;
+}
+
+/*
+ * Get the effective value of relallfrozen from pg_class and pg_temp_class
+ * tuple data.  The value from pg_temp_class (if present) takes precedence.
+ */
+static inline int32
+GetEffective_relallfrozen(Form_pg_class cf, Form_pg_temp_class tf)
+{
+	return tf != NULL ? tf->relallfrozen : cf->relallfrozen;
+}
+
 /*
  * Set the effective value of relfilenode in tuple form data from pg_class or
  * pg_temp_class.  The value is set in pg_temp_class instead of pg_class, if
@@ -121,6 +177,114 @@ SetEffective_reltablespace(Form_pg_class cf, Form_pg_temp_class tf, Oid val)
 		tf->reltablespace = val;
 }
 
+/*
+ * Set the effective value of relpages in tuple form data from pg_class or
+ * pg_temp_class.  The value is set in pg_temp_class instead of pg_class, if
+ * the pg_temp_class tuple form data is non-NULL.  If non-NULL, the cdirty or
+ * tdirty flag is updated, if the value actually changes.
+ */
+static inline void
+SetEffective_relpages(Form_pg_class cf, Form_pg_temp_class tf, int32 val,
+					  bool *cdirty, bool *tdirty)
+{
+	if (tf != NULL)
+	{
+		if (val != tf->relpages)
+		{
+			tf->relpages = val;
+			if (tdirty != NULL)
+				*tdirty = true;
+		}
+	}
+	else if (val != cf->relpages)
+	{
+		cf->relpages = val;
+		if (cdirty != NULL)
+			*cdirty = true;
+	}
+}
+
+/*
+ * Set the effective value of reltuples in tuple form data from pg_class or
+ * pg_temp_class.  The value is set in pg_temp_class instead of pg_class, if
+ * the pg_temp_class tuple form data is non-NULL.  If non-NULL, the cdirty or
+ * tdirty flag is updated, if the value actually changes.
+ */
+static inline void
+SetEffective_reltuples(Form_pg_class cf, Form_pg_temp_class tf, float4 val,
+					   bool *cdirty, bool *tdirty)
+{
+	if (tf != NULL)
+	{
+		if (val != tf->reltuples)
+		{
+			tf->reltuples = val;
+			if (tdirty != NULL)
+				*tdirty = true;
+		}
+	}
+	else if (val != cf->reltuples)
+	{
+		cf->reltuples = val;
+		if (cdirty != NULL)
+			*cdirty = true;
+	}
+}
+
+/*
+ * Set the effective value of relallvisible in tuple form data from pg_class
+ * or pg_temp_class.  The value is set in pg_temp_class instead of pg_class,
+ * if the pg_temp_class tuple form data is non-NULL.  If non-NULL, the cdirty
+ * or tdirty flag is updated, if the value actually changes.
+ */
+static inline void
+SetEffective_relallvisible(Form_pg_class cf, Form_pg_temp_class tf, int32 val,
+						   bool *cdirty, bool *tdirty)
+{
+	if (tf != NULL)
+	{
+		if (val != tf->relallvisible)
+		{
+			tf->relallvisible = val;
+			if (tdirty != NULL)
+				*tdirty = true;
+		}
+	}
+	else if (val != cf->relallvisible)
+	{
+		cf->relallvisible = val;
+		if (cdirty != NULL)
+			*cdirty = true;
+	}
+}
+
+/*
+ * Set the effective value of relallfrozen in tuple form data from pg_class or
+ * pg_temp_class.  The value is set in pg_temp_class instead of pg_class, if
+ * the pg_temp_class tuple form data is non-NULL.  If non-NULL, the cdirty or
+ * tdirty flag is updated, if the value actually changes.
+ */
+static inline void
+SetEffective_relallfrozen(Form_pg_class cf, Form_pg_temp_class tf, int32 val,
+						  bool *cdirty, bool *tdirty)
+{
+	if (tf != NULL)
+	{
+		if (val != tf->relallfrozen)
+		{
+			tf->relallfrozen = val;
+			if (tdirty != NULL)
+				*tdirty = true;
+		}
+	}
+	else if (val != cf->relallfrozen)
+	{
+		cf->relallfrozen = val;
+		if (cdirty != NULL)
+			*cdirty = true;
+	}
+}
+
 
 extern HeapTuple GetPgTempClassTuple(Oid relid);
 
@@ -128,6 +292,8 @@ extern void InsertPgTempClassTuple(Relation rel);
 
 extern void UpdatePgTempClassTuple(Oid relid, HeapTuple newtuple);
 
+extern void UpdatePgTempClassTupleInPlace(Oid relid, HeapTuple newtuple);
+
 extern void DeletePgTempClassTuple(Oid relid);
 
 extern HeapTuple GetPgClassAndPgTempClassTuples(Oid relid, bool lock_tuple,
diff --git a/src/test/regress/expected/global_temp.out b/src/test/regress/expected/global_temp.out
index 61cbc0314e5..9c4e7a0ea4d 100644
--- a/src/test/regress/expected/global_temp.out
+++ b/src/test/regress/expected/global_temp.out
@@ -417,6 +417,142 @@ SELECT relfilenode = pg_relation_filenode('tmp1'::regclass) AS ok
 \c
 SET search_path = global_temp_tests;
 VACUUM FULL tmp1;
+-- Test stats updates applied by CREATE INDEX, ANALYZE, VACUUM, and REPACK
+CREATE GLOBAL TEMP TABLE tmp2 (a int);
+INSERT INTO tmp2 SELECT * FROM generate_series(1, 100);
+SELECT c.oid::regclass,
+       c.relpages AS global_relpages, c.reltuples AS global_reltuples,
+       CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages,
+       t.reltuples AS local_reltuples
+  FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid
+ WHERE c.oid = 'tmp2'::regclass;
+ oid  | global_relpages | global_reltuples | local_relpages | local_reltuples 
+------+-----------------+------------------+----------------+-----------------
+ tmp2 |               0 |               -1 | zero           |              -1
+(1 row)
+
+CREATE INDEX tmp2_a_idx ON tmp2(a);
+SELECT c.oid::regclass,
+       c.relpages AS global_relpages, c.reltuples AS global_reltuples,
+       CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages,
+       t.reltuples AS local_reltuples
+  FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid
+ WHERE c.oid = 'tmp2'::regclass OR c.oid = 'tmp2_a_idx'::regclass ORDER BY 1;
+    oid     | global_relpages | global_reltuples | local_relpages | local_reltuples 
+------------+-----------------+------------------+----------------+-----------------
+ tmp2       |               0 |               -1 | non-zero       |             100
+ tmp2_a_idx |               0 |                0 | non-zero       |             100
+(2 rows)
+
+INSERT INTO tmp2 SELECT * FROM generate_series(101, 300);
+ANALYZE tmp2;
+SELECT c.oid::regclass,
+       c.relpages AS global_relpages, c.reltuples AS global_reltuples,
+       CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages,
+       t.reltuples AS local_reltuples
+  FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid
+ WHERE c.oid = 'tmp2'::regclass OR c.oid = 'tmp2_a_idx'::regclass ORDER BY 1;
+    oid     | global_relpages | global_reltuples | local_relpages | local_reltuples 
+------------+-----------------+------------------+----------------+-----------------
+ tmp2       |               0 |               -1 | non-zero       |             300
+ tmp2_a_idx |               0 |                0 | non-zero       |             300
+(2 rows)
+
+DELETE FROM tmp2 WHERE a % 2 = 0;
+VACUUM ANALYZE tmp2;
+SELECT c.oid::regclass,
+       c.relpages AS global_relpages, c.reltuples AS global_reltuples,
+       CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages,
+       t.reltuples AS local_reltuples
+  FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid
+ WHERE c.oid = 'tmp2'::regclass OR c.oid = 'tmp2_a_idx'::regclass ORDER BY 1;
+    oid     | global_relpages | global_reltuples | local_relpages | local_reltuples 
+------------+-----------------+------------------+----------------+-----------------
+ tmp2       |               0 |               -1 | non-zero       |             150
+ tmp2_a_idx |               0 |                0 | non-zero       |             150
+(2 rows)
+
+DELETE FROM tmp2 WHERE a % 3 = 0;
+REPACK (ANALYZE) tmp2;
+SELECT c.oid::regclass,
+       c.relpages AS global_relpages, c.reltuples AS global_reltuples,
+       CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages,
+       t.reltuples AS local_reltuples
+  FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid
+ WHERE c.oid = 'tmp2'::regclass OR c.oid = 'tmp2_a_idx'::regclass ORDER BY 1;
+    oid     | global_relpages | global_reltuples | local_relpages | local_reltuples 
+------------+-----------------+------------------+----------------+-----------------
+ tmp2       |               0 |               -1 | non-zero       |             100
+ tmp2_a_idx |               0 |               -1 | non-zero       |             100
+(2 rows)
+
+-- Test stats usage
+CREATE FUNCTION row_estimate(query text) RETURNS int
+LANGUAGE plpgsql AS
+$$
+DECLARE
+  line text;
+BEGIN
+  FOR line IN EXECUTE FORMAT('EXPLAIN %s', query)
+  LOOP
+    RETURN (regexp_match(line, 'rows=(\d*)'))[1]::int;
+  END LOOP;
+END;
+$$;
+SELECT row_estimate('SELECT * FROM tmp2');
+ row_estimate 
+--------------
+          100
+(1 row)
+
+-- Test manually updating stats
+SELECT pg_clear_relation_stats('global_temp_tests', 'tmp2');
+ pg_clear_relation_stats 
+-------------------------
+ 
+(1 row)
+
+SELECT oid::regclass, relpages, reltuples, relallvisible, relallfrozen
+  FROM pg_class WHERE oid = 'tmp2'::regclass;
+ oid  | relpages | reltuples | relallvisible | relallfrozen 
+------+----------+-----------+---------------+--------------
+ tmp2 |        0 |        -1 |             0 |            0
+(1 row)
+
+SELECT oid::regclass, relpages, reltuples, relallvisible, relallfrozen
+  FROM pg_temp_class WHERE oid = 'tmp2'::regclass;
+ oid  | relpages | reltuples | relallvisible | relallfrozen 
+------+----------+-----------+---------------+--------------
+ tmp2 |        0 |        -1 |             0 |            0
+(1 row)
+
+SELECT pg_restore_relation_stats(
+  'schemaname', 'global_temp_tests',
+  'relname', 'tmp2',
+  'relpages', 5,
+  'reltuples', 150::real,
+  'relallvisible', 10,
+  'relallfrozen', 20);
+ pg_restore_relation_stats 
+---------------------------
+ t
+(1 row)
+
+SELECT oid::regclass, relpages, reltuples, relallvisible, relallfrozen
+  FROM pg_class WHERE oid = 'tmp2'::regclass;
+ oid  | relpages | reltuples | relallvisible | relallfrozen 
+------+----------+-----------+---------------+--------------
+ tmp2 |        0 |        -1 |             0 |            0
+(1 row)
+
+SELECT oid::regclass, relpages, reltuples, relallvisible, relallfrozen
+  FROM pg_temp_class WHERE oid = 'tmp2'::regclass;
+ oid  | relpages | reltuples | relallvisible | relallfrozen 
+------+----------+-----------+---------------+--------------
+ tmp2 |        5 |       150 |            10 |           20
+(1 row)
+
+DROP TABLE tmp2;
 -- Test view creation
 INSERT INTO tmp1 VALUES (1, 'xxx');
 CREATE VIEW v AS SELECT * FROM tmp1;
diff --git a/src/test/regress/sql/global_temp.sql b/src/test/regress/sql/global_temp.sql
index 3c2e7eb988e..c1bc1f19ad6 100644
--- a/src/test/regress/sql/global_temp.sql
+++ b/src/test/regress/sql/global_temp.sql
@@ -246,6 +246,88 @@ SELECT relfilenode = pg_relation_filenode('tmp1'::regclass) AS ok
 SET search_path = global_temp_tests;
 VACUUM FULL tmp1;
 
+-- Test stats updates applied by CREATE INDEX, ANALYZE, VACUUM, and REPACK
+CREATE GLOBAL TEMP TABLE tmp2 (a int);
+INSERT INTO tmp2 SELECT * FROM generate_series(1, 100);
+SELECT c.oid::regclass,
+       c.relpages AS global_relpages, c.reltuples AS global_reltuples,
+       CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages,
+       t.reltuples AS local_reltuples
+  FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid
+ WHERE c.oid = 'tmp2'::regclass;
+
+CREATE INDEX tmp2_a_idx ON tmp2(a);
+SELECT c.oid::regclass,
+       c.relpages AS global_relpages, c.reltuples AS global_reltuples,
+       CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages,
+       t.reltuples AS local_reltuples
+  FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid
+ WHERE c.oid = 'tmp2'::regclass OR c.oid = 'tmp2_a_idx'::regclass ORDER BY 1;
+
+INSERT INTO tmp2 SELECT * FROM generate_series(101, 300);
+ANALYZE tmp2;
+SELECT c.oid::regclass,
+       c.relpages AS global_relpages, c.reltuples AS global_reltuples,
+       CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages,
+       t.reltuples AS local_reltuples
+  FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid
+ WHERE c.oid = 'tmp2'::regclass OR c.oid = 'tmp2_a_idx'::regclass ORDER BY 1;
+
+DELETE FROM tmp2 WHERE a % 2 = 0;
+VACUUM ANALYZE tmp2;
+SELECT c.oid::regclass,
+       c.relpages AS global_relpages, c.reltuples AS global_reltuples,
+       CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages,
+       t.reltuples AS local_reltuples
+  FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid
+ WHERE c.oid = 'tmp2'::regclass OR c.oid = 'tmp2_a_idx'::regclass ORDER BY 1;
+
+DELETE FROM tmp2 WHERE a % 3 = 0;
+REPACK (ANALYZE) tmp2;
+SELECT c.oid::regclass,
+       c.relpages AS global_relpages, c.reltuples AS global_reltuples,
+       CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages,
+       t.reltuples AS local_reltuples
+  FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid
+ WHERE c.oid = 'tmp2'::regclass OR c.oid = 'tmp2_a_idx'::regclass ORDER BY 1;
+
+-- Test stats usage
+CREATE FUNCTION row_estimate(query text) RETURNS int
+LANGUAGE plpgsql AS
+$$
+DECLARE
+  line text;
+BEGIN
+  FOR line IN EXECUTE FORMAT('EXPLAIN %s', query)
+  LOOP
+    RETURN (regexp_match(line, 'rows=(\d*)'))[1]::int;
+  END LOOP;
+END;
+$$;
+
+SELECT row_estimate('SELECT * FROM tmp2');
+
+-- Test manually updating stats
+SELECT pg_clear_relation_stats('global_temp_tests', 'tmp2');
+SELECT oid::regclass, relpages, reltuples, relallvisible, relallfrozen
+  FROM pg_class WHERE oid = 'tmp2'::regclass;
+SELECT oid::regclass, relpages, reltuples, relallvisible, relallfrozen
+  FROM pg_temp_class WHERE oid = 'tmp2'::regclass;
+
+SELECT pg_restore_relation_stats(
+  'schemaname', 'global_temp_tests',
+  'relname', 'tmp2',
+  'relpages', 5,
+  'reltuples', 150::real,
+  'relallvisible', 10,
+  'relallfrozen', 20);
+SELECT oid::regclass, relpages, reltuples, relallvisible, relallfrozen
+  FROM pg_class WHERE oid = 'tmp2'::regclass;
+SELECT oid::regclass, relpages, reltuples, relallvisible, relallfrozen
+  FROM pg_temp_class WHERE oid = 'tmp2'::regclass;
+
+DROP TABLE tmp2;
+
 -- Test view creation
 INSERT INTO tmp1 VALUES (1, 'xxx');
 CREATE VIEW v AS SELECT * FROM tmp1;
-- 
2.51.0

