From 79c30159895f3489a5cab1994ed3f3707c0b8884 Mon Sep 17 00:00:00 2001
From: Dean Rasheed <dean.a.rasheed@gmail.com>
Date: Thu, 18 Jun 2026 20:49:45 +0100
Subject: [PATCH v2 7/9] Add pg_temp_statistic global temporary catalog table.

This has the exact same columns as pg_statistic, but it is a global
temporary table, and is used to hold statistics about other global
temporary relations, allowing them to be session-specific.
---
 src/backend/catalog/heap.c                 |  20 ++-
 src/backend/catalog/system_views.sql       |   9 +-
 src/backend/commands/analyze.c             |  44 ++++--
 src/backend/executor/nodeHash.c            |   3 +-
 src/backend/statistics/attribute_stats.c   |  31 ++++-
 src/backend/utils/adt/selfuncs.c           |  18 ++-
 src/backend/utils/cache/lsyscache.c        |  23 +++-
 src/backend/utils/cache/relcache.c         |   1 +
 src/include/catalog/Makefile               |   3 +-
 src/include/catalog/meson.build            |   1 +
 src/include/catalog/pg_statistic.h         |   3 +
 src/include/catalog/pg_temp_statistic.h    | 147 +++++++++++++++++++++
 src/include/utils/lsyscache.h              |   1 +
 src/test/regress/expected/global_temp.out  |  37 ++++++
 src/test/regress/expected/oidjoins.out     |  11 ++
 src/test/regress/expected/rules.out        |  67 +++++++++-
 src/test/regress/expected/sanity_check.out |  23 ++++
 src/test/regress/sql/global_temp.sql       |  19 +++
 src/test/regress/sql/sanity_check.sql      |  20 +++
 19 files changed, 448 insertions(+), 33 deletions(-)
 create mode 100644 src/include/catalog/pg_temp_statistic.h

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 2170b1eb935..cf2bdcaadd3 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -53,6 +53,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_subscription_rel.h"
 #include "catalog/pg_tablespace.h"
+#include "catalog/pg_temp_statistic.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "commands/tablecmds.h"
@@ -3484,6 +3485,11 @@ CopyStatistics(Oid fromrelid, Oid torelid)
 	Relation	statrel;
 	CatalogIndexState indstate = NULL;
 
+	/*
+	 * Note: This is currently only used for concurrent index building, which
+	 * isn't supported on global temporary relations, so we never want
+	 * pg_temp_statistic here.
+	 */
 	statrel = table_open(StatisticRelationId, RowExclusiveLock);
 
 	/* Now search for stat records */
