From 64bf03d87a26c0f79532e5986914c26542882368 Mon Sep 17 00:00:00 2001
From: Dean Rasheed <dean.a.rasheed@gmail.com>
Date: Thu, 18 Jun 2026 23:38:12 +0100
Subject: [PATCH v10 09/11] Add pg_temp_statistic_ext_data global temporary
 catalog table.

This has the exact same columns as pg_statistic_ext_data, but it is a
global temporary table, and is used to hold extended statistics data
for global temporary tables, allowing the data to be session-specific
while the extended statistics object definitions remain shared.
---
 doc/src/sgml/catalogs.sgml                    |  51 ++++++++
 doc/src/sgml/func/func-statistics.sgml        |   5 +-
 doc/src/sgml/perform.sgml                     |   8 +-
 doc/src/sgml/system-views.sgml                |  24 ++--
 src/backend/catalog/genbki.pl                 |   1 +
 src/backend/catalog/global_temp.c             |  35 +++++-
 src/backend/catalog/system_views.sql          |  14 ++-
 src/backend/commands/statscmds.c              |  21 +++-
 src/backend/optimizer/util/plancat.c          |  13 +-
 src/backend/statistics/dependencies.c         |  11 +-
 src/backend/statistics/extended_stats.c       |  27 +++--
 src/backend/statistics/extended_stats_funcs.c |  48 ++++++--
 src/backend/statistics/mcv.c                  |  13 +-
 src/backend/statistics/mvdistinct.c           |  10 +-
 src/backend/utils/adt/selfuncs.c              |   5 +-
 src/include/catalog/Makefile                  |   3 +-
 src/include/catalog/meson.build               |   1 +
 src/include/catalog/pg_statistic_ext_data.h   |   3 +
 .../catalog/pg_temp_statistic_ext_data.h      |  62 ++++++++++
 src/include/commands/defrem.h                 |   2 +-
 src/include/statistics/statistics.h           |   8 +-
 src/test/isolation/expected/global-temp.out   | 111 ++++++++++--------
 src/test/isolation/specs/global-temp.spec     |  12 +-
 src/test/regress/expected/global_temp.out     |  71 +++++++++++
 src/test/regress/expected/oidjoins.out        |   1 +
 src/test/regress/expected/rules.out           |  19 ++-
 src/test/regress/expected/sanity_check.out    |  24 ++++
 src/test/regress/sql/global_temp.sql          |  41 +++++++
 src/test/regress/sql/sanity_check.sql         |  21 ++++
 29 files changed, 545 insertions(+), 120 deletions(-)
 create mode 100644 src/include/catalog/pg_temp_statistic_ext_data.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 5ecaf4f2764..1ff0ad02265 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -360,6 +360,11 @@
       <entry>planner statistics for global temporary relations</entry>
      </row>
 
+     <row>
+      <entry><link linkend="catalog-pg-temp-statistic-ext-data"><structname>pg_temp_statistic_ext_data</structname></link></entry>
+      <entry>extended planner statistics (built statistics) for global temporary tables</entry>
+     </row>
+
      <row>
       <entry><link linkend="catalog-pg-transform"><structname>pg_transform</structname></link></entry>
       <entry>transforms (data type to procedural language conversions)</entry>
@@ -8523,6 +8528,16 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
    information about tables the current user owns.
   </para>
 
+  <para>
+   Extended statistics data for global temporary tables is stored in
+   <link linkend="catalog-pg-temp-statistic-ext-data"><structname>pg_temp_statistic_ext_data</structname></link>,
+   instead of <structname>pg_statistic_ext_data</structname>, because the
+   contents of global temporary tables can vary between different sessions,
+   and so the statistical data needs to be local to each session
+   (<structname>pg_temp_statistic_ext_data</structname> is itself a global
+   temporary table).
+  </para>
+
   <table>
    <title><structname>pg_statistic_ext_data</structname> Columns</title>
    <tgroup cols="1">
@@ -9303,6 +9318,42 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
  </sect1>
 
 
+ <sect1 id="catalog-pg-temp-statistic-ext-data">
+  <title><structname>pg_temp_statistic_ext_data</structname></title>
+
+  <indexterm zone="catalog-pg-temp-statistic-ext-data">
+   <primary>pg_temp_statistic_ext_data</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_temp_statistic_ext_data</structname> is a global
+   temporary table that holds data for extended planner statistics defined in
+   <link linkend="catalog-pg-statistic-ext"><structname>pg_statistic_ext</structname></link>
+   for global temporary tables.  Each row in this catalog corresponds to a
+   <firstterm>statistics object</firstterm> created with
+   <link linkend="sql-createstatistics"><command>CREATE STATISTICS</command></link>
+   on a global temporary table.
+  </para>
+
+  <para>
+   <structname>pg_temp_statistic_ext_data</structname> has the same columns as
+   <structname>pg_statistic_ext_data</structname>, and its contents are created
+   and used by the planner in exactly the same way, except that they are local
+   to the current session, and are automatically deleted when the session exits.
+   Therefore, each session should run
+   <link linkend="sql-analyze"><command>ANALYZE</command></link> to create
+   its own extended statistics data for the contents of global temporary tables
+   with extended statistics defined, used in the session.
+  </para>
+
+  <para>
+   For more information about the table structure, and how its contents are
+   interpreted, see
+   <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>.
+  </para>
+ </sect1>
+
+
  <sect1 id="catalog-pg-transform">
   <title><structname>pg_transform</structname></title>
 
diff --git a/doc/src/sgml/func/func-statistics.sgml b/doc/src/sgml/func/func-statistics.sgml
index 22dee263cc2..2373fe4faf9 100644
--- a/doc/src/sgml/func/func-statistics.sgml
+++ b/doc/src/sgml/func/func-statistics.sgml
@@ -77,8 +77,9 @@ SELECT m.* FROM pg_statistic_ext join pg_statistic_ext_data on (oid = stxoid),
 </programlisting>
 
     Values of the <type>pg_mcv_list</type> type can be obtained only from the
-    <structname>pg_statistic_ext_data</structname>.<structfield>stxdmcv</structfield>
-    column.
+    <structname>pg_statistic_ext_data</structname>.<structfield>stxdmcv</structfield> and
+    <structname>pg_temp_statistic_ext_data</structname>.<structfield>stxdmcv</structfield>
+    columns.
    </para>
   </sect2>
 
diff --git a/doc/src/sgml/perform.sgml b/doc/src/sgml/perform.sgml
index e0117a8d953..7651c835db2 100644
--- a/doc/src/sgml/perform.sgml
+++ b/doc/src/sgml/perform.sgml
@@ -1457,6 +1457,10 @@ WHERE tablename = 'road';
     <primary>pg_statistic_ext_data</primary>
    </indexterm>
 
+   <indexterm>
+    <primary>pg_temp_statistic_ext_data</primary>
+   </indexterm>
+
    <para>
     It is common to see slow queries running bad execution plans because
     multiple columns used in the query clauses are correlated.
@@ -1486,7 +1490,9 @@ WHERE tablename = 'road';
     by <command>ANALYZE</command> (either a manual command, or background
     auto-analyze).  The collected values can be examined in the
     <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>
-    catalog.
+    catalog (or the
+    <link linkend="catalog-pg-temp-statistic-ext-data"><structname>pg_temp_statistic_ext_data</structname></link>
+    catalog, for global temporary tables).
    </para>
 
    <para>
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 6b1dd82e044..5ebc3fe4847 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -4641,10 +4641,13 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
    The view <structname>pg_stats_ext</structname> provides access to
    information about each extended statistics object in the database,
    combining information stored in the <link
-   linkend="catalog-pg-statistic-ext"><structname>pg_statistic_ext</structname></link>
-   and <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>
+   linkend="catalog-pg-statistic-ext"><structname>pg_statistic_ext</structname></link>,
+   <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>, and
+   <link linkend="catalog-pg-temp-statistic-ext-data"><structname>pg_temp_statistic_ext_data</structname></link>
    catalogs.  This view allows access only to rows of
