v7-0006-Add-relation-statistics-columns-to-pg_temp_class.patch
text/x-patch
Filename: v7-0006-Add-relation-statistics-columns-to-pg_temp_class.patch
Type: text/x-patch
Part: 5
Message:
Re: Global temporary tables
Patch
Format: format-patch
Series: patch v7-0006
Subject: Add relation statistics columns to pg_temp_class.
| File | + | − |
|---|---|---|
| src/backend/access/heap/heapam.c | 4 | 0 |
| src/backend/catalog/catalog.c | 2 | 0 |
| src/backend/catalog/heap.c | 30 | 11 |
| src/backend/catalog/index.c | 95 | 42 |
| src/backend/catalog/pg_temp_class.c | 84 | 0 |
| src/backend/commands/repack.c | 40 | 21 |
| src/backend/commands/vacuum.c | 59 | 30 |
| src/backend/statistics/relation_stats.c | 48 | 34 |
| src/backend/utils/cache/lsyscache.c | 11 | 0 |
| src/backend/utils/cache/relcache.c | 5 | 4 |
| src/include/catalog/pg_temp_class.h | 166 | 0 |
| src/include/utils/lsyscache.h | 1 | 0 |
| src/test/isolation/expected/global-temp.out | 32 | 0 |
| src/test/isolation/specs/global-temp.spec | 7 | 0 |
| src/test/regress/expected/global_temp.out | 136 | 0 |
| src/test/regress/sql/global_temp.sql | 82 | 0 |
From cc500b11078c5f22466aaf6af9ac029f3f964503 Mon Sep 17 00:00:00 2001
From: Dean Rasheed <dean.a.rasheed@gmail.com>
Date: Wed, 17 Jun 2026 02:04:59 +0100
Subject: [PATCH v7 06/10] Add relation statistics columns to pg_temp_class.
This adds relpages, reltuples, relallvisible, and relallfrozen columns
to pg_temp_class, and updates ANALYZE, CREATE INDEX, REPACK, VACUUM,
and pg_clear/restore_relation_stats() to apply statistics updates to
pg_temp_class instead of pg_class for global temporary relations. Each
session is then able to use its own local statistics when planning
queries.
---
src/backend/access/heap/heapam.c | 4 +
src/backend/catalog/catalog.c | 2 +
src/backend/catalog/heap.c | 41 +++--
src/backend/catalog/index.c | 137 +++++++++++-----
src/backend/catalog/pg_temp_class.c | 84 ++++++++++
src/backend/commands/repack.c | 61 ++++---
src/backend/commands/vacuum.c | 89 +++++++----
src/backend/statistics/relation_stats.c | 82 ++++++----
src/backend/utils/cache/lsyscache.c | 11 ++
src/backend/utils/cache/relcache.c | 9 +-
src/include/catalog/pg_temp_class.h | 166 ++++++++++++++++++++
src/include/utils/lsyscache.h | 1 +
src/test/isolation/expected/global-temp.out | 32 ++++
src/test/isolation/specs/global-temp.spec | 7 +
src/test/regress/expected/global_temp.out | 136 ++++++++++++++++
src/test/regress/sql/global_temp.sql | 82 ++++++++++
16 files changed, 802 insertions(+), 142 deletions(-)
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index abfd8e8970a..d2588779cb8 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -42,6 +42,7 @@
#include "access/xloginsert.h"
#include "catalog/pg_database.h"
#include "catalog/pg_database_d.h"
+#include "catalog/pg_temp_class.h"
#include "commands/vacuum.h"
#include "executor/instrument_node.h"
#include "pgstat.h"
@@ -4206,6 +4207,9 @@ check_lock_if_inplace_updateable_rel(Relation relation,
return;
}
break;
+ case TempRelationRelationId:
+ /* No lock required -- temp tables are only accessible by us */
+ return;
default:
Assert(!IsInplaceUpdateRelation(relation));
return;
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 65d786685f2..b0415be2e4f 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -40,6 +40,7 @@
#include "catalog/pg_shseclabel.h"
#include "catalog/pg_subscription.h"
#include "catalog/pg_tablespace.h"
+#include "catalog/pg_temp_class.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "utils/fmgroids.h"
@@ -195,6 +196,7 @@ bool
IsInplaceUpdateOid(Oid relid)
{
return (relid == RelationRelationId ||
+ relid == TempRelationRelationId ||
relid == DatabaseRelationId);
}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 012de434321..fdd06ecc877 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -3577,10 +3577,10 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
* with the heap relation to zero tuples.
*
* The routine will truncate and then reconstruct the indexes on
- * the specified relation. Caller must hold exclusive lock on rel.
+ * the specified relation. Caller must hold the specified lock on rel.
*/
static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
{
ListCell *indlist;
@@ -3591,8 +3591,8 @@ RelationTruncateIndexes(Relation heapRelation)
Relation currentIndex;
IndexInfo *indexInfo;
- /* Open the index relation; use exclusive lock, just to be sure */
- currentIndex = index_open(indexId, AccessExclusiveLock);
+ /* Open the index relation; use same lock as heap relation */
+ currentIndex = index_open(indexId, lockmode);
/*
* Fetch info needed for index_build. Since we know there are no
@@ -3634,13 +3634,23 @@ heap_truncate(List *relids)
List *relations = NIL;
ListCell *cell;
- /* Open relations for processing, and grab exclusive access on each */
+ /*
+ * Open relations for processing. For most relations, we must use
+ * AccessExclusiveLock to prevent schema and data changes. However, for
+ * global temporary relations, we must use RowExclusiveLock, because two
+ * backends trying to upgrade to an exclusive lock on the same relation
+ * here would deadlock. This is sufficent, because the relation's data is
+ * session-local.
+ */
foreach(cell, relids)
{
Oid rid = lfirst_oid(cell);
+ LOCKMODE lockmode;
Relation rel;
- rel = table_open(rid, AccessExclusiveLock);
+ lockmode = rel_is_global_temp(rid) ? RowExclusiveLock : AccessExclusiveLock;
+
+ rel = table_open(rid, lockmode);
relations = lappend(relations, rel);
}
@@ -3655,7 +3665,7 @@ heap_truncate(List *relids)
/* Truncate the relation */
heap_truncate_one_rel(rel);
- /* Close the relation, but keep exclusive lock on it until commit */
+ /* Close the relation, but keep lock on it until commit */
table_close(rel, NoLock);
}
}
@@ -3667,13 +3677,22 @@ heap_truncate(List *relids)
*
* This is not transaction-safe, because the truncation is done immediately
* and cannot be rolled back later. Caller is responsible for having
- * checked permissions etc, and must have obtained AccessExclusiveLock.
+ * checked permissions etc, and must have obtained the required lock, which is
+ * typically AccessExclusiveLock, except if it's a global temporary relation,
+ * in which case RowExclusiveLock is sufficient.
*/
void
heap_truncate_one_rel(Relation rel)
{
+ LOCKMODE lockmode;
Oid toastrelid;
+ /*
+ * For a global temporary relation, RowExclusiveLock is sufficient.
+ * Otherwise must use AccessExclusiveLock.
+ */
+ lockmode = RELATION_IS_GLOBAL_TEMP(rel) ? RowExclusiveLock : AccessExclusiveLock;
+
/*
* Truncate the relation. Partitioned tables have no storage, so there is
* nothing to do for them here.
@@ -3685,16 +3704,16 @@ heap_truncate_one_rel(Relation rel)
table_relation_nontransactional_truncate(rel);
/* If the relation has indexes, truncate the indexes too */
- RelationTruncateIndexes(rel);
+ RelationTruncateIndexes(rel, lockmode);
/* If there is a toast table, truncate that too */
toastrelid = rel->rd_rel->reltoastrelid;
if (OidIsValid(toastrelid))
{
- Relation toastrel = table_open(toastrelid, AccessExclusiveLock);
+ Relation toastrel = table_open(toastrelid, lockmode);
table_relation_nontransactional_truncate(toastrel);
- RelationTruncateIndexes(toastrel);
+ RelationTruncateIndexes(toastrel, lockmode);
/* keep the lock... */
table_close(toastrel, NoLock);
}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index f02d51704b4..943de8a4fda 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -123,6 +123,7 @@ static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
bool isvalid,
bool isready);
static void index_update_stats(Relation rel,
+ bool isreindex,
bool hasindex,
double reltuples);
static void IndexCheckExclusion(Relation heapRelation,
@@ -1275,6 +1276,7 @@ index_create(Relation heapRelation,
* having an index.
*/
index_update_stats(heapRelation,
+ false,
true,
-1.0);
/* Make the above update visible */
@@ -2806,28 +2808,37 @@ FormIndexDatum(IndexInfo *indexInfo,
/*
- * index_update_stats --- update pg_class entry after CREATE INDEX or REINDEX
+ * index_update_stats --- update effective pg_class entry after CREATE INDEX
+ * or REINDEX
*
- * This routine updates the pg_class row of either an index or its parent
- * relation after CREATE INDEX or REINDEX. Its rather bizarre API is designed
- * to ensure we can do all the necessary work in just one update.
+ * This routine updates the effective pg_class row of either an index or its
+ * parent relation after CREATE INDEX or REINDEX. Its rather bizarre API is
+ * designed to ensure we can do all the necessary work in just one update
+ * (except for a global temporary relation, which requires both pg_class and
+ * pg_temp_class to be updated).
*
+ * isreindex: recreated a previously-existing index; don't set relhasindex
* hasindex: set relhasindex to this value
* reltuples: if >= 0, set reltuples to this value; else no change
*
* If reltuples >= 0, relpages, relallvisible, and relallfrozen are also
* updated (using RelationGetNumberOfBlocks() and visibilitymap_count()).
*
+ * For a global temporary relation, relhasindex is set in pg_class and all the
+ * other fields are set in pg_temp_class. For any other type of relation, all
+ * the fields are set in pg_class.
+ *
* NOTE: an important side-effect of this operation is that an SI invalidation
* message is sent out to all backends --- including me --- causing relcache
* entries to be flushed or updated with the new data. This must happen even
- * if we find that no change is needed in the pg_class row. When updating
- * a heap entry, this ensures that other backends find out about the new
- * index. When updating an index, it's important because some index AMs
- * expect a relcache flush to occur after REINDEX.
+ * if we find that no change is needed in the pg_class or pg_temp_class rows.
+ * When updating a heap entry, this ensures that other backends find out about
+ * the new index. When updating an index, it's important because some index
+ * AMs expect a relcache flush to occur after REINDEX.
*/
static void
index_update_stats(Relation rel,
+ bool isreindex,
bool hasindex,
double reltuples)
{
@@ -2839,9 +2850,12 @@ index_update_stats(Relation rel,
Relation pg_class;
ScanKeyData key[1];
HeapTuple tuple;
+ HeapTuple temp_tuple;
void *state;
Form_pg_class rd_rel;
+ Form_pg_temp_class temp_rd_rel;
bool dirty;
+ bool temp_dirty;
/*
* As a special hack, if we are dealing with an empty table and the
@@ -2924,16 +2938,56 @@ index_update_stats(Relation rel,
* relallvisible) if the caller isn't providing an updated reltuples
* count, because that would bollix the reltuples/relpages ratio which is
* what's really important.
+ *
+ * If not for (1) above, pg_temp_class could be updated normally, and in
+ * fact we could work round (1) by simply not updating pg_temp_class in
+ * bootstrap mode, since its value just gets thrown away when initdb
+ * finishes. However, for consistency, we update it in-place, like
+ * pg_class --- see also vac_update_relstats().
*/
- pg_class = table_open(RelationRelationId, RowExclusiveLock);
+ /*
+ * For a global temporary relation, need a writable copy of its
+ * pg_temp_class tuple. Note: can't use systable_inplace_update_begin()
+ * here because the tuple might be a pending insert --- see header
+ * comments in pg_temp_class.c.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ temp_tuple = GetPgTempClassTuple(relid);
+ if (!HeapTupleIsValid(temp_tuple))
+ elog(ERROR, "cache lookup failed for global temp relation %u", relid);
+ temp_rd_rel = (Form_pg_temp_class) GETSTRUCT(temp_tuple);
+ }
+ else
+ {
+ temp_tuple = NULL;
+ temp_rd_rel = NULL;
+ }
- ScanKeyInit(&key[0],
- Anum_pg_class_oid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(relid));
- systable_inplace_update_begin(pg_class, ClassOidIndexId, true, NULL,
- 1, key, &tuple, &state);
+ /*
+ * If this is a reindex on a global temporary table, we don't need to set
+ * pg_class.relhasindex, and all other fields go in pg_temp_class, so we
+ * only need a read-only copy of the pg_class tuple. Otherwise, we need a
+ * writable copy of the pg_class tuple to scribble on.
+ */
+ if (isreindex && RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ pg_class = NULL;
+ tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+ state = NULL;
+ }
+ else
+ {
+ pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_class_oid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+ systable_inplace_update_begin(pg_class, ClassOidIndexId, true, NULL,
+ 1, key, &tuple, &state);
+ }
if (!HeapTupleIsValid(tuple))
elog(ERROR, "could not find tuple for relation %u", relid);
@@ -2942,10 +2996,11 @@ index_update_stats(Relation rel,
/* Should this be a more comprehensive test? */
Assert(rd_rel->relkind != RELKIND_PARTITIONED_INDEX);
- /* Apply required updates, if any, to copied tuple */
+ /* Apply required updates, if any, to copied tuple(s) */
dirty = false;
- if (rd_rel->relhasindex != hasindex)
+ temp_dirty = false;
+ if (!isreindex && rd_rel->relhasindex != hasindex)
{
rd_rel->relhasindex = hasindex;
dirty = true;
@@ -2953,30 +3008,18 @@ index_update_stats(Relation rel,
if (update_stats)
{
- if (rd_rel->relpages != (int32) relpages)
- {
- rd_rel->relpages = (int32) relpages;
- dirty = true;
- }
- if (rd_rel->reltuples != (float4) reltuples)
- {
- rd_rel->reltuples = (float4) reltuples;
- dirty = true;
- }
- if (rd_rel->relallvisible != (int32) relallvisible)
- {
- rd_rel->relallvisible = (int32) relallvisible;
- dirty = true;
- }
- if (rd_rel->relallfrozen != (int32) relallfrozen)
- {
- rd_rel->relallfrozen = (int32) relallfrozen;
- dirty = true;
- }
+ SetEffective_relpages(rd_rel, temp_rd_rel, (int32) relpages,
+ &dirty, &temp_dirty);
+ SetEffective_reltuples(rd_rel, temp_rd_rel, (float4) reltuples,
+ &dirty, &temp_dirty);
+ SetEffective_relallvisible(rd_rel, temp_rd_rel, (int32) relallvisible,
+ &dirty, &temp_dirty);
+ SetEffective_relallfrozen(rd_rel, temp_rd_rel, (int32) relallfrozen,
+ &dirty, &temp_dirty);
}
/*
- * If anything changed, write out the tuple
+ * If anything changed, write out the tuple(s)
*/
if (dirty)
{
@@ -2985,7 +3028,8 @@ index_update_stats(Relation rel,
}
else
{
- systable_inplace_update_cancel(state);
+ if (state != NULL)
+ systable_inplace_update_cancel(state);
/*
* While we didn't change relhasindex, CREATE INDEX needs a
@@ -2997,9 +3041,18 @@ index_update_stats(Relation rel,
CacheInvalidateRelcacheByTuple(tuple);
}
+ if (HeapTupleIsValid(temp_tuple))
+ {
+ if (temp_dirty)
+ UpdatePgTempClassTupleInPlace(relid, temp_tuple);
+
+ heap_freetuple(temp_tuple);
+ }
+
heap_freetuple(tuple);
- table_close(pg_class, RowExclusiveLock);
+ if (RelationIsValid(pg_class))
+ table_close(pg_class, RowExclusiveLock);
}
@@ -3177,11 +3230,11 @@ index_build(Relation heapRelation,
* Update heap and index pg_class rows
*/
index_update_stats(heapRelation,
- true,
+ isreindex, true,
stats->heap_tuples);
index_update_stats(indexRelation,
- false,
+ isreindex, false,
stats->index_tuples);
/* Make the updated catalog row versions visible */
diff --git a/src/backend/catalog/pg_temp_class.c b/src/backend/catalog/pg_temp_class.c
index 85dab4bbb0b..53b058fcf3d 100644
--- a/src/backend/catalog/pg_temp_class.c
+++ b/src/backend/catalog/pg_temp_class.c
@@ -54,6 +54,7 @@
#include "catalog/indexing.h"
#include "catalog/pg_temp_class.h"
#include "miscadmin.h"
+#include "utils/fmgroids.h"
#include "utils/hsearch.h"
#include "utils/memutils.h"
#include "utils/syscache.h"
@@ -168,6 +169,18 @@ get_pg_temp_class_tupdesc(void)
TupleDescInitEntry(tupdesc,
(AttrNumber) Anum_pg_temp_class_reltablespace,
"reltablespace", OIDOID, -1, 0);
+ TupleDescInitEntry(tupdesc,
+ (AttrNumber) Anum_pg_temp_class_relpages,
+ "relpages", INT4OID, -1, 0);
+ TupleDescInitEntry(tupdesc,
+ (AttrNumber) Anum_pg_temp_class_reltuples,
+ "reltuples", FLOAT4OID, -1, 0);
+ TupleDescInitEntry(tupdesc,
+ (AttrNumber) Anum_pg_temp_class_relallvisible,
+ "relallvisible", INT4OID, -1, 0);
+ TupleDescInitEntry(tupdesc,
+ (AttrNumber) Anum_pg_temp_class_relallfrozen,
+ "relallfrozen", INT4OID, -1, 0);
TupleDescFinalize(tupdesc);
MemoryContextSwitchTo(oldcontext);
@@ -194,6 +207,10 @@ heap_form_pg_temp_class_tuple(Relation rel)
values[Anum_pg_temp_class_oid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
values[Anum_pg_temp_class_relfilenode - 1] = ObjectIdGetDatum(form->relfilenode);
values[Anum_pg_temp_class_reltablespace - 1] = ObjectIdGetDatum(form->reltablespace);
+ values[Anum_pg_temp_class_relpages - 1] = Int32GetDatum(form->relpages);
+ values[Anum_pg_temp_class_reltuples - 1] = Float4GetDatum(form->reltuples);
+ values[Anum_pg_temp_class_relallvisible - 1] = Int32GetDatum(form->relallvisible);
+ values[Anum_pg_temp_class_relallfrozen - 1] = Int32GetDatum(form->relallfrozen);
return heap_form_tuple(get_pg_temp_class_tupdesc(), values, nulls);
}
@@ -395,6 +412,73 @@ UpdatePgTempClassTuple(Oid relid, HeapTuple newtuple)
table_close(pg_temp_class, RowExclusiveLock);
}
+/*
+ * UpdatePgTempClassTupleInPlace
+ *
+ * Do an in-place update of the pg_temp_class tuple for a global temporary
+ * relation.
+ */
+void
+UpdatePgTempClassTupleInPlace(Oid relid, HeapTuple newtuple)
+{
+ Relation pg_temp_class;
+ ScanKeyData key[1];
+ HeapTuple oldtuple;
+ void *inplace_state;
+
+ /*
+ * If there is a pending insert for this relation that hasn't been
+ * flushed, then insert the new tuple instead, and mark the pending entry
+ * as inserted, so subsequent fetches retrieve it from the database. On
+ * subtransaction rollback, the pending entry will be unmarked, and may be
+ * inserted instead. We intentionally flush to the database here in order
+ * to trigger cache invalidation for this relation.
+ */
+ if (pending_inserts != NULL)
+ {
+ PendingInsert *entry;
+
+ entry = hash_search(pending_inserts, &relid, HASH_FIND, NULL);
+ if (entry != NULL)
+ {
+ /* Entry must not have been deleted in the same subtransaction */
+ if (entry->deleted_subid != InvalidSubTransactionId)
+ elog(ERROR,
+ "pending insert for global temp relation %u was deleted",
+ relid);
+
+ if (entry->inserted_subid == InvalidSubTransactionId)
+ {
+ /* Insert the new version of the tuple */
+ pg_temp_class = open_pg_temp_class(RowExclusiveLock);
+ CatalogTupleInsert(pg_temp_class, newtuple);
+ table_close(pg_temp_class, RowExclusiveLock);
+ entry->inserted_subid = GetCurrentSubTransactionId();
+ return;
+ }
+ }
+ }
+
+ /* Otherwise, update the pg_temp_class tuple directly */
+ pg_temp_class = open_pg_temp_class(RowExclusiveLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_temp_class_oid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+
+ systable_inplace_update_begin(pg_temp_class, TempClassOidIndexId, true,
+ NULL, 1, key, &oldtuple, &inplace_state);
+ if (!HeapTupleIsValid(oldtuple))
+ elog(ERROR, "cache lookup failed for global temp relation %u", relid);
+
+ systable_inplace_update_finish(inplace_state, newtuple);
+
+ heap_freetuple(oldtuple);
+
+ table_close(pg_temp_class, RowExclusiveLock);
+}
+
/*
* DeletePgTempClassTuple
*
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 81bc7dcc312..5f11e699dc4 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1298,7 +1298,9 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex,
{
Relation relRelation;
HeapTuple reltup;
+ HeapTuple temp_reltup;
Form_pg_class relform;
+ Form_pg_temp_class temp_relform;
TupleDesc oldTupDesc PG_USED_FOR_ASSERTS_ONLY;
TupleDesc newTupDesc PG_USED_FOR_ASSERTS_ONLY;
VacuumParams params;
@@ -1490,21 +1492,30 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex,
tups_recently_dead,
pg_rusage_show(&ru0))));
- /* Update pg_class to reflect the correct values of pages and tuples. */
+ /*
+ * Update pg_class / pg_temp_class to reflect the correct values of pages
+ * and tuples.
+ */
relRelation = table_open(RelationRelationId, RowExclusiveLock);
- reltup = SearchSysCacheCopy1(RELOID,
- ObjectIdGetDatum(RelationGetRelid(NewHeap)));
+ reltup = GetPgClassAndPgTempClassTuples(RelationGetRelid(NewHeap), false,
+ &temp_reltup, true);
if (!HeapTupleIsValid(reltup))
elog(ERROR, "cache lookup failed for relation %u",
RelationGetRelid(NewHeap));
relform = (Form_pg_class) GETSTRUCT(reltup);
+ temp_relform = (Form_pg_temp_class) GETSTRUCT_SAFE(temp_reltup);
- relform->relpages = num_pages;
- relform->reltuples = num_tuples;
+ SetEffective_relpages(relform, temp_relform, num_pages, NULL, NULL);
+ SetEffective_reltuples(relform, temp_relform, num_tuples, NULL, NULL);
/* Don't update the stats for pg_class. See swap_relation_files. */
- if (RelationGetRelid(OldHeap) != RelationRelationId)
+ if (HeapTupleIsValid(temp_reltup))
+ {
+ UpdatePgTempClassTuple(RelationGetRelid(NewHeap), temp_reltup);
+ heap_freetuple(temp_reltup);
+ }
+ else if (RelationGetRelid(OldHeap) != RelationRelationId)
CatalogTupleUpdate(relRelation, &reltup->t_self, reltup);
else
CacheInvalidateRelcacheByTuple(reltup);
@@ -1733,21 +1744,29 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
int32 swap_allvisible;
int32 swap_allfrozen;
- swap_pages = relform1->relpages;
- relform1->relpages = relform2->relpages;
- relform2->relpages = swap_pages;
-
- swap_tuples = relform1->reltuples;
- relform1->reltuples = relform2->reltuples;
- relform2->reltuples = swap_tuples;
-
- swap_allvisible = relform1->relallvisible;
- relform1->relallvisible = relform2->relallvisible;
- relform2->relallvisible = swap_allvisible;
-
- swap_allfrozen = relform1->relallfrozen;
- relform1->relallfrozen = relform2->relallfrozen;
- relform2->relallfrozen = swap_allfrozen;
+ swap_pages = GetEffective_relpages(relform1, temp_relform1);
+ SetEffective_relpages(relform1, temp_relform1,
+ GetEffective_relpages(relform2, temp_relform2),
+ NULL, NULL);
+ SetEffective_relpages(relform2, temp_relform2, swap_pages, NULL, NULL);
+
+ swap_tuples = GetEffective_reltuples(relform1, temp_relform1);
+ SetEffective_reltuples(relform1, temp_relform1,
+ GetEffective_reltuples(relform2, temp_relform2),
+ NULL, NULL);
+ SetEffective_reltuples(relform2, temp_relform2, swap_tuples, NULL, NULL);
+
+ swap_allvisible = GetEffective_relallvisible(relform1, temp_relform1);
+ SetEffective_relallvisible(relform1, temp_relform1,
+ GetEffective_relallvisible(relform2, temp_relform2),
+ NULL, NULL);
+ SetEffective_relallvisible(relform2, temp_relform2, swap_allvisible, NULL, NULL);
+
+ swap_allfrozen = GetEffective_relallfrozen(relform1, temp_relform1);
+ SetEffective_relallfrozen(relform1, temp_relform1,
+ GetEffective_relallfrozen(relform2, temp_relform2),
+ NULL, NULL);
+ SetEffective_relallfrozen(relform2, temp_relform2, swap_allfrozen, NULL, NULL);
}
/*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 4e15028217c..9c776d5a978 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1393,9 +1393,10 @@ vac_estimate_reltuples(Relation relation,
* vac_update_relstats() -- update statistics for one relation
*
* Update the whole-relation statistics that are kept in its pg_class
- * row. There are additional stats that will be updated if we are
- * doing ANALYZE, but we always update these stats. This routine works
- * for both index and heap relation entries in pg_class.
+ * row (and its pg_temp_class row, for a global temporary relation).
+ * There are additional stats that will be updated if we are doing
+ * ANALYZE, but we always update these stats. This routine works for both
+ * index and heap relation entries in pg_class and pg_temp_class.
*
* We violate transaction semantics here by overwriting the rel's
* existing pg_class tuple with the new values. This is reasonably
@@ -1404,12 +1405,17 @@ vac_estimate_reltuples(Relation relation,
* we updated these tuples in the usual way, vacuuming pg_class itself
* wouldn't work very well --- by the time we got done with a vacuum
* cycle, most of the tuples in pg_class would've been obsoleted. Of
- * course, this only works for fixed-size not-null columns, but these are.
+ * course, this only works for fixed-size not-null columns, but these
+ * are. Likewise for pg_temp_class, which is also updated for a global
+ * temporary relation.
*
* Another reason for doing it this way is that when we are in a lazy
* VACUUM and have PROC_IN_VACUUM set, we mustn't do any regular updates.
* Somebody vacuuming pg_class might think they could delete a tuple
- * marked with xmin = our xid.
+ * marked with xmin = our xid. This isn't a problem for pg_temp_class
+ * because no other session can see our copy of its data, but it still
+ * makes sense to do an in-place update to avoid vacuumed pg_temp_class
+ * tuples being obsoleted.
*
* In addition to fundamentally nontransactional statistics such as
* relpages and relallvisible, we try to maintain certain lazily-updated
@@ -1424,8 +1430,8 @@ vac_estimate_reltuples(Relation relation,
* transaction. This is OK since postponing the flag maintenance is
* always allowable.
*
- * Note: num_tuples should count only *live* tuples, since
- * pg_class.reltuples is defined that way.
+ * Note: num_tuples should count only *live* tuples, since reltuples in
+ * pg_class and pg_temp_class is defined that way.
*
* This routine is shared by VACUUM and ANALYZE.
*/
@@ -1443,17 +1449,39 @@ vac_update_relstats(Relation relation,
Relation rd;
ScanKeyData key[1];
HeapTuple ctup;
+ HeapTuple temp_ctup;
void *inplace_state;
Form_pg_class pgcform;
+ Form_pg_temp_class temp_pgcform;
bool dirty,
+ temp_dirty,
futurexid,
futuremxid;
TransactionId oldfrozenxid;
MultiXactId oldminmulti;
+ /*
+ * For a global temporary relation, need a writable copy of its
+ * pg_temp_class tuple. Note: can't use systable_inplace_update_begin()
+ * here because the tuple might be a pending insert --- see header
+ * comments in pg_temp_class.c.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(relation))
+ {
+ temp_ctup = GetPgTempClassTuple(relid);
+ if (!HeapTupleIsValid(temp_ctup))
+ elog(ERROR, "cache lookup failed for global temp relation %u", relid);
+ temp_pgcform = (Form_pg_temp_class) GETSTRUCT(temp_ctup);
+ }
+ else
+ {
+ temp_ctup = NULL;
+ temp_pgcform = NULL;
+ }
+
+ /* Fetch a copy of the pg_class tuple to scribble on */
rd = table_open(RelationRelationId, RowExclusiveLock);
- /* Fetch a copy of the tuple to scribble on */
ScanKeyInit(&key[0],
Anum_pg_class_oid,
BTEqualStrategyNumber, F_OIDEQ,
@@ -1465,29 +1493,20 @@ vac_update_relstats(Relation relation,
relid);
pgcform = (Form_pg_class) GETSTRUCT(ctup);
- /* Apply statistical updates, if any, to copied tuple */
+ /* Apply statistical updates, if any, to copied tuple(s) */
dirty = false;
- if (pgcform->relpages != (int32) num_pages)
- {
- pgcform->relpages = (int32) num_pages;
- dirty = true;
- }
- if (pgcform->reltuples != (float4) num_tuples)
- {
- pgcform->reltuples = (float4) num_tuples;
- dirty = true;
- }
- if (pgcform->relallvisible != (int32) num_all_visible_pages)
- {
- pgcform->relallvisible = (int32) num_all_visible_pages;
- dirty = true;
- }
- if (pgcform->relallfrozen != (int32) num_all_frozen_pages)
- {
- pgcform->relallfrozen = (int32) num_all_frozen_pages;
- dirty = true;
- }
+ temp_dirty = false;
+ SetEffective_relpages(pgcform, temp_pgcform, (int32) num_pages,
+ &dirty, &temp_dirty);
+ SetEffective_reltuples(pgcform, temp_pgcform, (float4) num_tuples,
+ &dirty, &temp_dirty);
+ SetEffective_relallvisible(pgcform, temp_pgcform,
+ (int32) num_all_visible_pages,
+ &dirty, &temp_dirty);
+ SetEffective_relallfrozen(pgcform, temp_pgcform,
+ (int32) num_all_frozen_pages,
+ &dirty, &temp_dirty);
/* Apply DDL updates, but not inside an outer transaction (see above) */
@@ -1570,12 +1589,22 @@ vac_update_relstats(Relation relation,
}
}
- /* If anything changed, write out the tuple. */
+ /* If anything changed, write out the tuple(s) */
if (dirty)
systable_inplace_update_finish(inplace_state, ctup);
else
systable_inplace_update_cancel(inplace_state);
+ if (HeapTupleIsValid(temp_ctup))
+ {
+ if (temp_dirty)
+ UpdatePgTempClassTupleInPlace(relid, temp_ctup);
+
+ heap_freetuple(temp_ctup);
+ }
+
+ heap_freetuple(ctup);
+
table_close(rd, RowExclusiveLock);
if (futurexid)
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index d6631e9a9a4..26ec5b24692 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -20,6 +20,7 @@
#include "access/heapam.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
+#include "catalog/pg_temp_class.h"
#include "nodes/makefuncs.h"
#include "statistics/stat_utils.h"
#include "utils/builtins.h"
@@ -79,10 +80,11 @@ relation_statistics_update(FunctionCallInfo fcinfo)
bool update_relallfrozen = false;
HeapTuple ctup;
Form_pg_class pgcform;
- int replaces[4] = {0};
- Datum values[4] = {0};
- bool nulls[4] = {0};
- int nreplaces = 0;
+ Relation rel;
+ HeapTuple temp_ctup;
+ Form_pg_temp_class temp_pgcform;
+ bool dirty;
+ bool temp_dirty;
Oid locked_table = InvalidOid;
stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG);
@@ -139,52 +141,64 @@ relation_statistics_update(FunctionCallInfo fcinfo)
*/
crel = table_open(RelationRelationId, RowExclusiveLock);
- ctup = SearchSysCache1(RELOID, ObjectIdGetDatum(reloid));
+ ctup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(reloid));
if (!HeapTupleIsValid(ctup))
elog(ERROR, "pg_class entry for relid %u not found", reloid);
pgcform = (Form_pg_class) GETSTRUCT(ctup);
- if (update_relpages && relpages != pgcform->relpages)
+ /*
+ * For a global temporary table, need to update the pg_temp_class tuple
+ * instead. Force it into existence by opening the relation.
+ */
+ if (pgcform->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
{
- replaces[nreplaces] = Anum_pg_class_relpages;
- values[nreplaces] = UInt32GetDatum(relpages);
- nreplaces++;
- }
+ rel = relation_open(reloid, AccessShareLock);
+ relation_close(rel, AccessShareLock);
- if (update_reltuples && reltuples != pgcform->reltuples)
- {
- replaces[nreplaces] = Anum_pg_class_reltuples;
- values[nreplaces] = Float4GetDatum(reltuples);
- nreplaces++;
- }
+ temp_ctup = GetPgTempClassTuple(reloid);
+ if (!HeapTupleIsValid(temp_ctup))
+ elog(ERROR, "pg_temp_class entry for relid %u not found", reloid);
- if (update_relallvisible && relallvisible != pgcform->relallvisible)
- {
- replaces[nreplaces] = Anum_pg_class_relallvisible;
- values[nreplaces] = UInt32GetDatum(relallvisible);
- nreplaces++;
+ temp_pgcform = (Form_pg_temp_class) GETSTRUCT(temp_ctup);
}
-
- if (update_relallfrozen && relallfrozen != pgcform->relallfrozen)
+ else
{
- replaces[nreplaces] = Anum_pg_class_relallfrozen;
- values[nreplaces] = UInt32GetDatum(relallfrozen);
- nreplaces++;
+ temp_ctup = NULL;
+ temp_pgcform = NULL;
}
- if (nreplaces > 0)
+ dirty = false;
+ temp_dirty = false;
+
+ if (update_relpages)
+ SetEffective_relpages(pgcform, temp_pgcform, (int32) relpages,
+ &dirty, &temp_dirty);
+
+ if (update_reltuples)
+ SetEffective_reltuples(pgcform, temp_pgcform, (float4) reltuples,
+ &dirty, &temp_dirty);
+
+ if (update_relallvisible)
+ SetEffective_relallvisible(pgcform, temp_pgcform, (int32) relallvisible,
+ &dirty, &temp_dirty);
+
+ if (update_relallfrozen)
+ SetEffective_relallfrozen(pgcform, temp_pgcform, (int32) relallfrozen,
+ &dirty, &temp_dirty);
+
+ if (dirty)
+ CatalogTupleUpdate(crel, &ctup->t_self, ctup);
+
+ if (HeapTupleIsValid(temp_ctup))
{
- TupleDesc tupdesc = RelationGetDescr(crel);
- HeapTuple newtup;
+ if (temp_dirty)
+ UpdatePgTempClassTuple(reloid, temp_ctup);
- newtup = heap_modify_tuple_by_cols(ctup, tupdesc, nreplaces,
- replaces, values, nulls);
- CatalogTupleUpdate(crel, &newtup->t_self, newtup);
- heap_freetuple(newtup);
+ heap_freetuple(temp_ctup);
}
- ReleaseSysCache(ctup);
+ heap_freetuple(ctup);
/* release the lock, consistent with vac_update_relstats() */
table_close(crel, RowExclusiveLock);
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index cbd25fa6144..e8068a45cc8 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2411,6 +2411,17 @@ get_rel_persistence(Oid relid)
return result;
}
+/*
+ * rel_is_global_temp
+ *
+ * Returns true if the given relation is a global temporary relation.
+ */
+bool
+rel_is_global_temp(Oid relid)
+{
+ return get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP;
+}
+
/*
* get_rel_relam
*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index af8b5237e45..274132ced87 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4040,10 +4040,11 @@ RelationSetNewRelfilenumber(Relation relation, char persistence)
/* relpages etc. never change for sequences */
if (relation->rd_rel->relkind != RELKIND_SEQUENCE)
{
- classform->relpages = 0; /* it's empty until further notice */
- classform->reltuples = -1;
- classform->relallvisible = 0;
- classform->relallfrozen = 0;
+ /* it's empty until further notice */
+ SetEffective_relpages(classform, temp_classform, 0, NULL, NULL);
+ SetEffective_reltuples(classform, temp_classform, -1, NULL, NULL);
+ SetEffective_relallvisible(classform, temp_classform, 0, NULL, NULL);
+ SetEffective_relallfrozen(classform, temp_classform, 0, NULL, NULL);
}
classform->relfrozenxid = freezeXid;
classform->relminmxid = minmulti;
diff --git a/src/include/catalog/pg_temp_class.h b/src/include/catalog/pg_temp_class.h
index 80011ea0954..e6b77b50d23 100644
--- a/src/include/catalog/pg_temp_class.h
+++ b/src/include/catalog/pg_temp_class.h
@@ -45,6 +45,18 @@ CATALOG(pg_temp_class,8082,TempRelationRelationId) BKI_TEMP_RELATION
/* identifier of table space for relation (0 means default for database) */
Oid reltablespace BKI_DEFAULT(0) BKI_LOOKUP_OPT(pg_tablespace);
+
+ /* # of blocks (not always up-to-date) */
+ int32 relpages BKI_DEFAULT(0);
+
+ /* # of tuples (not always up-to-date; -1 means "unknown") */
+ float4 reltuples BKI_DEFAULT(-1);
+
+ /* # of all-visible blocks (not always up-to-date) */
+ int32 relallvisible BKI_DEFAULT(0);
+
+ /* # of all-frozen blocks (not always up-to-date) */
+ int32 relallfrozen BKI_DEFAULT(0);
} FormData_pg_temp_class;
END_CATALOG_STRUCT
@@ -71,6 +83,10 @@ MAKE_SYSCACHE(TEMPRELOID, pg_temp_class_oid_index, 128);
(target)->oid = (source)->oid; \
(target)->relfilenode = (source)->relfilenode; \
(target)->reltablespace = (source)->reltablespace; \
+ (target)->relpages = (source)->relpages; \
+ (target)->reltuples = (source)->reltuples; \
+ (target)->relallvisible = (source)->relallvisible; \
+ (target)->relallfrozen = (source)->relallfrozen; \
} while (0)
/*
@@ -93,6 +109,46 @@ GetEffective_reltablespace(Form_pg_class cf, Form_pg_temp_class tf)
return tf != NULL ? tf->reltablespace : cf->reltablespace;
}
+/*
+ * Get the effective value of relpages from pg_class and pg_temp_class tuple
+ * data. The value from pg_temp_class (if present) takes precedence.
+ */
+static inline int32
+GetEffective_relpages(Form_pg_class cf, Form_pg_temp_class tf)
+{
+ return tf != NULL ? tf->relpages : cf->relpages;
+}
+
+/*
+ * Get the effective value of reltuples from pg_class and pg_temp_class tuple
+ * data. The value from pg_temp_class (if present) takes precedence.
+ */
+static inline float4
+GetEffective_reltuples(Form_pg_class cf, Form_pg_temp_class tf)
+{
+ return tf != NULL ? tf->reltuples : cf->reltuples;
+}
+
+/*
+ * Get the effective value of relallvisible from pg_class and pg_temp_class
+ * tuple data. The value from pg_temp_class (if present) takes precedence.
+ */
+static inline int32
+GetEffective_relallvisible(Form_pg_class cf, Form_pg_temp_class tf)
+{
+ return tf != NULL ? tf->relallvisible : cf->relallvisible;
+}
+
+/*
+ * Get the effective value of relallfrozen from pg_class and pg_temp_class
+ * tuple data. The value from pg_temp_class (if present) takes precedence.
+ */
+static inline int32
+GetEffective_relallfrozen(Form_pg_class cf, Form_pg_temp_class tf)
+{
+ return tf != NULL ? tf->relallfrozen : cf->relallfrozen;
+}
+
/*
* Set the effective value of relfilenode in tuple form data from pg_class or
* pg_temp_class. The value is set in pg_temp_class instead of pg_class, if
@@ -121,6 +177,114 @@ SetEffective_reltablespace(Form_pg_class cf, Form_pg_temp_class tf, Oid val)
tf->reltablespace = val;
}
+/*
+ * Set the effective value of relpages in tuple form data from pg_class or
+ * pg_temp_class. The value is set in pg_temp_class instead of pg_class, if
+ * the pg_temp_class tuple form data is non-NULL. If non-NULL, the cdirty or
+ * tdirty flag is updated, if the value actually changes.
+ */
+static inline void
+SetEffective_relpages(Form_pg_class cf, Form_pg_temp_class tf, int32 val,
+ bool *cdirty, bool *tdirty)
+{
+ if (tf != NULL)
+ {
+ if (val != tf->relpages)
+ {
+ tf->relpages = val;
+ if (tdirty != NULL)
+ *tdirty = true;
+ }
+ }
+ else if (val != cf->relpages)
+ {
+ cf->relpages = val;
+ if (cdirty != NULL)
+ *cdirty = true;
+ }
+}
+
+/*
+ * Set the effective value of reltuples in tuple form data from pg_class or
+ * pg_temp_class. The value is set in pg_temp_class instead of pg_class, if
+ * the pg_temp_class tuple form data is non-NULL. If non-NULL, the cdirty or
+ * tdirty flag is updated, if the value actually changes.
+ */
+static inline void
+SetEffective_reltuples(Form_pg_class cf, Form_pg_temp_class tf, float4 val,
+ bool *cdirty, bool *tdirty)
+{
+ if (tf != NULL)
+ {
+ if (val != tf->reltuples)
+ {
+ tf->reltuples = val;
+ if (tdirty != NULL)
+ *tdirty = true;
+ }
+ }
+ else if (val != cf->reltuples)
+ {
+ cf->reltuples = val;
+ if (cdirty != NULL)
+ *cdirty = true;
+ }
+}
+
+/*
+ * Set the effective value of relallvisible in tuple form data from pg_class
+ * or pg_temp_class. The value is set in pg_temp_class instead of pg_class,
+ * if the pg_temp_class tuple form data is non-NULL. If non-NULL, the cdirty
+ * or tdirty flag is updated, if the value actually changes.
+ */
+static inline void
+SetEffective_relallvisible(Form_pg_class cf, Form_pg_temp_class tf, int32 val,
+ bool *cdirty, bool *tdirty)
+{
+ if (tf != NULL)
+ {
+ if (val != tf->relallvisible)
+ {
+ tf->relallvisible = val;
+ if (tdirty != NULL)
+ *tdirty = true;
+ }
+ }
+ else if (val != cf->relallvisible)
+ {
+ cf->relallvisible = val;
+ if (cdirty != NULL)
+ *cdirty = true;
+ }
+}
+
+/*
+ * Set the effective value of relallfrozen in tuple form data from pg_class or
+ * pg_temp_class. The value is set in pg_temp_class instead of pg_class, if
+ * the pg_temp_class tuple form data is non-NULL. If non-NULL, the cdirty or
+ * tdirty flag is updated, if the value actually changes.
+ */
+static inline void
+SetEffective_relallfrozen(Form_pg_class cf, Form_pg_temp_class tf, int32 val,
+ bool *cdirty, bool *tdirty)
+{
+ if (tf != NULL)
+ {
+ if (val != tf->relallfrozen)
+ {
+ tf->relallfrozen = val;
+ if (tdirty != NULL)
+ *tdirty = true;
+ }
+ }
+ else if (val != cf->relallfrozen)
+ {
+ cf->relallfrozen = val;
+ if (cdirty != NULL)
+ *cdirty = true;
+ }
+}
+
extern HeapTuple GetPgTempClassTuple(Oid relid);
@@ -128,6 +292,8 @@ extern void InsertPgTempClassTuple(Relation rel);
extern void UpdatePgTempClassTuple(Oid relid, HeapTuple newtuple);
+extern void UpdatePgTempClassTupleInPlace(Oid relid, HeapTuple newtuple);
+
extern void DeletePgTempClassTuple(Oid relid);
extern HeapTuple GetPgClassAndPgTempClassTuples(Oid relid, bool lock_tuple,
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 865980cb0f1..c556b667c5e 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -150,6 +150,7 @@ extern char get_rel_relkind(Oid relid);
extern bool get_rel_relispartition(Oid relid);
extern Oid get_rel_tablespace(Oid relid);
extern char get_rel_persistence(Oid relid);
+extern bool rel_is_global_temp(Oid relid);
extern Oid get_rel_relam(Oid relid);
extern Oid get_transform_fromsql(Oid typid, Oid langid, List *trftypes);
extern Oid get_transform_tosql(Oid typid, Oid langid, List *trftypes);
diff --git a/src/test/isolation/expected/global-temp.out b/src/test/isolation/expected/global-temp.out
index 65025721db1..bbaa768f479 100644
--- a/src/test/isolation/expected/global-temp.out
+++ b/src/test/isolation/expected/global-temp.out
@@ -252,6 +252,38 @@ key|pg_typeof|val
step drop1: DROP TABLE tmp2;
+starting permutation: create1dr b1 b2 ins1_2 ins2_2 sel1_2 sel2_2 c1 c2 sel1_2 sel2_2 drop1
+step create1dr: CREATE GLOBAL TEMP TABLE tmp2 (key int, val text) ON COMMIT DELETE ROWS;
+step b1: BEGIN;
+step b2: BEGIN;
+step ins1_2: INSERT INTO tmp2 VALUES (1, 's1');
+step ins2_2: INSERT INTO tmp2 VALUES (1, 's2');
+step sel1_2: SELECT * FROM tmp2;
+key|val
+---+---
+ 1|s1
+(1 row)
+
+step sel2_2: SELECT * FROM tmp2;
+key|val
+---+---
+ 1|s2
+(1 row)
+
+step c1: COMMIT;
+step c2: COMMIT;
+step sel1_2: SELECT * FROM tmp2;
+key|val
+---+---
+(0 rows)
+
+step sel2_2: SELECT * FROM tmp2;
+key|val
+---+---
+(0 rows)
+
+step drop1: DROP TABLE tmp2;
+
starting permutation: create1dr ins1_2 ins2_2 drop1 create1dr ins1_2 ins2_2 drop1
step create1dr: CREATE GLOBAL TEMP TABLE tmp2 (key int, val text) ON COMMIT DELETE ROWS;
step ins1_2: INSERT INTO tmp2 VALUES (1, 's1');
diff --git a/src/test/isolation/specs/global-temp.spec b/src/test/isolation/specs/global-temp.spec
index ecb07ad0b6e..a57fcfd3e53 100644
--- a/src/test/isolation/specs/global-temp.spec
+++ b/src/test/isolation/specs/global-temp.spec
@@ -14,6 +14,7 @@ teardown {
session s1
setup { SET allow_in_place_tablespaces = true; }
+step b1 { BEGIN; }
step create_tblspace { CREATE TABLESPACE regress_isolation_tablespace LOCATION ''; }
step list_tblspaces { SELECT spcname FROM pg_tablespace ORDER BY 1; }
step drop_tblspace { DROP TABLESPACE regress_isolation_tablespace; }
@@ -25,10 +26,12 @@ step sel1p { SELECT tableoid::regclass, * FROM tmp_parted; }
step create1 { CREATE GLOBAL TEMP TABLE tmp2 (key int, val text); }
step create1dr { CREATE GLOBAL TEMP TABLE tmp2 (key int, val text) ON COMMIT DELETE ROWS; }
step ins1_2 { INSERT INTO tmp2 VALUES (1, 's1'); }
+step sel1_2 { SELECT * FROM tmp2; }
step alter1a { ALTER TABLE tmp2 ALTER COLUMN key SET DATA TYPE numeric; }
step alter1b { ALTER TABLE tmp2 ALTER COLUMN val SET NOT NULL; }
step seltype1 { SELECT key, pg_typeof(key), val FROM tmp2; }
step drop1 { DROP TABLE tmp2; }
+step c1 { COMMIT; }
step idx1 { CREATE INDEX tmp_val_idx ON tmp(val); }
step sel1_idx {
SET enable_seqscan = off;
@@ -63,6 +66,7 @@ step r2 { ROLLBACK; }
step sp2 { SAVEPOINT sp; }
step rsp2 { ROLLBACK TO SAVEPOINT sp; }
step ins2_2 { INSERT INTO tmp2 VALUES (1, 's2'); }
+step sel2_2 { SELECT * FROM tmp2; }
step seltype2 { SELECT key, pg_typeof(key), val FROM tmp2; }
step sel2_idx {
SET enable_seqscan = off;
@@ -100,6 +104,9 @@ permutation ins1 b2 ins2 sp2 t2 rsp2 sel1 sel2 r2 sel1 sel2
permutation create1 ins1_2 alter1a alter1b ins2_2 seltype1 seltype2 drop1
permutation create1 ins1_2 ins2_2 alter1a alter1b seltype1 seltype2 drop1
+# Test concurrent ON COMMIT DELETE ROWS
+permutation create1dr b1 b2 ins1_2 ins2_2 sel1_2 sel2_2 c1 c2 sel1_2 sel2_2 drop1
+
# Test DROP with ON COMMIT DELETE ROWS
permutation create1dr ins1_2 ins2_2 drop1 create1dr ins1_2 ins2_2 drop1
diff --git a/src/test/regress/expected/global_temp.out b/src/test/regress/expected/global_temp.out
index cc9400284ff..8cec1b0cfca 100644
--- a/src/test/regress/expected/global_temp.out
+++ b/src/test/regress/expected/global_temp.out
@@ -494,6 +494,142 @@ SELECT * FROM tmp1;
1 | xxx | 1
(1 row)
+-- Test stats updates applied by CREATE INDEX, ANALYZE, VACUUM, and REPACK
+CREATE GLOBAL TEMP TABLE tmp2 (a int);
+INSERT INTO tmp2 SELECT * FROM generate_series(1, 100);
+SELECT c.oid::regclass,
+ c.relpages AS global_relpages, c.reltuples AS global_reltuples,
+ CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages,
+ t.reltuples AS local_reltuples
+ FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid
+ WHERE c.oid = 'tmp2'::regclass;
+ oid | global_relpages | global_reltuples | local_relpages | local_reltuples
+------+-----------------+------------------+----------------+-----------------
+ tmp2 | 0 | -1 | zero | -1
+(1 row)
+
+CREATE INDEX tmp2_a_idx ON tmp2(a);
+SELECT c.oid::regclass,
+ c.relpages AS global_relpages, c.reltuples AS global_reltuples,
+ CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages,
+ t.reltuples AS local_reltuples
+ FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid
+ WHERE c.oid = 'tmp2'::regclass OR c.oid = 'tmp2_a_idx'::regclass ORDER BY 1;
+ oid | global_relpages | global_reltuples | local_relpages | local_reltuples
+------------+-----------------+------------------+----------------+-----------------
+ tmp2 | 0 | -1 | non-zero | 100
+ tmp2_a_idx | 0 | 0 | non-zero | 100
+(2 rows)
+
+INSERT INTO tmp2 SELECT * FROM generate_series(101, 300);
+ANALYZE tmp2;
+SELECT c.oid::regclass,
+ c.relpages AS global_relpages, c.reltuples AS global_reltuples,
+ CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages,
+ t.reltuples AS local_reltuples
+ FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid
+ WHERE c.oid = 'tmp2'::regclass OR c.oid = 'tmp2_a_idx'::regclass ORDER BY 1;
+ oid | global_relpages | global_reltuples | local_relpages | local_reltuples
+------------+-----------------+------------------+----------------+-----------------
+ tmp2 | 0 | -1 | non-zero | 300
+ tmp2_a_idx | 0 | 0 | non-zero | 300
+(2 rows)
+
+DELETE FROM tmp2 WHERE a % 2 = 0;
+VACUUM ANALYZE tmp2;
+SELECT c.oid::regclass,
+ c.relpages AS global_relpages, c.reltuples AS global_reltuples,
+ CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages,
+ t.reltuples AS local_reltuples
+ FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid
+ WHERE c.oid = 'tmp2'::regclass OR c.oid = 'tmp2_a_idx'::regclass ORDER BY 1;
+ oid | global_relpages | global_reltuples | local_relpages | local_reltuples
+------------+-----------------+------------------+----------------+-----------------
+ tmp2 | 0 | -1 | non-zero | 150
+ tmp2_a_idx | 0 | 0 | non-zero | 150
+(2 rows)
+
+DELETE FROM tmp2 WHERE a % 3 = 0;
+REPACK (ANALYZE) tmp2;
+SELECT c.oid::regclass,
+ c.relpages AS global_relpages, c.reltuples AS global_reltuples,
+ CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages,
+ t.reltuples AS local_reltuples
+ FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid
+ WHERE c.oid = 'tmp2'::regclass OR c.oid = 'tmp2_a_idx'::regclass ORDER BY 1;
+ oid | global_relpages | global_reltuples | local_relpages | local_reltuples
+------------+-----------------+------------------+----------------+-----------------
+ tmp2 | 0 | -1 | non-zero | 100
+ tmp2_a_idx | 0 | 0 | non-zero | 100
+(2 rows)
+
+-- Test stats usage
+CREATE FUNCTION row_estimate(query text) RETURNS int
+LANGUAGE plpgsql AS
+$$
+DECLARE
+ line text;
+BEGIN
+ FOR line IN EXECUTE FORMAT('EXPLAIN %s', query)
+ LOOP
+ RETURN (regexp_match(line, 'rows=(\d*)'))[1]::int;
+ END LOOP;
+END;
+$$;
+SELECT row_estimate('SELECT * FROM tmp2');
+ row_estimate
+--------------
+ 100
+(1 row)
+
+-- Test manually updating stats
+SELECT pg_clear_relation_stats('global_temp_tests', 'tmp2');
+ pg_clear_relation_stats
+-------------------------
+
+(1 row)
+
+SELECT oid::regclass, relpages, reltuples, relallvisible, relallfrozen
+ FROM pg_class WHERE oid = 'tmp2'::regclass;
+ oid | relpages | reltuples | relallvisible | relallfrozen
+------+----------+-----------+---------------+--------------
+ tmp2 | 0 | -1 | 0 | 0
+(1 row)
+
+SELECT oid::regclass, relpages, reltuples, relallvisible, relallfrozen
+ FROM pg_temp_class WHERE oid = 'tmp2'::regclass;
+ oid | relpages | reltuples | relallvisible | relallfrozen
+------+----------+-----------+---------------+--------------
+ tmp2 | 0 | -1 | 0 | 0
+(1 row)
+
+SELECT pg_restore_relation_stats(
+ 'schemaname', 'global_temp_tests',
+ 'relname', 'tmp2',
+ 'relpages', 5,
+ 'reltuples', 150::real,
+ 'relallvisible', 10,
+ 'relallfrozen', 20);
+ pg_restore_relation_stats
+---------------------------
+ t
+(1 row)
+
+SELECT oid::regclass, relpages, reltuples, relallvisible, relallfrozen
+ FROM pg_class WHERE oid = 'tmp2'::regclass;
+ oid | relpages | reltuples | relallvisible | relallfrozen
+------+----------+-----------+---------------+--------------
+ tmp2 | 0 | -1 | 0 | 0
+(1 row)
+
+SELECT oid::regclass, relpages, reltuples, relallvisible, relallfrozen
+ FROM pg_temp_class WHERE oid = 'tmp2'::regclass;
+ oid | relpages | reltuples | relallvisible | relallfrozen
+------+----------+-----------+---------------+--------------
+ tmp2 | 5 | 150 | 10 | 20
+(1 row)
+
+DROP TABLE tmp2;
-- Test view creation
CREATE VIEW v AS SELECT * FROM tmp1;
SELECT * FROM v;
diff --git a/src/test/regress/sql/global_temp.sql b/src/test/regress/sql/global_temp.sql
index a19b25b5bc6..921595862f2 100644
--- a/src/test/regress/sql/global_temp.sql
+++ b/src/test/regress/sql/global_temp.sql
@@ -285,6 +285,88 @@ COMMIT;
SELECT oid::regclass FROM pg_temp_class ORDER BY 1;
SELECT * FROM tmp1;
+-- Test stats updates applied by CREATE INDEX, ANALYZE, VACUUM, and REPACK
+CREATE GLOBAL TEMP TABLE tmp2 (a int);
+INSERT INTO tmp2 SELECT * FROM generate_series(1, 100);
+SELECT c.oid::regclass,
+ c.relpages AS global_relpages, c.reltuples AS global_reltuples,
+ CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages,
+ t.reltuples AS local_reltuples
+ FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid
+ WHERE c.oid = 'tmp2'::regclass;
+
+CREATE INDEX tmp2_a_idx ON tmp2(a);
+SELECT c.oid::regclass,
+ c.relpages AS global_relpages, c.reltuples AS global_reltuples,
+ CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages,
+ t.reltuples AS local_reltuples
+ FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid
+ WHERE c.oid = 'tmp2'::regclass OR c.oid = 'tmp2_a_idx'::regclass ORDER BY 1;
+
+INSERT INTO tmp2 SELECT * FROM generate_series(101, 300);
+ANALYZE tmp2;
+SELECT c.oid::regclass,
+ c.relpages AS global_relpages, c.reltuples AS global_reltuples,
+ CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages,
+ t.reltuples AS local_reltuples
+ FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid
+ WHERE c.oid = 'tmp2'::regclass OR c.oid = 'tmp2_a_idx'::regclass ORDER BY 1;
+
+DELETE FROM tmp2 WHERE a % 2 = 0;
+VACUUM ANALYZE tmp2;
+SELECT c.oid::regclass,
+ c.relpages AS global_relpages, c.reltuples AS global_reltuples,
+ CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages,
+ t.reltuples AS local_reltuples
+ FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid
+ WHERE c.oid = 'tmp2'::regclass OR c.oid = 'tmp2_a_idx'::regclass ORDER BY 1;
+
+DELETE FROM tmp2 WHERE a % 3 = 0;
+REPACK (ANALYZE) tmp2;
+SELECT c.oid::regclass,
+ c.relpages AS global_relpages, c.reltuples AS global_reltuples,
+ CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages,
+ t.reltuples AS local_reltuples
+ FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid
+ WHERE c.oid = 'tmp2'::regclass OR c.oid = 'tmp2_a_idx'::regclass ORDER BY 1;
+
+-- Test stats usage
+CREATE FUNCTION row_estimate(query text) RETURNS int
+LANGUAGE plpgsql AS
+$$
+DECLARE
+ line text;
+BEGIN
+ FOR line IN EXECUTE FORMAT('EXPLAIN %s', query)
+ LOOP
+ RETURN (regexp_match(line, 'rows=(\d*)'))[1]::int;
+ END LOOP;
+END;
+$$;
+
+SELECT row_estimate('SELECT * FROM tmp2');
+
+-- Test manually updating stats
+SELECT pg_clear_relation_stats('global_temp_tests', 'tmp2');
+SELECT oid::regclass, relpages, reltuples, relallvisible, relallfrozen
+ FROM pg_class WHERE oid = 'tmp2'::regclass;
+SELECT oid::regclass, relpages, reltuples, relallvisible, relallfrozen
+ FROM pg_temp_class WHERE oid = 'tmp2'::regclass;
+
+SELECT pg_restore_relation_stats(
+ 'schemaname', 'global_temp_tests',
+ 'relname', 'tmp2',
+ 'relpages', 5,
+ 'reltuples', 150::real,
+ 'relallvisible', 10,
+ 'relallfrozen', 20);
+SELECT oid::regclass, relpages, reltuples, relallvisible, relallfrozen
+ FROM pg_class WHERE oid = 'tmp2'::regclass;
+SELECT oid::regclass, relpages, reltuples, relallvisible, relallfrozen
+ FROM pg_temp_class WHERE oid = 'tmp2'::regclass;
+
+DROP TABLE tmp2;
+
-- Test view creation
CREATE VIEW v AS SELECT * FROM tmp1;
SELECT * FROM v;
--
2.51.0