@@ -3532,12 +3538,22 @@ void
 RemoveStatistics(Oid relid, AttrNumber attnum)
 {
 	Relation	pgstatistic;
+	Oid			relidAttnumInhIndexId;
 	SysScanDesc scan;
 	ScanKeyData key[2];
 	int			nkeys;
 	HeapTuple	tuple;
 
-	pgstatistic = table_open(StatisticRelationId, RowExclusiveLock);
+	if (rel_is_global_temp(relid))
+	{
+		pgstatistic = table_open(TempStatisticRelationId, RowExclusiveLock);
+		relidAttnumInhIndexId = TempStatisticRelidAttnumInhIndexId;
+	}
+	else
+	{
+		pgstatistic = table_open(StatisticRelationId, RowExclusiveLock);
+		relidAttnumInhIndexId = StatisticRelidAttnumInhIndexId;
+	}
 
 	ScanKeyInit(&key[0],
 				Anum_pg_statistic_starelid,
@@ -3555,7 +3571,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
 		nkeys = 2;
 	}
 
-	scan = systable_beginscan(pgstatistic, StatisticRelidAttnumInhIndexId, true,
+	scan = systable_beginscan(pgstatistic, relidAttnumInhIndexId, true,
 							  NULL, nkeys, key);
 
 	/* we must loop even when attnum != 0, in case of inherited stats */
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 8f129baec90..8e6ff7c37a5 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -187,6 +187,11 @@ CREATE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+CREATE VIEW pg_all_statistic AS
+    SELECT * FROM pg_statistic
+    UNION ALL
+    SELECT * FROM pg_temp_statistic;
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
@@ -268,7 +273,7 @@ CREATE VIEW pg_stats WITH (security_barrier) AS
             WHEN stakind4 = 7 THEN stavalues4
             WHEN stakind5 = 7 THEN stavalues5
             END AS range_bounds_histogram
-    FROM pg_statistic s JOIN pg_class c ON (c.oid = s.starelid)
+    FROM pg_all_statistic s JOIN pg_class c ON (c.oid = s.starelid)
          JOIN pg_attribute a ON (c.oid = attrelid AND attnum = s.staattnum)
          LEFT JOIN pg_namespace n ON (n.oid = c.relnamespace)
     WHERE NOT attisdropped
@@ -276,6 +281,8 @@ CREATE VIEW pg_stats WITH (security_barrier) AS
     AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
 
 REVOKE ALL ON pg_statistic FROM public;
+REVOKE ALL ON pg_temp_statistic FROM public;
+REVOKE ALL ON pg_all_statistic FROM public;
 
 CREATE VIEW pg_stats_ext WITH (security_barrier) AS
     SELECT cn.nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..0db9810eba1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -29,6 +29,7 @@
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "catalog/pg_inherits.h"
+#include "catalog/pg_temp_statistic.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
@@ -176,9 +177,11 @@ analyze_rel(Oid relid, RangeVar *relation,
 	}
 
 	/*
-	 * We can ANALYZE any table except pg_statistic. See update_attstats
+	 * We can ANALYZE any table except pg_statistic and pg_temp_statistic. See
+	 * update_attstats
 	 */
-	if (RelationGetRelid(onerel) == StatisticRelationId)
+	if (RelationGetRelid(onerel) == StatisticRelationId ||
+		RelationGetRelid(onerel) == TempStatisticRelationId)
 	{
 		relation_close(onerel, ShareUpdateExclusiveLock);
 		return;
@@ -1691,20 +1694,23 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 /*
  *	update_attstats() -- update attribute statistics for one relation
  *
- *		Statistics are stored in several places: the pg_class row for the
- *		relation has stats about the whole relation, and there is a
- *		pg_statistic row for each (non-system) attribute that has ever
- *		been analyzed.  The pg_class values are updated by VACUUM, not here.
+ *		Statistics are stored in several places: the pg_class/pg_temp_class
+ *		row for the relation has stats about the whole relation, and there is
+ *		a pg_statistic/pg_temp_statistic row for each (non-system) attribute
+ *		that has ever been analyzed.  The pg_class/pg_temp_class values are
+ *		updated by VACUUM, not here.
  *
- *		pg_statistic rows are just added or updated normally.  This means
- *		that pg_statistic will probably contain some deleted rows at the
- *		completion of a vacuum cycle, unless it happens to get vacuumed last.
+ *		pg_statistic/pg_temp_statistic rows are just added or updated
+ *		normally.  This means that pg_statistic/pg_temp_statistic will
+ *		probably contain some deleted rows at the completion of a vacuum
+ *		cycle, unless it happens to get vacuumed last.
  *
- *		To keep things simple, we punt for pg_statistic, and don't try
- *		to compute or store rows for pg_statistic itself in pg_statistic.
+ *		To keep things simple, we punt for pg_statistic and pg_temp_statistic,
+ *		and don't try to compute or store rows for pg_statistic or
+ *		pg_temp_statistic themselves in pg_statistic or pg_temp_statistic.
  *		This could possibly be made to work, but it's not worth the trouble.
  *		Note analyze_rel() has seen to it that we won't come here when
- *		vacuuming pg_statistic itself.
+ *		vacuuming pg_statistic or pg_temp_statistic themselves.
  *
  *		Note: there would be a race condition here if two backends could
  *		ANALYZE the same table concurrently.  Presently, we lock that out
@@ -1716,11 +1722,21 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 	Relation	sd;
 	int			attno;
 	CatalogIndexState indstate = NULL;
+	SysCacheIdentifier cacheId;
 
 	if (natts <= 0)
 		return;					/* nothing to do */
 
-	sd = table_open(StatisticRelationId, RowExclusiveLock);
+	if (rel_is_global_temp(relid))
+	{
+		sd = table_open(TempStatisticRelationId, RowExclusiveLock);
+		cacheId = TEMPSTATRELATTINH;
+	}
+	else
+	{
+		sd = table_open(StatisticRelationId, RowExclusiveLock);
+		cacheId = STATRELATTINH;
+	}
 
 	for (attno = 0; attno < natts; attno++)
 	{
@@ -1811,7 +1827,7 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 		}
 
 		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
+		oldtup = SearchSysCache3(cacheId,
 								 ObjectIdGetDatum(relid),
 								 Int16GetDatum(stats->tupattnum),
 								 BoolGetDatum(inh));
diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c
index 8825bb6fa23..6ace6904810 100644
--- a/src/backend/executor/nodeHash.c
+++ b/src/backend/executor/nodeHash.c
@@ -2442,7 +2442,8 @@ ExecHashBuildSkewHash(HashState *hashstate, HashJoinTable hashtable,
 	/*
 	 * Try to find the MCV statistics for the outer relation's join key.
 	 */
-	statsTuple = SearchSysCache3(STATRELATTINH,
+	statsTuple = SearchSysCache3(rel_is_global_temp(node->skewTable) ?
+								 TEMPSTATRELATTINH : STATRELATTINH,
 								 ObjectIdGetDatum(node->skewTable),
 								 Int16GetDatum(node->skewColumn),
 								 BoolGetDatum(node->skewInherit));
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index 1cc4d657231..60ffc7db04f 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -21,6 +21,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_operator.h"
+#include "catalog/pg_temp_statistic.h"
 #include "nodes/makefuncs.h"
 #include "statistics/statistics.h"
 #include "statistics/stat_utils.h"
@@ -137,6 +138,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	Oid			locked_table = InvalidOid;
 
 	Relation	starel;
+	SysCacheIdentifier cacheId;
 	HeapTuple	statup;
 
 	Oid			atttypid = InvalidOid;
@@ -332,9 +334,18 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 
 	fmgr_info(F_ARRAY_IN, &array_in_fn);
 
-	starel = table_open(StatisticRelationId, RowExclusiveLock);
+	if (rel_is_global_temp(reloid))
+	{
+		starel = table_open(TempStatisticRelationId, RowExclusiveLock);
+		cacheId = TEMPSTATRELATTINH;
+	}
+	else
+	{
+		starel = table_open(StatisticRelationId, RowExclusiveLock);
+		cacheId = STATRELATTINH;
+	}
 
-	statup = SearchSysCache3(STATRELATTINH, ObjectIdGetDatum(reloid), Int16GetDatum(attnum), BoolGetDatum(inherited));
+	statup = SearchSysCache3(cacheId, ObjectIdGetDatum(reloid), Int16GetDatum(attnum), BoolGetDatum(inherited));
 
 	/* initialize from existing tuple if exists */
 	if (HeapTupleIsValid(statup))
@@ -567,12 +578,24 @@ upsert_pg_statistic(Relation starel, HeapTuple oldtup,
 static bool
 delete_pg_statistic(Oid reloid, AttrNumber attnum, bool stainherit)
 {
-	Relation	sd = table_open(StatisticRelationId, RowExclusiveLock);
+	Relation	sd;
 	HeapTuple	oldtup;
 	bool		result = false;
+	SysCacheIdentifier cacheId;
+
+	if (rel_is_global_temp(reloid))
+	{
+		sd = table_open(TempStatisticRelationId, RowExclusiveLock);
+		cacheId = TEMPSTATRELATTINH;
+	}
+	else
+	{
+		sd = table_open(StatisticRelationId, RowExclusiveLock);
+		cacheId = STATRELATTINH;
+	}
 
 	/* Is there already a pg_statistic tuple for this attribute? */
-	oldtup = SearchSysCache3(STATRELATTINH,
+	oldtup = SearchSysCache3(cacheId,
 							 ObjectIdGetDatum(reloid),
 							 Int16GetDatum(attnum),
 							 BoolGetDatum(stainherit));
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index d6efd07073a..11faeffe118 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5830,7 +5830,8 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						else if (index->indpred == NIL)
 						{
 							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
+								SearchSysCache3(rel_is_global_temp(index->indexoid) ?
+												TEMPSTATRELATTINH : STATRELATTINH,
 												ObjectIdGetDatum(index->indexoid),
 												Int16GetDatum(pos + 1),
 												BoolGetDatum(false));
@@ -6060,7 +6061,8 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 		 * Plain table or parent of an inheritance appendrel, so look up the
 		 * column in pg_statistic
 		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+		vardata->statsTuple = SearchSysCache3(rel_is_global_temp(rte->relid) ?
+											  TEMPSTATRELATTINH : STATRELATTINH,
 											  ObjectIdGetDatum(rte->relid),
 											  Int16GetDatum(var->varattno),
 											  BoolGetDatum(rte->inh));
@@ -6537,7 +6539,8 @@ examine_indexcol_variable(PlannerInfo *root, IndexOptInfo *index,
 		}
 		else
 		{
-			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+			vardata->statsTuple = SearchSysCache3(rel_is_global_temp(relid) ?
+												  TEMPSTATRELATTINH : STATRELATTINH,
 												  ObjectIdGetDatum(relid),
 												  Int16GetDatum(colnum),
 												  BoolGetDatum(rte->inh));
@@ -6563,7 +6566,8 @@ examine_indexcol_variable(PlannerInfo *root, IndexOptInfo *index,
 		}
 		else
 		{
-			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+			vardata->statsTuple = SearchSysCache3(rel_is_global_temp(relid) ?
+												  TEMPSTATRELATTINH : STATRELATTINH,
 												  ObjectIdGetDatum(relid),
 												  Int16GetDatum(colnum),
 												  BoolGetDatum(false));
@@ -9117,7 +9121,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 			else
 			{
 				vardata.statsTuple =
-					SearchSysCache3(STATRELATTINH,
+					SearchSysCache3(rel_is_global_temp(rte->relid) ?
+									TEMPSTATRELATTINH : STATRELATTINH,
 									ObjectIdGetDatum(rte->relid),
 									Int16GetDatum(attnum),
 									BoolGetDatum(false));
@@ -9147,7 +9152,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 			}
 			else
 			{
-				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
+				vardata.statsTuple = SearchSysCache3(rel_is_global_temp(index->indexoid) ?
+													 TEMPSTATRELATTINH : STATRELATTINH,
 													 ObjectIdGetDatum(index->indexoid),
 													 Int16GetDatum(attnum),
 													 BoolGetDatum(false));
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index cbd25fa6144..ecc34f07e36 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -41,6 +41,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_subscription.h"
 #include "catalog/pg_temp_class.h"
+#include "catalog/pg_temp_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
 #include "miscadmin.h"
@@ -2411,6 +2412,17 @@ get_rel_persistence(Oid relid)
 	return result;
 }
 
+/*
+ * rel_is_global_temp
+ *
+ *		Returns true if the given relation is a global temporary relation.
+ */
+bool
+rel_is_global_temp(Oid relid)
+{
+	return get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP;
+}
+
 /*
  * get_rel_relam
  *
@@ -3488,7 +3500,8 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
-	tp = SearchSysCache3(STATRELATTINH,
+	tp = SearchSysCache3(rel_is_global_temp(relid) ?
+						 TEMPSTATRELATTINH : STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
 						 BoolGetDatum(false));
@@ -3582,7 +3595,9 @@ get_attstatsslot(AttStatsSlot *sslot, HeapTuple statstuple,
 
 	if (flags & ATTSTATSSLOT_VALUES)
 	{
-		val = SysCacheGetAttrNotNull(STATRELATTINH, statstuple,
+		val = SysCacheGetAttrNotNull(IsTempStatisticTuple(statstuple) ?
+									 TEMPSTATRELATTINH : STATRELATTINH,
+									 statstuple,
 									 Anum_pg_statistic_stavalues1 + i);
 
 		/*
@@ -3627,7 +3642,9 @@ get_attstatsslot(AttStatsSlot *sslot, HeapTuple statstuple,
 
 	if (flags & ATTSTATSSLOT_NUMBERS)
 	{
-		val = SysCacheGetAttrNotNull(STATRELATTINH, statstuple,
+		val = SysCacheGetAttrNotNull(IsTempStatisticTuple(statstuple) ?
+									 TEMPSTATRELATTINH : STATRELATTINH,
+									 statstuple,
 									 Anum_pg_statistic_stanumbers1 + i);
 
 		/*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 52523b14e92..971e075bff6 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -62,6 +62,7 @@
 #include "catalog/pg_subscription.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_temp_class.h"
+#include "catalog/pg_temp_statistic.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
diff --git a/src/include/catalog/Makefile b/src/include/catalog/Makefile
index 629a13edc24..6aec03add22 100644
--- a/src/include/catalog/Makefile
+++ b/src/include/catalog/Makefile
@@ -87,7 +87,8 @@ CATALOG_HEADERS := \
 	pg_propgraph_label.h \
 	pg_propgraph_label_property.h \
 	pg_propgraph_property.h \
-	pg_temp_class.h
+	pg_temp_class.h \
+	pg_temp_statistic.h
 
 GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h)
 
diff --git a/src/include/catalog/meson.build b/src/include/catalog/meson.build
index 404f8503276..db809c4b3c0 100644
--- a/src/include/catalog/meson.build
+++ b/src/include/catalog/meson.build
@@ -75,6 +75,7 @@ catalog_headers = [
   'pg_propgraph_label_property.h',
   'pg_propgraph_property.h',
   'pg_temp_class.h',
+  'pg_temp_statistic.h',
 ]
 
 # The .dat files we need can just be listed alphabetically.
diff --git a/src/include/catalog/pg_statistic.h b/src/include/catalog/pg_statistic.h
index 032bf177b95..ff55f7f5bb4 100644
--- a/src/include/catalog/pg_statistic.h
+++ b/src/include/catalog/pg_statistic.h
@@ -28,6 +28,9 @@
  */
 BEGIN_CATALOG_STRUCT
 
+/*
+ * NB: Any changes made here must be reflected in pg_temp_statistic.
+ */
 CATALOG(pg_statistic,2619,StatisticRelationId)
 {
 	/* These fields form the unique key for the entry: */
diff --git a/src/include/catalog/pg_temp_statistic.h b/src/include/catalog/pg_temp_statistic.h
new file mode 100644
index 00000000000..afad9dd660c
--- /dev/null
+++ b/src/include/catalog/pg_temp_statistic.h
@@ -0,0 +1,147 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_temp_statistic.h
+ *	  definition of the "temporary statistics" system catalog
+ *	  (pg_temp_statistic)
+ *
+ * This is a global temporary system catalog table storing session-specific
+ * statistics for temporary relations.  Currently, it is only used for
+ * global temporary relations.
+ *
+ * Portions Copyright (c) 2026, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/pg_temp_statistic.h
+ *
+ * NOTES
+ *	  The Catalog.pm module reads this file and derives schema
+ *	  information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_TEMP_STATISTIC_H
+#define PG_TEMP_STATISTIC_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_temp_statistic_d.h"	/* IWYU pragma: export */
+
+/* ----------------
+ *		pg_temp_statistic definition.  cpp turns this into
+ *		typedef struct FormData_pg_temp_statistic
+ * ----------------
+ */
+BEGIN_CATALOG_STRUCT
+
+/*
+ * NB: The fields here must exactly match those in pg_statistic.
+ */
+CATALOG(pg_temp_statistic,8084,TempStatisticRelationId) BKI_TEMP_RELATION
+{
+	/* These fields form the unique key for the entry: */
+	Oid			starelid BKI_LOOKUP(pg_class);	/* relation containing
+												 * attribute */
+	int16		staattnum;		/* attribute (column) stats are for */
+	bool		stainherit;		/* true if inheritance children are included */
+
+	/* the fraction of the column's entries that are NULL: */
+	float4		stanullfrac;
+
+	/*
+	 * stawidth is the average width in bytes of non-null entries.  For
+	 * fixed-width datatypes this is of course the same as the typlen, but for
+	 * var-width types it is more useful.  Note that this is the average width
+	 * of the data as actually stored, post-TOASTing (eg, for a
+	 * moved-out-of-line value, only the size of the pointer object is
+	 * counted).  This is the appropriate definition for the primary use of
+	 * the statistic, which is to estimate sizes of in-memory hash tables of
+	 * tuples.
+	 */
+	int32		stawidth;
+
+	/* ----------------
+	 * stadistinct indicates the (approximate) number of distinct non-null
+	 * data values in the column.  The interpretation is:
+	 *		0		unknown or not computed
+	 *		> 0		actual number of distinct values
+	 *		< 0		negative of multiplier for number of rows
+	 * The special negative case allows us to cope with columns that are
+	 * unique (stadistinct = -1) or nearly so (for example, a column in which
+	 * non-null values appear about twice on the average could be represented
+	 * by stadistinct = -0.5 if there are no nulls, or -0.4 if 20% of the
+	 * column is nulls).  Because the number-of-rows statistic in pg_class may
+	 * be updated more frequently than pg_statistic is, it's important to be
+	 * able to describe such situations as a multiple of the number of rows,
+	 * rather than a fixed number of distinct values.  But in other cases a
+	 * fixed number is correct (eg, a boolean column).
+	 * ----------------
+	 */
+	float4		stadistinct;
+
+	/* ----------------
+	 * To allow keeping statistics on different kinds of datatypes,
+	 * we do not hard-wire any particular meaning for the remaining
+	 * statistical fields.  Instead, we provide several "slots" in which
+	 * statistical data can be placed.  Each slot includes:
+	 *		kind			integer code identifying kind of data (see below)
+	 *		op				OID of associated operator, if needed
+	 *		coll			OID of relevant collation, or 0 if none
+	 *		numbers			float4 array (for statistical values)
+	 *		values			anyarray (for representations of data values)
+	 * The ID, operator, and collation fields are never NULL; they are zeroes
+	 * in an unused slot.  The numbers and values fields are NULL in an
+	 * unused slot, and might also be NULL in a used slot if the slot kind
+	 * has no need for one or the other.
+	 * ----------------
+	 */
+
+	int16		stakind1;
+	int16		stakind2;
+	int16		stakind3;
+	int16		stakind4;
+	int16		stakind5;
+
+	Oid			staop1 BKI_LOOKUP_OPT(pg_operator);
+	Oid			staop2 BKI_LOOKUP_OPT(pg_operator);
+	Oid			staop3 BKI_LOOKUP_OPT(pg_operator);
+	Oid			staop4 BKI_LOOKUP_OPT(pg_operator);
+	Oid			staop5 BKI_LOOKUP_OPT(pg_operator);
+
+	Oid			stacoll1 BKI_LOOKUP_OPT(pg_collation);
+	Oid			stacoll2 BKI_LOOKUP_OPT(pg_collation);
+	Oid			stacoll3 BKI_LOOKUP_OPT(pg_collation);
+	Oid			stacoll4 BKI_LOOKUP_OPT(pg_collation);
+	Oid			stacoll5 BKI_LOOKUP_OPT(pg_collation);
+
+#ifdef CATALOG_VARLEN			/* variable-length fields start here */
+	float4		stanumbers1[1];
+	float4		stanumbers2[1];
+	float4		stanumbers3[1];
+	float4		stanumbers4[1];
+	float4		stanumbers5[1];
+
+	/*
+	 * Values in these arrays are values of the column's data type, or of some
+	 * related type such as an array element type.  We presently have to cheat
+	 * quite a bit to allow polymorphic arrays of this kind, but perhaps
+	 * someday it'll be a less bogus facility.
+	 */
+	anyarray	stavalues1;
+	anyarray	stavalues2;
+	anyarray	stavalues3;
+	anyarray	stavalues4;
+	anyarray	stavalues5;
+#endif
+} FormData_pg_temp_statistic;
+
+END_CATALOG_STRUCT
+
+DECLARE_TOAST(pg_temp_statistic, 8085, 8086);
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_temp_statistic_relid_att_inh_index, 8087, TempStatisticRelidAttnumInhIndexId, pg_temp_statistic, btree(starelid oid_ops, staattnum int2_ops, stainherit bool_ops));
+
+MAKE_SYSCACHE(TEMPSTATRELATTINH, pg_temp_statistic_relid_att_inh_index, 128);
+
+/* Is the specified tuple from pg_temp_statistic? */
+#define IsTempStatisticTuple(tuple) \
+	((tuple)->t_tableOid == TempStatisticRelationId)
+
+#endif							/* PG_TEMP_STATISTIC_H */
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 865980cb0f1..c556b667c5e 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -150,6 +150,7 @@ extern char get_rel_relkind(Oid relid);
 extern bool get_rel_relispartition(Oid relid);
 extern Oid	get_rel_tablespace(Oid relid);
 extern char get_rel_persistence(Oid relid);
+extern bool rel_is_global_temp(Oid relid);
 extern Oid	get_rel_relam(Oid relid);
 extern Oid	get_transform_fromsql(Oid typid, Oid langid, List *trftypes);
 extern Oid	get_transform_tosql(Oid typid, Oid langid, List *trftypes);
diff --git a/src/test/regress/expected/global_temp.out b/src/test/regress/expected/global_temp.out
index 9c4e7a0ea4d..d624f877e3d 100644
--- a/src/test/regress/expected/global_temp.out
+++ b/src/test/regress/expected/global_temp.out
@@ -552,6 +552,43 @@ SELECT oid::regclass, relpages, reltuples, relallvisible, relallfrozen
  tmp2 |        5 |       150 |            10 |           20
 (1 row)
 
+DROP TABLE tmp2;
+-- Test column stats
+CREATE GLOBAL TEMP TABLE tmp2 (a int, b int);
+INSERT INTO tmp2 SELECT x, floor(sqrt(x)) FROM generate_series(36, 99) x;
+ANALYZE tmp2;
+SELECT stavalues1 FROM pg_statistic
+ WHERE starelid = 'tmp2'::regclass AND staattnum = 2;
+ stavalues1 
+------------
+(0 rows)
+
+SELECT stavalues1 FROM pg_temp_statistic
+ WHERE starelid = 'tmp2'::regclass AND staattnum = 2;
+ stavalues1 
+------------
+ {9,8,7,6}
+(1 row)
+
+SELECT most_common_vals FROM pg_stats
+ WHERE tablename = 'tmp2' AND attname = 'b';
+ most_common_vals 
+------------------
+ {9,8,7,6}
+(1 row)
+
+SELECT COUNT(*) FROM tmp2 WHERE b = 8;
+ count 
+-------
+    17
+(1 row)
+
+SELECT row_estimate('SELECT * FROM tmp2 WHERE b = 8');
+ row_estimate 
+--------------
+           17
+(1 row)
+
 DROP TABLE tmp2;
 -- Test view creation
 INSERT INTO tmp1 VALUES (1, 'xxx');
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 3c3404e51b8..be2efa78257 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -287,3 +287,14 @@ NOTICE:  checking pg_propgraph_property {pgptypid} => pg_type {oid}
 NOTICE:  checking pg_propgraph_property {pgpcollation} => pg_collation {oid}
 NOTICE:  checking pg_temp_class {oid} => pg_class {oid}
 NOTICE:  checking pg_temp_class {reltablespace} => pg_tablespace {oid}
+NOTICE:  checking pg_temp_statistic {starelid} => pg_class {oid}
+NOTICE:  checking pg_temp_statistic {staop1} => pg_operator {oid}
+NOTICE:  checking pg_temp_statistic {staop2} => pg_operator {oid}
+NOTICE:  checking pg_temp_statistic {staop3} => pg_operator {oid}
+NOTICE:  checking pg_temp_statistic {staop4} => pg_operator {oid}
+NOTICE:  checking pg_temp_statistic {staop5} => pg_operator {oid}
+NOTICE:  checking pg_temp_statistic {stacoll1} => pg_collation {oid}
+NOTICE:  checking pg_temp_statistic {stacoll2} => pg_collation {oid}
+NOTICE:  checking pg_temp_statistic {stacoll3} => pg_collation {oid}
+NOTICE:  checking pg_temp_statistic {stacoll4} => pg_collation {oid}
+NOTICE:  checking pg_temp_statistic {stacoll5} => pg_collation {oid}
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index a65a5bf0c4f..f872091b82c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1302,6 +1302,71 @@ pg_aios| SELECT pid,
     f_localmem,
     f_buffered
    FROM pg_get_aios() pg_get_aios(pid, io_id, io_generation, state, operation, off, length, target, handle_data_len, raw_result, result, target_desc, f_sync, f_localmem, f_buffered);
+pg_all_statistic| SELECT pg_statistic.starelid,
+    pg_statistic.staattnum,
+    pg_statistic.stainherit,
+    pg_statistic.stanullfrac,
+    pg_statistic.stawidth,
+    pg_statistic.stadistinct,
+    pg_statistic.stakind1,
+    pg_statistic.stakind2,
+    pg_statistic.stakind3,
+    pg_statistic.stakind4,
+    pg_statistic.stakind5,
+    pg_statistic.staop1,
+    pg_statistic.staop2,
+    pg_statistic.staop3,
+    pg_statistic.staop4,
+    pg_statistic.staop5,
+    pg_statistic.stacoll1,
+    pg_statistic.stacoll2,
+    pg_statistic.stacoll3,
+    pg_statistic.stacoll4,
+    pg_statistic.stacoll5,
+    pg_statistic.stanumbers1,
+    pg_statistic.stanumbers2,
+    pg_statistic.stanumbers3,
+    pg_statistic.stanumbers4,
+    pg_statistic.stanumbers5,
+    pg_statistic.stavalues1,
+    pg_statistic.stavalues2,
+    pg_statistic.stavalues3,
+    pg_statistic.stavalues4,
+    pg_statistic.stavalues5
+   FROM pg_statistic
+UNION ALL
+ SELECT pg_temp_statistic.starelid,
+    pg_temp_statistic.staattnum,
+    pg_temp_statistic.stainherit,
+    pg_temp_statistic.stanullfrac,
+    pg_temp_statistic.stawidth,
+    pg_temp_statistic.stadistinct,
+    pg_temp_statistic.stakind1,
+    pg_temp_statistic.stakind2,
+    pg_temp_statistic.stakind3,
+    pg_temp_statistic.stakind4,
+    pg_temp_statistic.stakind5,
+    pg_temp_statistic.staop1,
+    pg_temp_statistic.staop2,
+    pg_temp_statistic.staop3,
+    pg_temp_statistic.staop4,
+    pg_temp_statistic.staop5,
+    pg_temp_statistic.stacoll1,
+    pg_temp_statistic.stacoll2,
+    pg_temp_statistic.stacoll3,
+    pg_temp_statistic.stacoll4,
+    pg_temp_statistic.stacoll5,
+    pg_temp_statistic.stanumbers1,
+    pg_temp_statistic.stanumbers2,
+    pg_temp_statistic.stanumbers3,
+    pg_temp_statistic.stanumbers4,
+    pg_temp_statistic.stanumbers5,
+    pg_temp_statistic.stavalues1,
+    pg_temp_statistic.stavalues2,
+    pg_temp_statistic.stavalues3,
+    pg_temp_statistic.stavalues4,
+    pg_temp_statistic.stavalues5
+   FROM pg_temp_statistic;
 pg_available_extension_versions| SELECT e.name,
     e.version,
     (x.extname IS NOT NULL) AS installed,
@@ -2692,7 +2757,7 @@ pg_stats| SELECT n.nspname AS schemaname,
             WHEN (s.stakind5 = 7) THEN s.stavalues5
             ELSE NULL::anyarray
         END AS range_bounds_histogram
-   FROM (((pg_statistic s
+   FROM (((pg_all_statistic s
      JOIN pg_class c ON ((c.oid = s.starelid)))
      JOIN pg_attribute a ON (((c.oid = a.attrelid) AND (a.attnum = s.staattnum))))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 8370c1561cc..5f5756d5596 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -25,3 +25,26 @@ SELECT relname, relkind
 ---------+---------
 (0 rows)
 
+-- check that pg_statistic and pg_temp_statistic have the exact same columns
+WITH t1 AS (
+  SELECT attname, atttypid, attlen, attnum, atttypmod, attndims, attbyval,
+         attalign, attstorage, attcompression, attnotnull, atthasdef,
+         atthasmissing, attidentity, attgenerated, attisdropped, attislocal,
+         attinhcount, attcollation
+    FROM pg_attribute
+   WHERE attrelid = 'pg_statistic'::regclass
+), t2 AS (
+  SELECT attname, atttypid, attlen, attnum, atttypmod, attndims, attbyval,
+         attalign, attstorage, attcompression, attnotnull, atthasdef,
+         atthasmissing, attidentity, attgenerated, attisdropped, attislocal,
+         attinhcount, attcollation
+    FROM pg_attribute
+   WHERE attrelid = 'pg_temp_statistic'::regclass
+)
+(SELECT * FROM t1 EXCEPT SELECT * FROM t2)
+UNION ALL
+(SELECT * FROM t2 EXCEPT SELECT * FROM t1);
+ attname | atttypid | attlen | attnum | atttypmod | attndims | attbyval | attalign | attstorage | attcompression | attnotnull | atthasdef | atthasmissing | attidentity | attgenerated | attisdropped | attislocal | attinhcount | attcollation 
+---------+----------+--------+--------+-----------+----------+----------+----------+------------+----------------+------------+-----------+---------------+-------------+--------------+--------------+------------+-------------+--------------
+(0 rows)
+
diff --git a/src/test/regress/sql/global_temp.sql b/src/test/regress/sql/global_temp.sql
index c1bc1f19ad6..2481ec0be56 100644
--- a/src/test/regress/sql/global_temp.sql
+++ b/src/test/regress/sql/global_temp.sql
@@ -328,6 +328,25 @@ SELECT oid::regclass, relpages, reltuples, relallvisible, relallfrozen
 
 DROP TABLE tmp2;
 
+-- Test column stats
+CREATE GLOBAL TEMP TABLE tmp2 (a int, b int);
+INSERT INTO tmp2 SELECT x, floor(sqrt(x)) FROM generate_series(36, 99) x;
+ANALYZE tmp2;
+
+SELECT stavalues1 FROM pg_statistic
+ WHERE starelid = 'tmp2'::regclass AND staattnum = 2;
+
+SELECT stavalues1 FROM pg_temp_statistic
+ WHERE starelid = 'tmp2'::regclass AND staattnum = 2;
+
+SELECT most_common_vals FROM pg_stats
+ WHERE tablename = 'tmp2' AND attname = 'b';
+
+SELECT COUNT(*) FROM tmp2 WHERE b = 8;
+SELECT row_estimate('SELECT * FROM tmp2 WHERE b = 8');
+
+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/sanity_check.sql b/src/test/regress/sql/sanity_check.sql
index 162e5324b5d..ed0096d7823 100644
--- a/src/test/regress/sql/sanity_check.sql
+++ b/src/test/regress/sql/sanity_check.sql
@@ -19,3 +19,23 @@ SELECT relname, relkind
   FROM pg_class
  WHERE relkind IN ('v', 'c', 'f', 'p', 'I')
        AND relfilenode <> 0;
+
+-- check that pg_statistic and pg_temp_statistic have the exact same columns
+WITH t1 AS (
+  SELECT attname, atttypid, attlen, attnum, atttypmod, attndims, attbyval,
+         attalign, attstorage, attcompression, attnotnull, atthasdef,
+         atthasmissing, attidentity, attgenerated, attisdropped, attislocal,
+         attinhcount, attcollation
+    FROM pg_attribute
+   WHERE attrelid = 'pg_statistic'::regclass
+), t2 AS (
+  SELECT attname, atttypid, attlen, attnum, atttypmod, attndims, attbyval,
+         attalign, attstorage, attcompression, attnotnull, atthasdef,
+         atthasmissing, attidentity, attgenerated, attisdropped, attislocal,
+         attinhcount, attcollation
+    FROM pg_attribute
+   WHERE attrelid = 'pg_temp_statistic'::regclass
+)
+(SELECT * FROM t1 EXCEPT SELECT * FROM t2)
+UNION ALL
+(SELECT * FROM t2 EXCEPT SELECT * FROM t1);
-- 
2.51.0