-   <link linkend="catalog-pg-statistic-ext"><structname>pg_statistic_ext</structname></link> and <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>
+   <link linkend="catalog-pg-statistic-ext"><structname>pg_statistic_ext</structname></link>,
+   <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>, and
+   <link linkend="catalog-pg-temp-statistic-ext-data"><structname>pg_temp_statistic_ext_data</structname></link>
    that correspond to tables the user owns, and therefore
    it is safe to allow public read access to this view.
   </para>
@@ -4772,7 +4775,8 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>inherited</structfield> <type>bool</type>
-       (references <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>.<structfield>stxdinherit</structfield>)
+       (references <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>.<structfield>stxdinherit</structfield> or
+       <link linkend="catalog-pg-temp-statistic-ext-data"><structname>pg_temp_statistic_ext_data</structname></link>.<structfield>stxdinherit</structfield>)
       </para>
       <para>
        If true, the stats include values from child tables, not just the
@@ -4872,10 +4876,13 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
    The view <structname>pg_stats_ext_exprs</structname> provides access to
    information about all expressions included in extended statistics objects,
    combining information stored in the <link
-   linkend="catalog-pg-statistic-ext"><structname>pg_statistic_ext</structname></link>
-   and <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>
+   linkend="catalog-pg-statistic-ext"><structname>pg_statistic_ext</structname></link>,
+   <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>, and
+   <link linkend="catalog-pg-temp-statistic-ext-data"><structname>pg_temp_statistic_ext_data</structname></link>
    catalogs.  This view allows access only to rows of
-   <link linkend="catalog-pg-statistic-ext"><structname>pg_statistic_ext</structname></link> and <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>
+   <link linkend="catalog-pg-statistic-ext"><structname>pg_statistic_ext</structname></link>,
+   <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>, and
+   <link linkend="catalog-pg-temp-statistic-ext-data"><structname>pg_temp_statistic_ext_data</structname></link>
    that correspond to tables the user owns, and therefore
    it is safe to allow public read access to this view.
   </para>
@@ -4984,7 +4991,8 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>inherited</structfield> <type>bool</type>
-       (references <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>.<structfield>stxdinherit</structfield>)
+       (references <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>.<structfield>stxdinherit</structfield> or
+       <link linkend="catalog-pg-temp-statistic-ext-data"><structname>pg_temp_statistic_ext_data</structname></link>.<structfield>stxdinherit</structfield>)
       </para>
       <para>
        If true, the stats include values from child tables, not just the
diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl
index fe58334747d..fded3bfcf6c 100644
--- a/src/backend/catalog/genbki.pl
+++ b/src/backend/catalog/genbki.pl
@@ -802,6 +802,7 @@ print $syscache_ids_fh "#ifndef SYSCACHE_IDS_H
 
 #include \"catalog/pg_temp_class_d.h\"
 #include \"catalog/pg_temp_statistic_d.h\"
