v5-0009-Add-pg_temp_statistic_ext_data-global-temporary-c.patch
text/x-patch
Filename: v5-0009-Add-pg_temp_statistic_ext_data-global-temporary-c.patch
Type: text/x-patch
Part: 8
Message:
Re: Global temporary tables
Patch
Format: format-patch
Series: patch v5-0009
Subject: Add pg_temp_statistic_ext_data global temporary catalog table.
| File | + | − |
|---|---|---|
| src/backend/catalog/system_views.sql | 11 | 3 |
| src/backend/commands/statscmds.c | 16 | 5 |
| src/backend/optimizer/util/plancat.c | 9 | 4 |
| src/backend/statistics/dependencies.c | 7 | 4 |
| src/backend/statistics/extended_stats.c | 18 | 9 |
| src/backend/statistics/extended_stats_funcs.c | 37 | 11 |
| src/backend/statistics/mcv.c | 8 | 5 |
| src/backend/statistics/mvdistinct.c | 7 | 3 |
| src/backend/utils/adt/selfuncs.c | 3 | 2 |
| src/backend/utils/cache/relcache.c | 1 | 0 |
| src/include/catalog/Makefile | 2 | 1 |
| src/include/catalog/meson.build | 1 | 0 |
| src/include/catalog/pg_statistic_ext_data.h | 3 | 0 |
| src/include/catalog/pg_temp_statistic_ext_data.h | 62 | 0 |
| src/include/commands/defrem.h | 1 | 1 |
| src/include/statistics/statistics.h | 4 | 4 |
| src/test/regress/expected/global_temp.out | 71 | 0 |
| src/test/regress/expected/oidjoins.out | 1 | 0 |
| src/test/regress/expected/rules.out | 17 | 2 |
| src/test/regress/expected/sanity_check.out | 24 | 0 |
| src/test/regress/sql/global_temp.sql | 41 | 0 |
| src/test/regress/sql/sanity_check.sql | 21 | 0 |
From c91e8a1c72f18319da8fe746bed52392e1f951a8 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 v5 09/10] Add pg_temp_statistic_ext_data global temporary
catalog table.
This has the 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 global.
---
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/backend/utils/cache/relcache.c | 1 +
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/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 ++++++
22 files changed, 365 insertions(+), 54 deletions(-)
create mode 100644 src/include/catalog/pg_temp_statistic_ext_data.h
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 8e6ff7c37a5..63b89faebee 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 b354723be44..95ed3bd0b21 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"
@@ -775,14 +776,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. */
@@ -830,8 +841,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 7c4be174869..1c71b1ff969 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1652,14 +1652,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;
@@ -1824,9 +1827,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 95dcc218978..0d75b8d7840 100644
--- a/src/backend/statistics/dependencies.c
+++ b/src/backend/statistics/dependencies.c
@@ -685,20 +685,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,
@@ -1608,7 +1611,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 2b83355d26e..0c52bc083f6 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);
@@ -1891,7 +1897,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
@@ -2450,8 +2456,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;
@@ -2460,12 +2467,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 2cb3942056f..545b3f191d3 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(FunctionCallInfo fcinfo);
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(FunctionCallInfo fcinfo)
success = false;
}
- upsert_pg_statistic_ext_data(values, nulls, replaces);
+ upsert_pg_statistic_ext_data(relid, values, nulls, replaces);
cleanup:
if (HeapTupleIsValid(tup))
@@ -1700,14 +1714,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));
@@ -1842,7 +1868,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 0b7da605a4c..f77b06ca9dc 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)
@@ -2058,7 +2061,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 4f8f578a22f..d321ef10ba7 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 11faeffe118..e05ee05a820 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4677,7 +4677,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
@@ -5929,7 +5929,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/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 856878ad10b..ae193dc0c39 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -64,6 +64,7 @@
#include "catalog/pg_tablespace.h"
#include "catalog/pg_temp_class.h"
#include "catalog/pg_temp_statistic.h"
+#include "catalog/pg_temp_statistic_ext_data.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 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 d080ad59b71..9edc0a76a75 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(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 8f9b9d237fd..2ab93adaf82 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,6 +126,6 @@ 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);
#endif /* STATISTICS_H */
diff --git a/src/test/regress/expected/global_temp.out b/src/test/regress/expected/global_temp.out
index 3cc80aa283c..1ed2ae930e4 100644
--- a/src/test/regress/expected/global_temp.out
+++ b/src/test/regress/expected/global_temp.out
@@ -605,6 +605,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
INSERT INTO tmp1 VALUES (1, 'xxx');
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 f872091b82c..6288d48c33b 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,
@@ -2783,7 +2798,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,
@@ -2886,7 +2901,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 c08ad074e71..928989039b9 100644
--- a/src/test/regress/sql/global_temp.sql
+++ b/src/test/regress/sql/global_temp.sql
@@ -356,6 +356,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
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 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.51.0