From 4cb10b5762a11f8aba8f9bd6e0c253d153f4338f Mon Sep 17 00:00:00 2001 From: Dean Rasheed Date: Wed, 17 Jun 2026 02:04:59 +0100 Subject: [PATCH v6 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 | 82 ++++++++++ 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, 800 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 628c8aa7026..3f21080d82c 100644 --- a/src/backend/catalog/pg_temp_class.c +++ b/src/backend/catalog/pg_temp_class.c @@ -57,6 +57,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" @@ -143,6 +144,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); @@ -169,6 +182,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); } @@ -372,6 +389,71 @@ 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, update and flush it + * (along with all other pending inserts). We intentionally flush here in + * order to trigger cache invalidation for this relation. + */ + if (have_pending_inserts) + { + PendingInsert *entry; + Form_pg_temp_class old_form; + Form_pg_temp_class new_form; + + entry = hash_search(pending_inserts, &relid, HASH_FIND, NULL); + if (entry != NULL) + { + old_form = (Form_pg_temp_class) GETSTRUCT(entry->tuple); + new_form = (Form_pg_temp_class) GETSTRUCT(newtuple); + COPY_PG_TEMP_CLASS_ATTRS(new_form, old_form); + + flush_pending_pg_temp_class_inserts(); + return; + } + } + + /* + * Otherwise, update pg_temp_class directly, making note of the + * subtransaction ID, if this is the first time opening pg_temp_class. + */ + pg_temp_class = table_open(TempRelationRelationId, RowExclusiveLock); + if (!pg_temp_class_opened) + { + pg_temp_class_opened = true; + pg_temp_class_subid = GetCurrentSubTransactionId(); + } + + ScanKeyInit(&key[0], + Anum_pg_temp_class_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relid)); + + systable_inplace_update_begin(pg_temp_class, TempClassOidIndexId, true, + NULL, 1, key, &oldtuple, &inplace_state); + if (!HeapTupleIsValid(oldtuple)) + elog(ERROR, "cache lookup failed for global temp relation %u", relid); + + systable_inplace_update_finish(inplace_state, newtuple); + + heap_freetuple(oldtuple); + + table_close(pg_temp_class, RowExclusiveLock); +} + /* * DeletePgTempClassTuple * diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 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 5f9299d7352..67fda4d2ffb 100644 --- a/src/test/regress/expected/global_temp.out +++ b/src/test/regress/expected/global_temp.out @@ -433,6 +433,142 @@ SELECT relfilenode = pg_relation_filenode('tmp1'::regclass) AS ok \c SET search_path = global_temp_tests; VACUUM FULL tmp1; +-- Test stats updates applied by CREATE INDEX, ANALYZE, VACUUM, and REPACK +CREATE GLOBAL TEMP TABLE tmp2 (a int); +INSERT INTO tmp2 SELECT * FROM generate_series(1, 100); +SELECT c.oid::regclass, + c.relpages AS global_relpages, c.reltuples AS global_reltuples, + CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages, + t.reltuples AS local_reltuples + FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid + WHERE c.oid = 'tmp2'::regclass; + oid | global_relpages | global_reltuples | local_relpages | local_reltuples +------+-----------------+------------------+----------------+----------------- + tmp2 | 0 | -1 | zero | -1 +(1 row) + +CREATE INDEX tmp2_a_idx ON tmp2(a); +SELECT c.oid::regclass, + c.relpages AS global_relpages, c.reltuples AS global_reltuples, + CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages, + t.reltuples AS local_reltuples + FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid + WHERE c.oid = 'tmp2'::regclass OR c.oid = 'tmp2_a_idx'::regclass ORDER BY 1; + oid | global_relpages | global_reltuples | local_relpages | local_reltuples +------------+-----------------+------------------+----------------+----------------- + tmp2 | 0 | -1 | non-zero | 100 + tmp2_a_idx | 0 | 0 | non-zero | 100 +(2 rows) + +INSERT INTO tmp2 SELECT * FROM generate_series(101, 300); +ANALYZE tmp2; +SELECT c.oid::regclass, + c.relpages AS global_relpages, c.reltuples AS global_reltuples, + CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages, + t.reltuples AS local_reltuples + FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid + WHERE c.oid = 'tmp2'::regclass OR c.oid = 'tmp2_a_idx'::regclass ORDER BY 1; + oid | global_relpages | global_reltuples | local_relpages | local_reltuples +------------+-----------------+------------------+----------------+----------------- + tmp2 | 0 | -1 | non-zero | 300 + tmp2_a_idx | 0 | 0 | non-zero | 300 +(2 rows) + +DELETE FROM tmp2 WHERE a % 2 = 0; +VACUUM ANALYZE tmp2; +SELECT c.oid::regclass, + c.relpages AS global_relpages, c.reltuples AS global_reltuples, + CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages, + t.reltuples AS local_reltuples + FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid + WHERE c.oid = 'tmp2'::regclass OR c.oid = 'tmp2_a_idx'::regclass ORDER BY 1; + oid | global_relpages | global_reltuples | local_relpages | local_reltuples +------------+-----------------+------------------+----------------+----------------- + tmp2 | 0 | -1 | non-zero | 150 + tmp2_a_idx | 0 | 0 | non-zero | 150 +(2 rows) + +DELETE FROM tmp2 WHERE a % 3 = 0; +REPACK (ANALYZE) tmp2; +SELECT c.oid::regclass, + c.relpages AS global_relpages, c.reltuples AS global_reltuples, + CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages, + t.reltuples AS local_reltuples + FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid + WHERE c.oid = 'tmp2'::regclass OR c.oid = 'tmp2_a_idx'::regclass ORDER BY 1; + oid | global_relpages | global_reltuples | local_relpages | local_reltuples +------------+-----------------+------------------+----------------+----------------- + tmp2 | 0 | -1 | non-zero | 100 + tmp2_a_idx | 0 | 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 INSERT INTO tmp1 VALUES (1, 'xxx'); CREATE VIEW v AS SELECT * FROM tmp1; diff --git a/src/test/regress/sql/global_temp.sql b/src/test/regress/sql/global_temp.sql index 14ef4e7b4e2..ed262910aff 100644 --- a/src/test/regress/sql/global_temp.sql +++ b/src/test/regress/sql/global_temp.sql @@ -255,6 +255,88 @@ SELECT relfilenode = pg_relation_filenode('tmp1'::regclass) AS ok SET search_path = global_temp_tests; VACUUM FULL tmp1; +-- Test stats updates applied by CREATE INDEX, ANALYZE, VACUUM, and REPACK +CREATE GLOBAL TEMP TABLE tmp2 (a int); +INSERT INTO tmp2 SELECT * FROM generate_series(1, 100); +SELECT c.oid::regclass, + c.relpages AS global_relpages, c.reltuples AS global_reltuples, + CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages, + t.reltuples AS local_reltuples + FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid + WHERE c.oid = 'tmp2'::regclass; + +CREATE INDEX tmp2_a_idx ON tmp2(a); +SELECT c.oid::regclass, + c.relpages AS global_relpages, c.reltuples AS global_reltuples, + CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages, + t.reltuples AS local_reltuples + FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid + WHERE c.oid = 'tmp2'::regclass OR c.oid = 'tmp2_a_idx'::regclass ORDER BY 1; + +INSERT INTO tmp2 SELECT * FROM generate_series(101, 300); +ANALYZE tmp2; +SELECT c.oid::regclass, + c.relpages AS global_relpages, c.reltuples AS global_reltuples, + CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages, + t.reltuples AS local_reltuples + FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid + WHERE c.oid = 'tmp2'::regclass OR c.oid = 'tmp2_a_idx'::regclass ORDER BY 1; + +DELETE FROM tmp2 WHERE a % 2 = 0; +VACUUM ANALYZE tmp2; +SELECT c.oid::regclass, + c.relpages AS global_relpages, c.reltuples AS global_reltuples, + CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages, + t.reltuples AS local_reltuples + FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid + WHERE c.oid = 'tmp2'::regclass OR c.oid = 'tmp2_a_idx'::regclass ORDER BY 1; + +DELETE FROM tmp2 WHERE a % 3 = 0; +REPACK (ANALYZE) tmp2; +SELECT c.oid::regclass, + c.relpages AS global_relpages, c.reltuples AS global_reltuples, + CASE WHEN t.relpages = 0 THEN 'zero' ELSE 'non-zero' END AS local_relpages, + t.reltuples AS local_reltuples + FROM pg_class c JOIN pg_temp_class t ON t.oid = c.oid + WHERE c.oid = 'tmp2'::regclass OR c.oid = 'tmp2_a_idx'::regclass ORDER BY 1; + +-- Test stats usage +CREATE FUNCTION row_estimate(query text) RETURNS int +LANGUAGE plpgsql AS +$$ +DECLARE + line text; +BEGIN + FOR line IN EXECUTE FORMAT('EXPLAIN %s', query) + LOOP + RETURN (regexp_match(line, 'rows=(\d*)'))[1]::int; + END LOOP; +END; +$$; + +SELECT row_estimate('SELECT * FROM tmp2'); + +-- Test manually updating stats +SELECT pg_clear_relation_stats('global_temp_tests', 'tmp2'); +SELECT oid::regclass, relpages, reltuples, relallvisible, relallfrozen + FROM pg_class WHERE oid = 'tmp2'::regclass; +SELECT oid::regclass, relpages, reltuples, relallvisible, relallfrozen + FROM pg_temp_class WHERE oid = 'tmp2'::regclass; + +SELECT pg_restore_relation_stats( + 'schemaname', 'global_temp_tests', + 'relname', 'tmp2', + 'relpages', 5, + 'reltuples', 150::real, + 'relallvisible', 10, + 'relallfrozen', 20); +SELECT oid::regclass, relpages, reltuples, relallvisible, relallfrozen + FROM pg_class WHERE oid = 'tmp2'::regclass; +SELECT oid::regclass, relpages, reltuples, relallvisible, relallfrozen + FROM pg_temp_class WHERE oid = 'tmp2'::regclass; + +DROP TABLE tmp2; + -- Test view creation INSERT INTO tmp1 VALUES (1, 'xxx'); CREATE VIEW v AS SELECT * FROM tmp1; -- 2.51.0