+#include \"catalog/pg_temp_statistic_ext_data_d.h\"
 
 typedef enum SysCacheIdentifier
 {
diff --git a/src/backend/catalog/global_temp.c b/src/backend/catalog/global_temp.c
index 7d265ab76a2..3a6e58e14ef 100644
--- a/src/backend/catalog/global_temp.c
+++ b/src/backend/catalog/global_temp.c
@@ -1146,6 +1146,8 @@ ProcessInvalidatedGlobalTempRelations(void)
 	{
 		bool		tuples_deleted = false;
 		Relation	statrel;
+		SysScanDesc scan;
+		HeapTuple	tuple;
 
 		/*
 		 * Delete and forget locally-created storage for dropped relations.
@@ -1188,8 +1190,6 @@ ProcessInvalidatedGlobalTempRelations(void)
 		foreach_oid(relid, gtrs_dropped)
 		{
 			ScanKeyData key[1];
-			SysScanDesc scan;
-			HeapTuple	tuple;
 
 			gtr_remove_usage(relid);
 			remove_on_commit_action(relid);
@@ -1222,6 +1222,37 @@ ProcessInvalidatedGlobalTempRelations(void)
 
 		table_close(statrel, RowExclusiveLock);
 
+		/*
+		 * Delete any orphaned extended stats data from
+		 * pg_temp_statistic_ext_data.  This requires a full table scan, since
+		 * there is no stxrelid column referring back to the relation.  That
+		 * also means that it's not strictly limited to gtrs_dropped, but
+		 * that's probably no bad thing --- it will tidy up *any* orphaned
+		 * global temporary extended stats data, though in theory, that should
+		 * be limited to gtrs_dropped.
+		 */
+		statrel = table_open(TempStatisticExtDataRelationId, RowExclusiveLock);
+
+		scan = systable_beginscan(statrel, InvalidOid, false, NULL, 0, NULL);
+
+		while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+		{
+			Oid			stxoid;
+
+			stxoid = SysCacheGetAttrNotNull(TEMPSTATEXTDATASTXOID, tuple,
+											Anum_pg_temp_statistic_ext_data_stxoid);
+
+			if (!SearchSysCacheExists1(STATEXTOID, ObjectIdGetDatum(stxoid)))
+			{
+				CatalogTupleDelete(statrel, &tuple->t_self);
+				tuples_deleted = true;
+			}
+		}
+
+		systable_endscan(scan);
+
+		table_close(statrel, RowExclusiveLock);
+
 		/* If we deleted anything, make the changes visible */
 		if (tuples_deleted)
 			CommandCounterIncrement();
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 8e1369caa9c..a30dcc9bcb0 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -284,6 +284,11 @@ 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_all_statistic_ext_data AS
+    SELECT * FROM pg_statistic_ext_data
+    UNION ALL
+    SELECT * FROM pg_temp_statistic_ext_data;
+
 CREATE VIEW pg_stats_ext WITH (security_barrier) AS
     SELECT cn.nspname AS schemaname,
            c.relname AS tablename,
@@ -307,7 +312,7 @@ CREATE VIEW pg_stats_ext WITH (security_barrier) AS
            m.most_common_freqs,
            m.most_common_base_freqs
     FROM pg_statistic_ext s JOIN pg_class c ON (c.oid = s.stxrelid)
-         JOIN pg_statistic_ext_data sd ON (s.oid = sd.stxoid)
+         JOIN pg_all_statistic_ext_data sd ON (s.oid = sd.stxoid)
          LEFT JOIN pg_namespace cn ON (cn.oid = c.relnamespace)
          LEFT JOIN pg_namespace sn ON (sn.oid = s.stxnamespace)
          LEFT JOIN LATERAL
@@ -404,7 +409,7 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
                WHEN (stat.a).stakind5 = 7 THEN (stat.a).stavalues5
                END) AS range_bounds_histogram
     FROM pg_statistic_ext s JOIN pg_class c ON (c.oid = s.stxrelid)
-         LEFT JOIN pg_statistic_ext_data sd ON (s.oid = sd.stxoid)
+         LEFT JOIN pg_all_statistic_ext_data sd ON (s.oid = sd.stxoid)
          LEFT JOIN pg_namespace cn ON (cn.oid = c.relnamespace)
          LEFT JOIN pg_namespace sn ON (sn.oid = s.stxnamespace)
          JOIN LATERAL (
@@ -414,8 +419,11 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
     WHERE pg_has_role(c.relowner, 'USAGE')
     AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
 
--- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
+-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data,
+-- pg_temp_statistic_ext_data, or pg_all_statistic_ext_data
 REVOKE ALL ON pg_statistic_ext_data FROM public;
+REVOKE ALL ON pg_temp_statistic_ext_data FROM public;
+REVOKE ALL ON pg_all_statistic_ext_data FROM public;
 
 CREATE VIEW pg_publication_tables AS
     SELECT
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index c4b5b478644..ce47738b51f 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -25,6 +25,7 @@
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_statistic_ext_data.h"
+#include "catalog/pg_temp_statistic_ext_data.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
 #include "miscadmin.h"
@@ -790,14 +791,24 @@ AlterStatistics(AlterStatsStmt *stmt)
  * exists, so don't error out.
  */
 void
-RemoveStatisticsDataById(Oid statsOid, bool inh)
+RemoveStatisticsDataById(Oid relid, Oid statsOid, bool inh)
 {
 	Relation	relation;
+	SysCacheIdentifier cacheId;
 	HeapTuple	tup;
 
-	relation = table_open(StatisticExtDataRelationId, RowExclusiveLock);
+	if (rel_is_global_temp(relid))
+	{
+		relation = table_open(TempStatisticExtDataRelationId, RowExclusiveLock);
+		cacheId = TEMPSTATEXTDATASTXOID;
+	}
+	else
+	{
+		relation = table_open(StatisticExtDataRelationId, RowExclusiveLock);
+		cacheId = STATEXTDATASTXOID;
+	}
 
-	tup = SearchSysCache2(STATEXTDATASTXOID, ObjectIdGetDatum(statsOid),
+	tup = SearchSysCache2(cacheId, ObjectIdGetDatum(statsOid),
 						  BoolGetDatum(inh));
 
 	/* We don't know if the data row for inh value exists. */
@@ -845,8 +856,8 @@ RemoveStatisticsById(Oid statsOid)
 	 */
 	rel = table_open(relid, ShareUpdateExclusiveLock);
 
-	RemoveStatisticsDataById(statsOid, true);
-	RemoveStatisticsDataById(statsOid, false);
+	RemoveStatisticsDataById(relid, statsOid, true);
+	RemoveStatisticsDataById(relid, statsOid, false);
 
 	CacheInvalidateRelcacheByRelid(relid);
 
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 4c9c5e9fc33..1c6bb49ef00 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1650,14 +1650,17 @@ get_relation_constraints(PlannerInfo *root,
  * The result is stored in stainfos list.
  */
 static void
-get_relation_statistics_worker(List **stainfos, RelOptInfo *rel,
+get_relation_statistics_worker(Oid relid, List **stainfos, RelOptInfo *rel,
 							   Oid statOid, bool inh,
 							   Bitmapset *keys, List *exprs)
 {
+	SysCacheIdentifier cacheId;
 	Form_pg_statistic_ext_data dataForm;
 	HeapTuple	dtup;
 
-	dtup = SearchSysCache2(STATEXTDATASTXOID,
+	cacheId = rel_is_global_temp(relid) ? TEMPSTATEXTDATASTXOID : STATEXTDATASTXOID;
+
+	dtup = SearchSysCache2(cacheId,
 						   ObjectIdGetDatum(statOid), BoolGetDatum(inh));
 	if (!HeapTupleIsValid(dtup))
 		return;
@@ -1822,9 +1825,11 @@ get_relation_statistics(PlannerInfo *root, RelOptInfo *rel,
 
 		/* extract statistics for possible values of stxdinherit flag */
 
-		get_relation_statistics_worker(&stainfos, rel, statOid, true, keys, exprs);
+		get_relation_statistics_worker(RelationGetRelid(relation), &stainfos,
+									   rel, statOid, true, keys, exprs);
 
-		get_relation_statistics_worker(&stainfos, rel, statOid, false, keys, exprs);
+		get_relation_statistics_worker(RelationGetRelid(relation), &stainfos,
+									   rel, statOid, false, keys, exprs);
 
 		ReleaseSysCache(htup);
 		bms_free(keys);
diff --git a/src/backend/statistics/dependencies.c b/src/backend/statistics/dependencies.c
index 81bcf76cc1c..b7f0a704544 100644
--- a/src/backend/statistics/dependencies.c
+++ b/src/backend/statistics/dependencies.c
@@ -684,20 +684,23 @@ dependency_is_fully_matched(MVDependency *dependency, Bitmapset *attnums)
  *		Load the functional dependencies for the indicated pg_statistic_ext tuple
  */
 MVDependencies *
-statext_dependencies_load(Oid mvoid, bool inh)
+statext_dependencies_load(Oid relid, Oid mvoid, bool inh)
 {
+	SysCacheIdentifier cacheId;
 	MVDependencies *result;
 	bool		isnull;
 	Datum		deps;
 	HeapTuple	htup;
 
-	htup = SearchSysCache2(STATEXTDATASTXOID,
+	cacheId = rel_is_global_temp(relid) ? TEMPSTATEXTDATASTXOID : STATEXTDATASTXOID;
+
+	htup = SearchSysCache2(cacheId,
 						   ObjectIdGetDatum(mvoid),
 						   BoolGetDatum(inh));
 	if (!HeapTupleIsValid(htup))
 		elog(ERROR, "cache lookup failed for statistics object %u", mvoid);
 
-	deps = SysCacheGetAttr(STATEXTDATASTXOID, htup,
+	deps = SysCacheGetAttr(cacheId, htup,
 						   Anum_pg_statistic_ext_data_stxddependencies, &isnull);
 	if (isnull)
 		elog(ERROR,
@@ -1604,7 +1607,7 @@ dependencies_clauselist_selectivity(PlannerInfo *root,
 		if (nmatched + nexprs < 2)
 			continue;
 
-		deps = statext_dependencies_load(stat->statOid, rte->inh);
+		deps = statext_dependencies_load(rte->relid, stat->statOid, rte->inh);
 
 		/*
 		 * The expressions may be represented by different attnums in the
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index f0d90f09b07..4e56fb7642e 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -23,6 +23,7 @@
 #include "catalog/indexing.h"
 #include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_statistic_ext_data.h"
+#include "catalog/pg_temp_statistic_ext_data.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
 #include "executor/executor.h"
@@ -77,7 +78,7 @@ typedef struct StatExtEntry
 static List *fetch_statentries_for_relation(Relation pg_statext, Relation rel);
 static VacAttrStats **lookup_var_attr_stats(Bitmapset *attrs, List *exprs,
 											int nvacatts, VacAttrStats **vacatts);
-static void statext_store(Oid statOid, bool inh,
+static void statext_store(Oid relid, Oid statOid, bool inh,
 						  MVNDistinct *ndistinct, MVDependencies *dependencies,
 						  MCVList *mcv, Datum exprs, VacAttrStats **stats);
 static int	statext_compute_stattarget(int stattarget,
@@ -227,7 +228,7 @@ BuildRelationExtStatistics(Relation onerel, bool inh, double totalrows,
 		}
 
 		/* store the statistics in the catalog */
-		statext_store(stat->statOid, inh,
+		statext_store(RelationGetRelid(onerel), stat->statOid, inh,
 					  ndistinct, dependencies, mcv, exprstats, stats);
 
 		/* for reporting progress */
@@ -805,7 +806,7 @@ lookup_var_attr_stats(Bitmapset *attrs, List *exprs,
  *	tuple.
  */
 static void
-statext_store(Oid statOid, bool inh,
+statext_store(Oid relid, Oid statOid, bool inh,
 			  MVNDistinct *ndistinct, MVDependencies *dependencies,
 			  MCVList *mcv, Datum exprs, VacAttrStats **stats)
 {
@@ -814,7 +815,12 @@ statext_store(Oid statOid, bool inh,
 	Datum		values[Natts_pg_statistic_ext_data];
 	bool		nulls[Natts_pg_statistic_ext_data];
 
-	pg_stextdata = table_open(StatisticExtDataRelationId, RowExclusiveLock);
+	if (rel_is_global_temp(relid))
+		pg_stextdata = table_open(TempStatisticExtDataRelationId,
+								  RowExclusiveLock);
+	else
+		pg_stextdata = table_open(StatisticExtDataRelationId,
+								  RowExclusiveLock);
 
 	memset(nulls, true, sizeof(nulls));
 	memset(values, 0, sizeof(values));
@@ -862,7 +868,7 @@ statext_store(Oid statOid, bool inh,
 	 * Delete the old tuple if it exists, and insert a new one. It's easier
 	 * than trying to update or insert, based on various conditions.
 	 */
-	RemoveStatisticsDataById(statOid, inh);
+	RemoveStatisticsDataById(relid, statOid, inh);
 
 	/* form and insert a new tuple */
 	stup = heap_form_tuple(RelationGetDescr(pg_stextdata), values, nulls);
@@ -1885,7 +1891,7 @@ statext_mcv_clauselist_selectivity(PlannerInfo *root, List *clauses, int varReli
 			MCVList    *mcv_list;
 
 			/* Load the MCV list stored in the statistics object */
-			mcv_list = statext_mcv_load(stat->statOid, rte->inh);
+			mcv_list = statext_mcv_load(rte->relid, stat->statOid, rte->inh);
 
 			/*
 			 * Compute the selectivity of the ORed list of clauses covered by
@@ -2444,8 +2450,9 @@ serialize_expr_stats(AnlExprData *exprdata, int nexprs)
  * data to use.
  */
 HeapTuple
-statext_expressions_load(Oid stxoid, bool inh, int idx)
+statext_expressions_load(Oid relid, Oid stxoid, bool inh, int idx)
 {
+	SysCacheIdentifier cacheId;
 	bool		isnull;
 	Datum		value;
 	HeapTuple	htup;
@@ -2454,12 +2461,14 @@ statext_expressions_load(Oid stxoid, bool inh, int idx)
 	HeapTupleData tmptup;
 	HeapTuple	tup;
 
-	htup = SearchSysCache2(STATEXTDATASTXOID,
+	cacheId = rel_is_global_temp(relid) ? TEMPSTATEXTDATASTXOID : STATEXTDATASTXOID;
+
+	htup = SearchSysCache2(cacheId,
 						   ObjectIdGetDatum(stxoid), BoolGetDatum(inh));
 	if (!HeapTupleIsValid(htup))
 		elog(ERROR, "cache lookup failed for statistics object %u", stxoid);
 
-	value = SysCacheGetAttr(STATEXTDATASTXOID, htup,
+	value = SysCacheGetAttr(cacheId, htup,
 							Anum_pg_statistic_ext_data_stxdexpr, &isnull);
 	if (isnull)
 		elog(ERROR,
diff --git a/src/backend/statistics/extended_stats_funcs.c b/src/backend/statistics/extended_stats_funcs.c
index 6bd41254221..0039950dc01 100644
--- a/src/backend/statistics/extended_stats_funcs.c
+++ b/src/backend/statistics/extended_stats_funcs.c
@@ -24,6 +24,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_statistic_ext_data.h"
+#include "catalog/pg_temp_statistic_ext_data.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
@@ -125,7 +126,7 @@ static bool extended_statistics_update(const NullableDatum *args);
 
 static HeapTuple get_pg_statistic_ext(Relation pg_stext, Oid nspoid,
 									  const char *stxname);
-static bool delete_pg_statistic_ext_data(Oid stxoid, bool inherited);
+static bool delete_pg_statistic_ext_data(Oid relid, Oid stxoid, bool inherited);
 
 /*
  * Track the extended statistics kinds expected for a pg_statistic_ext
@@ -140,7 +141,8 @@ typedef struct
 } StakindFlags;
 
 static void expand_stxkind(HeapTuple tup, StakindFlags *enabled);
-static void upsert_pg_statistic_ext_data(const Datum *values,
+static void upsert_pg_statistic_ext_data(Oid relid,
+										 const Datum *values,
 										 const bool *nulls,
 										 const bool *replaces);
 
@@ -265,16 +267,28 @@ expand_stxkind(HeapTuple tup, StakindFlags *enabled)
  * Perform the actual storage of a pg_statistic_ext_data tuple.
  */
 static void
-upsert_pg_statistic_ext_data(const Datum *values, const bool *nulls,
-							 const bool *replaces)
+upsert_pg_statistic_ext_data(Oid relid, const Datum *values,
+							 const bool *nulls, const bool *replaces)
 {
 	Relation	pg_stextdata;
+	SysCacheIdentifier cacheId;
 	HeapTuple	stxdtup;
 	HeapTuple	newtup;
 
-	pg_stextdata = table_open(StatisticExtDataRelationId, RowExclusiveLock);
+	if (rel_is_global_temp(relid))
+	{
+		pg_stextdata = table_open(TempStatisticExtDataRelationId,
+								  RowExclusiveLock);
+		cacheId = TEMPSTATEXTDATASTXOID;
+	}
+	else
+	{
+		pg_stextdata = table_open(StatisticExtDataRelationId,
+								  RowExclusiveLock);
+		cacheId = STATEXTDATASTXOID;
+	}
 
-	stxdtup = SearchSysCache2(STATEXTDATASTXOID,
+	stxdtup = SearchSysCache2(cacheId,
 							  values[Anum_pg_statistic_ext_data_stxoid - 1],
 							  values[Anum_pg_statistic_ext_data_stxdinherit - 1]);
 
@@ -749,7 +763,7 @@ extended_statistics_update(const NullableDatum *args)
 			success = false;
 	}
 
-	upsert_pg_statistic_ext_data(values, nulls, replaces);
+	upsert_pg_statistic_ext_data(relid, values, nulls, replaces);
 
 cleanup:
 	if (HeapTupleIsValid(tup))
@@ -1689,14 +1703,26 @@ exprs_error:
  * row and "inherited" pair.
  */
 static bool
-delete_pg_statistic_ext_data(Oid stxoid, bool inherited)
+delete_pg_statistic_ext_data(Oid relid, Oid stxoid, bool inherited)
 {
-	Relation	sed = table_open(StatisticExtDataRelationId, RowExclusiveLock);
+	Relation	sed;
+	SysCacheIdentifier cacheId;
 	HeapTuple	oldtup;
 	bool		result = false;
 
+	if (rel_is_global_temp(relid))
+	{
+		sed = table_open(TempStatisticExtDataRelationId, RowExclusiveLock);
+		cacheId = TEMPSTATEXTDATASTXOID;
+	}
+	else
+	{
+		sed = table_open(StatisticExtDataRelationId, RowExclusiveLock);
+		cacheId = STATEXTDATASTXOID;
+	}
+
 	/* Is there already a pg_statistic_ext_data tuple for this attribute? */
-	oldtup = SearchSysCache2(STATEXTDATASTXOID,
+	oldtup = SearchSysCache2(cacheId,
 							 ObjectIdGetDatum(stxoid),
 							 BoolGetDatum(inherited));
 
@@ -1828,7 +1854,7 @@ pg_clear_extended_stats(PG_FUNCTION_ARGS)
 		PG_RETURN_VOID();
 	}
 
-	delete_pg_statistic_ext_data(stxform->oid, inherited);
+	delete_pg_statistic_ext_data(relid, stxform->oid, inherited);
 	heap_freetuple(tup);
 
 	table_close(pg_stext, RowExclusiveLock);
diff --git a/src/backend/statistics/mcv.c b/src/backend/statistics/mcv.c
index 10a91c97aeb..6bcd3082e5c 100644
--- a/src/backend/statistics/mcv.c
+++ b/src/backend/statistics/mcv.c
@@ -553,18 +553,21 @@ build_column_frequencies(SortItem *groups, int ngroups,
  *		Load the MCV list for the indicated pg_statistic_ext_data tuple.
  */
 MCVList *
-statext_mcv_load(Oid mvoid, bool inh)
+statext_mcv_load(Oid relid, Oid mvoid, bool inh)
 {
+	SysCacheIdentifier cacheId;
 	MCVList    *result;
 	bool		isnull;
 	Datum		mcvlist;
-	HeapTuple	htup = SearchSysCache2(STATEXTDATASTXOID,
-									   ObjectIdGetDatum(mvoid), BoolGetDatum(inh));
+	HeapTuple	htup;
 
+	cacheId = rel_is_global_temp(relid) ? TEMPSTATEXTDATASTXOID : STATEXTDATASTXOID;
+
+	htup = SearchSysCache2(cacheId, ObjectIdGetDatum(mvoid), BoolGetDatum(inh));
 	if (!HeapTupleIsValid(htup))
 		elog(ERROR, "cache lookup failed for statistics object %u", mvoid);
 
-	mcvlist = SysCacheGetAttr(STATEXTDATASTXOID, htup,
+	mcvlist = SysCacheGetAttr(cacheId, htup,
 							  Anum_pg_statistic_ext_data_stxdmcv, &isnull);
 
 	if (isnull)
@@ -2054,7 +2057,7 @@ mcv_clauselist_selectivity(PlannerInfo *root, StatisticExtInfo *stat,
 	bool	   *matches = NULL;
 
 	/* load the MCV list stored in the statistics object */
-	mcv = statext_mcv_load(stat->statOid, rte->inh);
+	mcv = statext_mcv_load(rte->relid, stat->statOid, rte->inh);
 
 	/* build a match bitmap for the clauses */
 	matches = mcv_get_match_bitmap(root, clauses, stat->keys, stat->exprs,
diff --git a/src/backend/statistics/mvdistinct.c b/src/backend/statistics/mvdistinct.c
index 601f108a2b4..5e1e3c819ce 100644
--- a/src/backend/statistics/mvdistinct.c
+++ b/src/backend/statistics/mvdistinct.c
@@ -28,6 +28,7 @@
 #include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_statistic_ext_data.h"
 #include "statistics/extended_stats_internal.h"
+#include "utils/lsyscache.h"
 #include "utils/syscache.h"
 #include "utils/typcache.h"
 #include "varatt.h"
@@ -142,19 +143,22 @@ statext_ndistinct_build(double totalrows, StatsBuildData *data)
  *		Load the ndistinct value for the indicated pg_statistic_ext tuple
  */
 MVNDistinct *
-statext_ndistinct_load(Oid mvoid, bool inh)
+statext_ndistinct_load(Oid relid, Oid mvoid, bool inh)
 {
+	SysCacheIdentifier cacheId;
 	MVNDistinct *result;
 	bool		isnull;
 	Datum		ndist;
 	HeapTuple	htup;
 
-	htup = SearchSysCache2(STATEXTDATASTXOID,
+	cacheId = rel_is_global_temp(relid) ? TEMPSTATEXTDATASTXOID : STATEXTDATASTXOID;
+
+	htup = SearchSysCache2(cacheId,
 						   ObjectIdGetDatum(mvoid), BoolGetDatum(inh));
 	if (!HeapTupleIsValid(htup))
 		elog(ERROR, "cache lookup failed for statistics object %u", mvoid);
 
-	ndist = SysCacheGetAttr(STATEXTDATASTXOID, htup,
+	ndist = SysCacheGetAttr(cacheId, htup,
 							Anum_pg_statistic_ext_data_stxdndistinct, &isnull);
 	if (isnull)
 		elog(ERROR,
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 1db37761d9b..aba2a400d53 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4680,7 +4680,7 @@ estimate_multivariate_ndistinct(PlannerInfo *root, RelOptInfo *rel,
 
 	Assert(nmatches_vars + nmatches_exprs > 1);
 
-	stats = statext_ndistinct_load(statOid, rte->inh);
+	stats = statext_ndistinct_load(rte->relid, statOid, rte->inh);
 
 	/*
 	 * If we have a match, search it for the specific item that matches (there
@@ -5938,7 +5938,8 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 					 * Now we just create a new copy every time.
 					 */
 					vardata->statsTuple =
-						statext_expressions_load(info->statOid, rte->inh, pos);
+						statext_expressions_load(rte->relid, info->statOid,
+												 rte->inh, pos);
 
 					/* Nothing to release if no data found */
 					if (vardata->statsTuple != NULL)
diff --git a/src/include/catalog/Makefile b/src/include/catalog/Makefile
index 6aec03add22..f60a6454df2 100644
--- a/src/include/catalog/Makefile
+++ b/src/include/catalog/Makefile
@@ -88,7 +88,8 @@ CATALOG_HEADERS := \
 	pg_propgraph_label_property.h \
 	pg_propgraph_property.h \
 	pg_temp_class.h \
-	pg_temp_statistic.h
+	pg_temp_statistic.h \
+	pg_temp_statistic_ext_data.h
 
 GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h)
 
diff --git a/src/include/catalog/meson.build b/src/include/catalog/meson.build
index db809c4b3c0..7599731de7d 100644
--- a/src/include/catalog/meson.build
+++ b/src/include/catalog/meson.build
@@ -76,6 +76,7 @@ catalog_headers = [
   'pg_propgraph_property.h',
   'pg_temp_class.h',
   'pg_temp_statistic.h',
+  'pg_temp_statistic_ext_data.h',
 ]
 
 # The .dat files we need can just be listed alphabetically.
diff --git a/src/include/catalog/pg_statistic_ext_data.h b/src/include/catalog/pg_statistic_ext_data.h
index dbc4acc7d1a..e3a6fda0030 100644
--- a/src/include/catalog/pg_statistic_ext_data.h
+++ b/src/include/catalog/pg_statistic_ext_data.h
@@ -30,6 +30,9 @@
  */
 BEGIN_CATALOG_STRUCT
 
+/*
+ * NB: Any changes made here must be reflected in pg_temp_statistic_ext_data.
+ */
 CATALOG(pg_statistic_ext_data,3429,StatisticExtDataRelationId)
 {
 	Oid			stxoid BKI_LOOKUP(pg_statistic_ext);	/* statistics object
diff --git a/src/include/catalog/pg_temp_statistic_ext_data.h b/src/include/catalog/pg_temp_statistic_ext_data.h
new file mode 100644
index 00000000000..eb3b4b2ed04
--- /dev/null
+++ b/src/include/catalog/pg_temp_statistic_ext_data.h
@@ -0,0 +1,62 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_temp_statistic_ext_data.h
+ *	  definition of the "temporary extended statistics data" system catalog
+ *	  (pg_temp_statistic_ext_data)
+ *
+ * This is a global temporary system catalog table storing the statistical
+ * data for extended statistics objects on temporary tables.  Currently, it
+ * is only used for global temporary tables.
+ *
+ * Portions Copyright (c) 2026, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/pg_temp_statistic_ext_data.h
+ *
+ * NOTES
+ *	  The Catalog.pm module reads this file and derives schema
+ *	  information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_TEMP_STATISTIC_EXT_DATA_H
+#define PG_TEMP_STATISTIC_EXT_DATA_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_temp_statistic_ext_data_d.h"	/* IWYU pragma: export */
+
+/* ----------------
+ *		pg_temp_statistic_ext_data definition.  cpp turns this into
+ *		typedef struct FormData_pg_temp_statistic_ext_data
+ * ----------------
+ */
+BEGIN_CATALOG_STRUCT
+
+/*
+ * NB: The fields here must exactly match those in pg_statistic_ext_data.
+ */
+CATALOG(pg_temp_statistic_ext_data,8088,TempStatisticExtDataRelationId) BKI_TEMP_RELATION
+{
+	Oid			stxoid BKI_LOOKUP(pg_statistic_ext);	/* statistics object
+														 * this data is for */
+	bool		stxdinherit;	/* true if inheritance children are included */
+
+#ifdef CATALOG_VARLEN			/* variable-length fields start here */
+
+	pg_ndistinct stxdndistinct; /* ndistinct coefficients (serialized) */
+	pg_dependencies stxddependencies;	/* dependencies (serialized) */
+	pg_mcv_list stxdmcv;		/* MCV (serialized) */
+	pg_statistic stxdexpr[1];	/* stats for expressions */
+
+#endif
+
+} FormData_pg_temp_statistic_ext_data;
+
+END_CATALOG_STRUCT
+
+DECLARE_TOAST(pg_temp_statistic_ext_data, 8089, 8090);
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_temp_statistic_ext_data_stxoid_inh_index, 8091, TempStatisticExtDataStxoidInhIndexId, pg_temp_statistic_ext_data, btree(stxoid oid_ops, stxdinherit bool_ops));
+
+MAKE_SYSCACHE(TEMPSTATEXTDATASTXOID, pg_temp_statistic_ext_data_stxoid_inh_index, 4);
+
+#endif							/* PG_TEMP_STATISTIC_EXT_DATA_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 574f860bdd2..c6c403ddc20 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -89,7 +89,7 @@ extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 extern ObjectAddress CreateStatistics(List *relids, CreateStatsStmt *stmt, bool check_rights);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
-extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
+extern void RemoveStatisticsDataById(Oid relid, Oid statsOid, bool inh);
 extern Oid	StatisticsGetRelation(Oid statId, bool missing_ok);
 
 /* commands/aggregatecmds.c */
diff --git a/src/include/statistics/statistics.h b/src/include/statistics/statistics.h
index fa0430b9477..bcec24b8717 100644
--- a/src/include/statistics/statistics.h
+++ b/src/include/statistics/statistics.h
@@ -94,9 +94,9 @@ typedef struct MCVList
 	MCVItem		items[FLEXIBLE_ARRAY_MEMBER];	/* array of MCV items */
 } MCVList;
 
-extern MVNDistinct *statext_ndistinct_load(Oid mvoid, bool inh);
-extern MVDependencies *statext_dependencies_load(Oid mvoid, bool inh);
-extern MCVList *statext_mcv_load(Oid mvoid, bool inh);
+extern MVNDistinct *statext_ndistinct_load(Oid relid, Oid mvoid, bool inh);
+extern MVDependencies *statext_dependencies_load(Oid relid, Oid mvoid, bool inh);
+extern MCVList *statext_mcv_load(Oid relid, Oid mvoid, bool inh);
 
 extern void BuildRelationExtStatistics(Relation onerel, bool inh, double totalrows,
 									   int numrows, HeapTuple *rows,
@@ -126,7 +126,7 @@ extern StatisticExtInfo *choose_best_statistics(List *stats, char requiredkind,
 												Bitmapset **clause_attnums,
 												List **clause_exprs,
 												int nclauses);
-extern HeapTuple statext_expressions_load(Oid stxoid, bool inh, int idx);
+extern HeapTuple statext_expressions_load(Oid relid, Oid stxoid, bool inh, int idx);
 
 /*
  * Statistics values applied to pg_class during stats import or restore
diff --git a/src/test/isolation/expected/global-temp.out b/src/test/isolation/expected/global-temp.out
index 2eac0c1d43f..b021c2f26e7 100644
--- a/src/test/isolation/expected/global-temp.out
+++ b/src/test/isolation/expected/global-temp.out
@@ -569,132 +569,147 @@ key|val|seq
 
 step reset_tblspace: ALTER TABLE tmp SET TABLESPACE pg_default;
 
-starting permutation: create1 ins1_2 analyze1 cat1 drop1 cat1
+starting permutation: create1 ext_stats1 ins1_2 analyze1 cat1 drop1 cat1
 step create1: CREATE GLOBAL TEMP TABLE tmp2 (key int, val text, icol int, bcol box);
+step ext_stats1: CREATE STATISTICS tmp2_stats ON key, val FROM tmp2;
 step ins1_2: INSERT INTO tmp2 VALUES (1, 's1');
 step analyze1: ANALYZE tmp2;
 step cat1: 
   SELECT (SELECT count(*) FROM pg_temp_class WHERE oid >= 12000),
-         (SELECT count(*) FROM pg_temp_statistic);
+         (SELECT count(*) FROM pg_temp_statistic),
+         (SELECT count(*) FROM pg_temp_statistic_ext_data);
 
-count|count
------+-----
-    3|    4
+count|count|count
+-----+-----+-----
+    3|    4|    1
 (1 row)
 
 step drop1: DROP TABLE tmp2;
 step cat1: 
   SELECT (SELECT count(*) FROM pg_temp_class WHERE oid >= 12000),
-         (SELECT count(*) FROM pg_temp_statistic);
+         (SELECT count(*) FROM pg_temp_statistic),
+         (SELECT count(*) FROM pg_temp_statistic_ext_data);
 
-count|count
------+-----
-    0|    0
+count|count|count
+-----+-----+-----
+    0|    0|    0
 (1 row)
 
 
-starting permutation: create1 ins1_2 analyze1 cat1 drop2 cat1
+starting permutation: create1 ext_stats1 ins1_2 analyze1 cat1 drop2 cat1
 step create1: CREATE GLOBAL TEMP TABLE tmp2 (key int, val text, icol int, bcol box);
+step ext_stats1: CREATE STATISTICS tmp2_stats ON key, val FROM tmp2;
 step ins1_2: INSERT INTO tmp2 VALUES (1, 's1');
 step analyze1: ANALYZE tmp2;
 step cat1: 
   SELECT (SELECT count(*) FROM pg_temp_class WHERE oid >= 12000),
-         (SELECT count(*) FROM pg_temp_statistic);
+         (SELECT count(*) FROM pg_temp_statistic),
+         (SELECT count(*) FROM pg_temp_statistic_ext_data);
 
-count|count
------+-----
-    3|    4
+count|count|count
+-----+-----+-----
+    3|    4|    1
 (1 row)
 
 step drop2: DROP TABLE tmp2;
 step cat1: 
   SELECT (SELECT count(*) FROM pg_temp_class WHERE oid >= 12000),
-         (SELECT count(*) FROM pg_temp_statistic);
+         (SELECT count(*) FROM pg_temp_statistic),
+         (SELECT count(*) FROM pg_temp_statistic_ext_data);
 
-count|count
------+-----
-    0|    0
+count|count|count
+-----+-----+-----
+    0|    0|    0
 (1 row)
 
 
-starting permutation: create1 ins1_2 analyze1 cat1 b1 drop2 cat1 r1 cat1
+starting permutation: create1 ext_stats1 ins1_2 analyze1 cat1 b1 drop2 cat1 r1 cat1
 step create1: CREATE GLOBAL TEMP TABLE tmp2 (key int, val text, icol int, bcol box);
+step ext_stats1: CREATE STATISTICS tmp2_stats ON key, val FROM tmp2;
 step ins1_2: INSERT INTO tmp2 VALUES (1, 's1');
 step analyze1: ANALYZE tmp2;
 step cat1: 
   SELECT (SELECT count(*) FROM pg_temp_class WHERE oid >= 12000),
-         (SELECT count(*) FROM pg_temp_statistic);
+         (SELECT count(*) FROM pg_temp_statistic),
+         (SELECT count(*) FROM pg_temp_statistic_ext_data);
 
-count|count
------+-----
-    3|    4
+count|count|count
+-----+-----+-----
+    3|    4|    1
 (1 row)
 
 step b1: BEGIN;
 step drop2: DROP TABLE tmp2;
 step cat1: 
   SELECT (SELECT count(*) FROM pg_temp_class WHERE oid >= 12000),
-         (SELECT count(*) FROM pg_temp_statistic);
+         (SELECT count(*) FROM pg_temp_statistic),
+         (SELECT count(*) FROM pg_temp_statistic_ext_data);
 
-count|count
------+-----
-    0|    0
+count|count|count
+-----+-----+-----
+    0|    0|    0
 (1 row)
 
 step r1: ROLLBACK;
 step cat1: 
   SELECT (SELECT count(*) FROM pg_temp_class WHERE oid >= 12000),
-         (SELECT count(*) FROM pg_temp_statistic);
+         (SELECT count(*) FROM pg_temp_statistic),
+         (SELECT count(*) FROM pg_temp_statistic_ext_data);
 
-count|count
------+-----
-    0|    0
+count|count|count
+-----+-----+-----
+    0|    0|    0
 (1 row)
 
 
-starting permutation: create1 ins1_2 analyze1 b1 cat1 sp1 drop2 cat1 rsp1 cat1 r1 cat1
+starting permutation: create1 ext_stats1 ins1_2 analyze1 b1 cat1 sp1 drop2 cat1 rsp1 cat1 r1 cat1
 step create1: CREATE GLOBAL TEMP TABLE tmp2 (key int, val text, icol int, bcol box);
+step ext_stats1: CREATE STATISTICS tmp2_stats ON key, val FROM tmp2;
 step ins1_2: INSERT INTO tmp2 VALUES (1, 's1');
 step analyze1: ANALYZE tmp2;
 step b1: BEGIN;
 step cat1: 
   SELECT (SELECT count(*) FROM pg_temp_class WHERE oid >= 12000),
-         (SELECT count(*) FROM pg_temp_statistic);
+         (SELECT count(*) FROM pg_temp_statistic),
+         (SELECT count(*) FROM pg_temp_statistic_ext_data);
 
-count|count
------+-----
-    3|    4
+count|count|count
+-----+-----+-----
+    3|    4|    1
 (1 row)
 
 step sp1: SAVEPOINT sp;
 step drop2: DROP TABLE tmp2;
 step cat1: 
   SELECT (SELECT count(*) FROM pg_temp_class WHERE oid >= 12000),
-         (SELECT count(*) FROM pg_temp_statistic);
+         (SELECT count(*) FROM pg_temp_statistic),
+         (SELECT count(*) FROM pg_temp_statistic_ext_data);
 
-count|count
------+-----
-    0|    0
+count|count|count
+-----+-----+-----
+    0|    0|    0
 (1 row)
 
 step rsp1: ROLLBACK TO SAVEPOINT sp;
 step cat1: 
   SELECT (SELECT count(*) FROM pg_temp_class WHERE oid >= 12000),
-         (SELECT count(*) FROM pg_temp_statistic);
+         (SELECT count(*) FROM pg_temp_statistic),
+         (SELECT count(*) FROM pg_temp_statistic_ext_data);
 
-count|count
------+-----
-    0|    0
+count|count|count
+-----+-----+-----
+    0|    0|    0
 (1 row)
 
 step r1: ROLLBACK;
 step cat1: 
   SELECT (SELECT count(*) FROM pg_temp_class WHERE oid >= 12000),
-         (SELECT count(*) FROM pg_temp_statistic);
+         (SELECT count(*) FROM pg_temp_statistic),
+         (SELECT count(*) FROM pg_temp_statistic_ext_data);
 
-count|count
------+-----
-    0|    0
+count|count|count
+-----+-----+-----
+    0|    0|    0
 (1 row)
 
 
diff --git a/src/test/isolation/specs/global-temp.spec b/src/test/isolation/specs/global-temp.spec
index 9eccb7e3d41..8123feba835 100644
--- a/src/test/isolation/specs/global-temp.spec
+++ b/src/test/isolation/specs/global-temp.spec
@@ -37,10 +37,12 @@ step alter1g { ALTER TABLE tmp2 ADD CONSTRAINT tmp2_fk FOREIGN KEY (icol) REFERE
 step alter1h { ALTER TABLE tmp2 ADD CONSTRAINT tmp2_ex EXCLUDE USING gist (bcol WITH &&); }
 step uniq_idx1 { CREATE UNIQUE INDEX tmp2_un ON tmp2(val); }
 step seltype1 { SELECT key, pg_typeof(key), val FROM tmp2; }
+step ext_stats1 { CREATE STATISTICS tmp2_stats ON key, val FROM tmp2; }
 step analyze1 { ANALYZE tmp2; }
 step cat1 {
   SELECT (SELECT count(*) FROM pg_temp_class WHERE oid >= 12000),
-         (SELECT count(*) FROM pg_temp_statistic);
+         (SELECT count(*) FROM pg_temp_statistic),
+         (SELECT count(*) FROM pg_temp_statistic_ext_data);
 }
 step r1 { ROLLBACK; }
 step sp1 { SAVEPOINT sp; }
@@ -149,10 +151,10 @@ permutation ins1 ins2 t2 sel1 sel2 ins2 t1 sel1 sel2 ins1 t2 sel1 sel2
 permutation ins1 ins2 alt_tblspace get_tblspace1 get_tblspace2 sel1 sel2 reset_tblspace
 
 # Test global temp catalog tidy-up after DROP
-permutation create1 ins1_2 analyze1 cat1 drop1 cat1
-permutation create1 ins1_2 analyze1 cat1 drop2 cat1
-permutation create1 ins1_2 analyze1 cat1 b1 drop2 cat1 r1 cat1
-permutation create1 ins1_2 analyze1 b1 cat1 sp1 drop2 cat1 rsp1 cat1 r1 cat1
+permutation create1 ext_stats1 ins1_2 analyze1 cat1 drop1 cat1
+permutation create1 ext_stats1 ins1_2 analyze1 cat1 drop2 cat1
+permutation create1 ext_stats1 ins1_2 analyze1 cat1 b1 drop2 cat1 r1 cat1
+permutation create1 ext_stats1 ins1_2 analyze1 b1 cat1 sp1 drop2 cat1 rsp1 cat1 r1 cat1
 
 # Tidy up
 permutation drop_tblspace list_tblspaces
diff --git a/src/test/regress/expected/global_temp.out b/src/test/regress/expected/global_temp.out
index f0dbffb6801..849d20e56c1 100644
--- a/src/test/regress/expected/global_temp.out
+++ b/src/test/regress/expected/global_temp.out
@@ -792,6 +792,77 @@ SELECT row_estimate('SELECT * FROM tmp2 WHERE b = 8');
            17
 (1 row)
 
+DROP TABLE tmp2;
+-- Test extended stats
+CREATE GLOBAL TEMP TABLE tmp2 (a int, b int, c int);
+INSERT INTO tmp2
+  SELECT x, floor(sqrt(x)), floor(sqrt(x)) + 100 FROM generate_series(36, 99) x;
+CREATE STATISTICS tmp2_stats ON b, c FROM tmp2;
+ANALYZE tmp2;
+SELECT stxname,
+       replace(d.stxdndistinct, '}, ', E'},\n') AS stxdndistinct,
+       replace(d.stxddependencies, '}, ', E'},\n') AS stxddependencies
+  FROM pg_statistic_ext s
+  LEFT JOIN pg_statistic_ext_data d ON d.stxoid = s.oid
+ WHERE s.stxname = 'tmp2_stats';
+  stxname   | stxdndistinct | stxddependencies 
+------------+---------------+------------------
+ tmp2_stats |               | 
+(1 row)
+
+SELECT stxname,
+       replace(d.stxdndistinct, '}, ', E'},\n') AS stxdndistinct,
+       replace(d.stxddependencies, '}, ', E'},\n') AS stxddependencies
+  FROM pg_statistic_ext s
+  LEFT JOIN pg_temp_statistic_ext_data d ON d.stxoid = s.oid
+ WHERE s.stxname = 'tmp2_stats';
+  stxname   |              stxdndistinct               |                      stxddependencies                      
+------------+------------------------------------------+------------------------------------------------------------
+ tmp2_stats | [{"attributes": [2, 3], "ndistinct": 4}] | [{"attributes": [2], "dependency": 3, "degree": 1.000000},+
+            |                                          | {"attributes": [3], "dependency": 2, "degree": 1.000000}]
+(1 row)
+
+SELECT m.*
+  FROM pg_statistic_ext s, pg_statistic_ext_data d,
+       pg_mcv_list_items(d.stxdmcv) m
+ WHERE s.stxname = 'tmp2_stats'
+   AND d.stxoid = s.oid;
+ index | values | nulls | frequency | base_frequency 
+-------+--------+-------+-----------+----------------
+(0 rows)
+
+SELECT m.*
+  FROM pg_statistic_ext s, pg_temp_statistic_ext_data d,
+       pg_mcv_list_items(d.stxdmcv) m
+ WHERE s.stxname = 'tmp2_stats'
+   AND d.stxoid = s.oid;
+ index | values  | nulls | frequency | base_frequency 
+-------+---------+-------+-----------+----------------
+     0 | {9,109} | {f,f} |  0.296875 | 0.088134765625
+     1 | {8,108} | {f,f} |  0.265625 | 0.070556640625
+     2 | {7,107} | {f,f} |  0.234375 | 0.054931640625
+     3 | {6,106} | {f,f} |  0.203125 | 0.041259765625
+(4 rows)
+
+SELECT most_common_vals FROM pg_stats_ext
+ WHERE schemaname = 'global_temp_tests' AND tablename = 'tmp2';
+         most_common_vals          
+-----------------------------------
+ {{9,109},{8,108},{7,107},{6,106}}
+(1 row)
+
+SELECT COUNT(*) FROM tmp2 WHERE b = 8 AND c = 108;
+ count 
+-------
+    17
+(1 row)
+
+SELECT row_estimate('SELECT * FROM tmp2 WHERE b = 8 AND c = 108');
+ row_estimate 
+--------------
+           17
+(1 row)
+
 DROP TABLE tmp2;
 -- Test view creation
 CREATE VIEW v AS SELECT * FROM tmp1;
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index be2efa78257..bccae052a15 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -298,3 +298,4 @@ 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}
+NOTICE:  checking pg_temp_statistic_ext_data {stxoid} => pg_statistic_ext {oid}
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index b5ecc98cc2c..72cc4906c2e 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1367,6 +1367,21 @@ UNION ALL
     pg_temp_statistic.stavalues4,
     pg_temp_statistic.stavalues5
    FROM pg_temp_statistic;
+pg_all_statistic_ext_data| SELECT pg_statistic_ext_data.stxoid,
+    pg_statistic_ext_data.stxdinherit,
+    pg_statistic_ext_data.stxdndistinct,
+    pg_statistic_ext_data.stxddependencies,
+    pg_statistic_ext_data.stxdmcv,
+    pg_statistic_ext_data.stxdexpr
+   FROM pg_statistic_ext_data
+UNION ALL
+ SELECT pg_temp_statistic_ext_data.stxoid,
+    pg_temp_statistic_ext_data.stxdinherit,
+    pg_temp_statistic_ext_data.stxdndistinct,
+    pg_temp_statistic_ext_data.stxddependencies,
+    pg_temp_statistic_ext_data.stxdmcv,
+    pg_temp_statistic_ext_data.stxdexpr
+   FROM pg_temp_statistic_ext_data;
 pg_available_extension_versions| SELECT e.name,
     e.version,
     (x.extname IS NOT NULL) AS installed,
@@ -2796,7 +2811,7 @@ pg_stats_ext| SELECT cn.nspname AS schemaname,
     m.most_common_base_freqs
    FROM (((((pg_statistic_ext s
      JOIN pg_class c ON ((c.oid = s.stxrelid)))
-     JOIN pg_statistic_ext_data sd ON ((s.oid = sd.stxoid)))
+     JOIN pg_all_statistic_ext_data sd ON ((s.oid = sd.stxoid)))
      LEFT JOIN pg_namespace cn ON ((cn.oid = c.relnamespace)))
      LEFT JOIN pg_namespace sn ON ((sn.oid = s.stxnamespace)))
      LEFT JOIN LATERAL ( SELECT array_agg(pg_mcv_list_items."values") AS most_common_vals,
@@ -2899,7 +2914,7 @@ pg_stats_ext_exprs| SELECT cn.nspname AS schemaname,
         END AS range_bounds_histogram
    FROM (((((pg_statistic_ext s
      JOIN pg_class c ON ((c.oid = s.stxrelid)))
-     LEFT JOIN pg_statistic_ext_data sd ON ((s.oid = sd.stxoid)))
+     LEFT JOIN pg_all_statistic_ext_data sd ON ((s.oid = sd.stxoid)))
      LEFT JOIN pg_namespace cn ON ((cn.oid = c.relnamespace)))
      LEFT JOIN pg_namespace sn ON ((sn.oid = s.stxnamespace)))
      JOIN LATERAL ( SELECT unnest(pg_get_statisticsobjdef_expressions(s.oid)) AS expr,
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 5f5756d5596..3f14d8f9e8a 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -48,3 +48,27 @@ UNION ALL
 ---------+----------+--------+--------+-----------+----------+----------+----------+------------+----------------+------------+-----------+---------------+-------------+--------------+--------------+------------+-------------+--------------
 (0 rows)
 
+-- check that pg_statistic_ext_data and pg_temp_statistic_ext_data 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_ext_data'::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_ext_data'::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 9d414045629..93226f06f6e 100644
--- a/src/test/regress/sql/global_temp.sql
+++ b/src/test/regress/sql/global_temp.sql
@@ -441,6 +441,47 @@ SELECT row_estimate('SELECT * FROM tmp2 WHERE b = 8');
 
 DROP TABLE tmp2;
 
+-- Test extended stats
+CREATE GLOBAL TEMP TABLE tmp2 (a int, b int, c int);
+INSERT INTO tmp2
+  SELECT x, floor(sqrt(x)), floor(sqrt(x)) + 100 FROM generate_series(36, 99) x;
+CREATE STATISTICS tmp2_stats ON b, c FROM tmp2;
+ANALYZE tmp2;
+
+SELECT stxname,
+       replace(d.stxdndistinct, '}, ', E'},\n') AS stxdndistinct,
+       replace(d.stxddependencies, '}, ', E'},\n') AS stxddependencies
+  FROM pg_statistic_ext s
+  LEFT JOIN pg_statistic_ext_data d ON d.stxoid = s.oid
+ WHERE s.stxname = 'tmp2_stats';
+
+SELECT stxname,
+       replace(d.stxdndistinct, '}, ', E'},\n') AS stxdndistinct,
+       replace(d.stxddependencies, '}, ', E'},\n') AS stxddependencies
+  FROM pg_statistic_ext s
+  LEFT JOIN pg_temp_statistic_ext_data d ON d.stxoid = s.oid
+ WHERE s.stxname = 'tmp2_stats';
+
+SELECT m.*
+  FROM pg_statistic_ext s, pg_statistic_ext_data d,
+       pg_mcv_list_items(d.stxdmcv) m
+ WHERE s.stxname = 'tmp2_stats'
+   AND d.stxoid = s.oid;
+
+SELECT m.*
+  FROM pg_statistic_ext s, pg_temp_statistic_ext_data d,
+       pg_mcv_list_items(d.stxdmcv) m
+ WHERE s.stxname = 'tmp2_stats'
+   AND d.stxoid = s.oid;
+
+SELECT most_common_vals FROM pg_stats_ext
+ WHERE schemaname = 'global_temp_tests' AND tablename = 'tmp2';
+
+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 view creation
 CREATE VIEW v AS SELECT * FROM tmp1;
 SELECT * FROM v;
diff --git a/src/test/regress/sql/sanity_check.sql b/src/test/regress/sql/sanity_check.sql
index ed0096d7823..4bf00feba2c 100644
--- a/src/test/regress/sql/sanity_check.sql
+++ b/src/test/regress/sql/sanity_check.sql
@@ -39,3 +39,24 @@ WITH t1 AS (
 (SELECT * FROM t1 EXCEPT SELECT * FROM t2)
 UNION ALL
 (SELECT * FROM t2 EXCEPT SELECT * FROM t1);
+
+-- check that pg_statistic_ext_data and pg_temp_statistic_ext_data 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_ext_data'::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_ext_data'::regclass
+)
+(SELECT * FROM t1 EXCEPT SELECT * FROM t2)
+UNION ALL
+(SELECT * FROM t2 EXCEPT SELECT * FROM t1);
-- 
2.43.0

