From 27a804cb790ba2842aa573ea8c91cc613e7af842 Mon Sep 17 00:00:00 2001 From: Haibo Yan Date: Tue, 8 Sep 2026 14:53:09 -0700 Subject: [PATCH 1/2] Remove pg_temp_class in favor of backend-local GTT state Global temporary tables have a shared definition (a single pg_class row, visible to every backend) but per-backend physical storage, so several pg_class fields -- relpages, reltuples, relallvisible, relallfrozen, relfrozenxid, relminmxid, reltablespace, relfilenode -- need a per-backend value instead of a single shared one. Up to now this has been done by adding pg_temp_class, a catalog table holding one row per (relation, backend) pair with the backend-local values, overlaid onto the shared pg_class row wherever it's read. Maintaining pg_temp_class as a real catalog requires a fair amount of supporting machinery: its own indexes, its own bootstrap/genbki handling, and careful handling of the fact that a backend's own writes to it must be visible to that backend's later catalog scans without being visible to any other backend. gtr_local_usage (the existing backend-local hash table that already records which global temporary relations are in use in the current backend, keyed by relid) has exactly the same lifetime as the corresponding pg_temp_class row would: both begin when the relation is first created or opened in the backend, and both end when the relation is dropped, the backend forgets about it, or the backend exits. Given that, there's no need for a second object with its own catalog-like machinery; the backend-local physical/statistics/freeze state can just be additional fields on the existing GtrUsageEntry. This removes pg_temp_class and adds that state directly to GtrUsageEntry, together with GetGlobalTempRelPhysState() and two ways to update it: SetGlobalTempRelPhysState(), for DDL-driven changes that must roll back with their (sub)transaction (e.g. ALTER TABLE ... SET TABLESPACE), and SetGlobalTempRelPhysStateInPlace(), for VACUUM/ANALYZE style updates that survive a rollback, matching the existing heap_inplace_update() semantics used for the same fields on an ordinary relation. ScanPgRelation() overlays this state onto the copied pg_class tuple before building the relcache entry, exactly where it previously overlaid the pg_temp_class row, so no other code needs to know the catalog is gone. A pg_gtt_relation_state() SQL function is added to expose the current backend's local physical/statistics/freeze state for global temporary relations with local storage. --- src/backend/access/heap/heapam.c | 4 - src/backend/access/heap/vacuumlazy.c | 6 +- src/backend/catalog/Makefile | 1 - src/backend/catalog/catalog.c | 2 - src/backend/catalog/genbki.pl | 1 - src/backend/catalog/global_temp.c | 484 ++++++++++++++++-- src/backend/catalog/heap.c | 5 +- src/backend/catalog/index.c | 129 +++-- src/backend/catalog/meson.build | 1 - src/backend/catalog/pg_temp_class.c | 270 ---------- src/backend/commands/analyze.c | 7 +- src/backend/commands/repack.c | 229 ++++----- src/backend/commands/tablecmds.c | 53 +- src/backend/commands/vacuum.c | 175 ++++--- src/backend/parser/parse_utilcmd.c | 1 - src/backend/statistics/relation_stats.c | 95 ++-- src/backend/utils/adt/dbsize.c | 1 - src/backend/utils/cache/gtcatcache.c | 73 +-- src/backend/utils/cache/inval.c | 12 +- src/backend/utils/cache/lsyscache.c | 12 +- src/backend/utils/cache/relcache.c | 146 ++++-- src/include/catalog/Makefile | 1 - src/include/catalog/global_temp.h | 27 + src/include/catalog/meson.build | 1 - src/include/catalog/pg_proc.dat | 8 + src/include/catalog/pg_temp_class.h | 381 -------------- src/include/commands/vacuum.h | 3 +- src/include/utils/gtcatcache.h | 3 - src/include/utils/relcache.h | 8 + src/test/isolation/expected/global-temp.out | 26 +- .../isolation/expected/vacuum-global-temp.out | 4 +- src/test/isolation/specs/global-temp.spec | 6 +- .../isolation/specs/vacuum-global-temp.spec | 4 +- src/test/regress/expected/global_temp.out | 135 +++-- src/test/regress/expected/oidjoins.out | 2 - src/test/regress/sql/global_temp.sql | 91 ++-- src/tools/pgindent/typedefs.list | 4 +- 37 files changed, 1096 insertions(+), 1315 deletions(-) delete mode 100644 src/backend/catalog/pg_temp_class.c delete mode 100644 src/include/catalog/pg_temp_class.h diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index ef4569d24a0..72d6541734c 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -42,7 +42,6 @@ #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" @@ -4396,9 +4395,6 @@ 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/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index 08de985f21c..8a0ff4d4bdb 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -918,7 +918,8 @@ heap_vacuum_rel(Relation rel, const VacuumParams *params, PROGRESS_VACUUM_PHASE_FINAL_CLEANUP); /* - * Prepare to update rel's pg_class and/or pg_temp_class entries. + * Prepare to update rel's pg_class entry (or, for a global temporary + * relation, its backend-local physical state). * * Aggressive VACUUMs must always be able to advance relfrozenxid to a * value >= FreezeLimit, and relminmxid to a value >= MultiXactCutoff. @@ -962,7 +963,8 @@ heap_vacuum_rel(Relation rel, const VacuumParams *params, new_rel_allfrozen = new_rel_allvisible; /* - * Now actually update rel's pg_class and/or pg_temp_class entries. + * Now actually update rel's pg_class entry (or backend-local physical + * state). * * In principle new_live_tuples could be -1 indicating that we (still) * don't know the tuple count. In practice that can't happen, since we diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index e9497eb202d..31bb94a4b7e 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -46,7 +46,6 @@ OBJS = \ pg_shdepend.o \ pg_subscription.o \ pg_tablespace.o \ - pg_temp_class.o \ pg_temp_index.o \ pg_type.o \ storage.o \ diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c index eb5d849c067..d99bb724b83 100644 --- a/src/backend/catalog/catalog.c +++ b/src/backend/catalog/catalog.c @@ -40,7 +40,6 @@ #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" @@ -196,7 +195,6 @@ bool IsInplaceUpdateOid(Oid relid) { return (relid == RelationRelationId || - relid == TempRelationRelationId || relid == DatabaseRelationId); } diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl index 9a8ce3d2c00..8c578dd9bba 100644 --- a/src/backend/catalog/genbki.pl +++ b/src/backend/catalog/genbki.pl @@ -800,7 +800,6 @@ print_boilerplate($syscache_ids_fh, "syscache_ids.h", "SysCache identifiers"); print $syscache_ids_fh "#ifndef SYSCACHE_IDS_H #define SYSCACHE_IDS_H -#include \"catalog/pg_temp_class_d.h\" #include \"catalog/pg_temp_index_d.h\" #include \"catalog/pg_temp_statistic_d.h\" #include \"catalog/pg_temp_statistic_ext_data_d.h\" diff --git a/src/backend/catalog/global_temp.c b/src/backend/catalog/global_temp.c index eb934e48363..73a2c6d041c 100644 --- a/src/backend/catalog/global_temp.c +++ b/src/backend/catalog/global_temp.c @@ -62,11 +62,11 @@ #include "access/xlogutils.h" #include "catalog/global_temp.h" #include "catalog/indexing.h" -#include "catalog/pg_temp_class.h" #include "catalog/pg_temp_index.h" #include "catalog/storage.h" #include "commands/sequence.h" #include "commands/tablecmds.h" +#include "funcapi.h" #include "lib/dshash.h" #include "miscadmin.h" #include "storage/ipc.h" @@ -76,6 +76,7 @@ #include "storage/subsystems.h" #include "utils/fmgroids.h" #include "utils/gtcatcache.h" +#include "utils/inval.h" #include "utils/memutils.h" #include "utils/syscache.h" #include "utils/tuplestore.h" @@ -117,6 +118,27 @@ static bool eoxact_storage_list_overflowed = false; eoxact_storage_list_overflowed = true; \ } while (0) +/* + * GtrRelPhysState is declared in global_temp.h, since it's part of the + * public API (GetGlobalTempRelPhysState() et al.) --- see the comment there. + */ + +/* + * GtrRelPhysStateHistory + * + * A saved previous version of a GtrRelPhysState, kept so that a + * transactional change (see SetGlobalTempRelPhysState()) can be undone by + * (sub)transaction rollback. This is the same technique used for + * GTCatCacheEntry's "prev" chain, minus everything related to catalog + * flushing, since this state is purely in-memory. + */ +typedef struct GtrRelPhysStateHistory +{ + GtrRelPhysState state; + SubTransactionId subid; + struct GtrRelPhysStateHistory *prev; +} GtrRelPhysStateHistory; + /* * gtr_local_usage * @@ -128,6 +150,19 @@ typedef struct GtrUsageEntry char relkind; /* relkind of the relation */ SubTransactionId started_subid; /* usage started in current xact */ SubTransactionId stopped_subid; /* usage ended with another subid set */ + + /* + * Physical/statistics/freeze state, valid for relkinds with storage. + * physsubid records the subtransaction of the most recent *transactional* + * update made via SetGlobalTempRelPhysState(); physprev holds saved + * previous versions for rollback. Updates made via + * SetGlobalTempRelPhysStateInPlace() bypass this entirely and are never + * rolled back --- this matches the way VACUUM/ANALYZE update a permanent + * relation's pg_class row non-transactionally. + */ + GtrRelPhysState phys; + SubTransactionId physsubid; + GtrRelPhysStateHistory *physprev; } GtrUsageEntry; static HTAB *gtr_local_usage; @@ -553,12 +588,14 @@ gtr_init_usage_tables(void) * gtr_record_usage * * Record the fact that we're using a global temporary relation by adding - * entries to the local and shared usage hash tables. + * entries to the local and shared usage hash tables. Returns the local + * usage entry, whether or not it was newly created, so that the caller can + * initialize (or verify) its physical/statistics/freeze state. * - * Note: This is intentionally idempotent --- it does nothing if we already - * have usage records for this relation. + * Note: This is intentionally idempotent --- it does nothing but return the + * existing entry if we already have usage records for this relation. */ -static void +static GtrUsageEntry * gtr_record_usage(Oid relid, char relkind) { GtrUsageEntry *local_entry; @@ -572,7 +609,7 @@ gtr_record_usage(Oid relid, char relkind) /* Add local usage entry, if not already there */ local_entry = hash_search(gtr_local_usage, &relid, HASH_ENTER, &found); if (found) - return; /* already recorded, nothing to do */ + return local_entry; /* already recorded, nothing to do */ /* * For a sequence, the storage is created non-transactionally, and isn't @@ -591,6 +628,10 @@ gtr_record_usage(Oid relid, char relkind) } local_entry->stopped_subid = InvalidSubTransactionId; + /* No physical/statistics/freeze state recorded yet */ + local_entry->physsubid = InvalidSubTransactionId; + local_entry->physprev = NULL; + /* Remember the relation's relkind */ local_entry->relkind = relkind; @@ -616,6 +657,8 @@ gtr_record_usage(Oid relid, char relkind) shared_entry->usage_count = 1; dshash_release_lock(gtr_shared_usage, shared_entry); + + return local_entry; } /* @@ -630,16 +673,41 @@ gtr_record_usage(Oid relid, char relkind) static void gtr_remove_usage(Oid relid) { + GtrUsageEntry *entry; GtrSharedUsageKey key; GtrSharedUsageEntry *shared_entry; /* Initialize the usage tables, if necessary */ gtr_init_usage_tables(); - /* Remove local usage entry */ - if (!hash_search(gtr_local_usage, &relid, HASH_REMOVE, NULL)) + /* Find the local usage entry */ + entry = hash_search(gtr_local_usage, &relid, HASH_FIND, NULL); + if (entry == NULL) return; /* nothing to do */ + /* + * Discard any outstanding transactional history for the entry. In the + * usual case (removal via + * AtEOXact_UsageCleanup()/AtEOSubXact_UsageCleanup(), after + * AtEOXact_RelPhysCleanup() has already run for the same entry) this is + * already empty, but a global temporary relation can also be removed + * directly, without going through that per-subxact unwinding, if it is + * dropped by another backend (see + * ProcessInvalidatedGlobalTempRelations()) or via DISCARD GLOBAL TEMP + * (see gtr_finalize_discard()), so any remaining history must be freed + * here rather than assumed away. + */ + while (entry->physprev != NULL) + { + GtrRelPhysStateHistory *prev = entry->physprev; + + entry->physprev = prev->prev; + pfree(prev); + } + + /* Remove local usage entry */ + hash_search(gtr_local_usage, &relid, HASH_REMOVE, NULL); + /* Update/delete shared usage entry */ key.dbid = MyDatabaseId; key.relid = relid; @@ -711,7 +779,7 @@ gtr_finalize_discard(void) RelationMarkInvalid(usage_entry->relid); } - /* Discard all cached pg_temp_class and pg_temp_index tuples */ + /* Discard all cached pg_temp_index tuples */ GTCatCacheDiscard(); /* Reset tempfrozenxid and tempminmxid for this backend */ @@ -719,6 +787,12 @@ gtr_finalize_discard(void) MyProc->tempminmxid = InvalidMultiXactId; } +/* forward declarations, since these are needed before their definitions */ +static void AtEOXact_RelPhysCleanup(GtrUsageEntry *entry, bool isCommit); +static void AtEOSubXact_RelPhysCleanup(GtrUsageEntry *entry, bool isCommit, + SubTransactionId mySubid, + SubTransactionId parentSubid); + /* * AtEOXact_UsageCleanup * @@ -731,6 +805,9 @@ gtr_finalize_discard(void) static void AtEOXact_UsageCleanup(GtrUsageEntry *entry, bool isCommit) { + /* Clean up any transactional physical-state history first */ + AtEOXact_RelPhysCleanup(entry, isCommit); + /* * If the relation is no longer in use after this transaction ends, remove * the usage hash table entries for it. Otherwise, reset the hash entry's @@ -762,6 +839,9 @@ AtEOSubXact_UsageCleanup(GtrUsageEntry *entry, bool isCommit, SubTransactionId mySubid, SubTransactionId parentSubid) { + /* Clean up any transactional physical-state history first */ + AtEOSubXact_RelPhysCleanup(entry, isCommit, mySubid, parentSubid); + /* * Did usage start in the current subtransaction? * @@ -793,6 +873,272 @@ AtEOSubXact_UsageCleanup(GtrUsageEntry *entry, bool isCommit, } } +/* + * AtEOXact_RelPhysCleanup + * + * Clean up the physical/statistics/freeze state for a single global + * temporary relation at main-transaction commit or abort. This is + * independent of, and always performed in addition to, + * AtEOXact_UsageCleanup() for the same entry. + * + * NB: this processing must be idempotent, because EOXactUsageListAdd() + * doesn't bother to prevent duplicate entries in eoxact_usage_list[]. + */ +static void +AtEOXact_RelPhysCleanup(GtrUsageEntry *entry, bool isCommit) +{ + GtrRelPhysStateHistory *prev = entry->physprev; + + if (entry->physsubid == InvalidSubTransactionId) + return; + + Assert(prev != NULL); + + if (isCommit) + { + entry->physsubid = InvalidSubTransactionId; + entry->physprev = NULL; + pfree(prev); + } + else + { + /* Rollback: restore the state as it was before this transaction */ + entry->phys = prev->state; + entry->physsubid = prev->subid; + entry->physprev = prev->prev; + pfree(prev); + } +} + +/* + * AtEOSubXact_RelPhysCleanup + * + * Clean up the physical/statistics/freeze state for a single global + * temporary relation at sub-transaction commit or abort. This is + * independent of, and always performed in addition to, + * AtEOSubXact_UsageCleanup() for the same entry. + * + * NB: this processing must be idempotent, because EOXactUsageListAdd() + * doesn't bother to prevent duplicate entries in eoxact_usage_list[]. + */ +static void +AtEOSubXact_RelPhysCleanup(GtrUsageEntry *entry, bool isCommit, + SubTransactionId mySubid, + SubTransactionId parentSubid) +{ + GtrRelPhysStateHistory *prev; + + if (entry->physsubid != mySubid) + return; + + prev = entry->physprev; + Assert(prev != NULL); + + if (isCommit) + { + /* Reparent this change to the parent subtransaction */ + entry->physsubid = parentSubid; + if (prev->subid == parentSubid) + { + /* Parent already has its own checkpoint; merge into it */ + entry->physprev = prev->prev; + pfree(prev); + } + } + else + { + /* Subrollback: restore the state as it was before this subxact */ + entry->phys = prev->state; + entry->physsubid = prev->subid; + entry->physprev = prev->prev; + pfree(prev); + } +} + +/* + * GtrInitRelPhysState + * + * Initialize the physical/statistics/freeze state for a global temporary + * relation from the given relation's pg_class data. Called when a usage + * record is first created for the relation (see TrackGlobalTempRelation()), + * regardless of which backend originally created the relation. + * + * This initial value is not itself subject to (sub)transaction rollback via + * physsubid/physprev --- if the usage record's own creation is rolled back, + * the whole entry (including this initial state) disappears with it. + */ +static void +GtrInitRelPhysState(GtrUsageEntry *entry, Form_pg_class classform) +{ + entry->phys.relfilenode = classform->relfilenode; + entry->phys.reltablespace = classform->reltablespace; + entry->phys.relpages = classform->relpages; + entry->phys.reltuples = classform->reltuples; + entry->phys.relallvisible = classform->relallvisible; + entry->phys.relallfrozen = classform->relallfrozen; + entry->phys.relfrozenxid = classform->relfrozenxid; + entry->phys.relminmxid = classform->relminmxid; + entry->physsubid = InvalidSubTransactionId; + entry->physprev = NULL; +} + +/* + * GetGlobalTempRelPhysState + * + * Get the current physical/statistics/freeze state for a global temporary + * relation in use by this backend. Returns false if the relation has no + * local usage record (e.g. it's not a global temporary relation, or this + * backend hasn't used it yet). + */ +bool +GetGlobalTempRelPhysState(Oid relid, GtrRelPhysState *state) +{ + GtrUsageEntry *entry; + + if (gtr_local_usage == NULL) + return false; + + entry = hash_search(gtr_local_usage, &relid, HASH_FIND, NULL); + if (entry == NULL) + return false; + + *state = entry->phys; + return true; +} + +/* + * SetGlobalTempRelPhysState + * + * Transactionally update the physical/statistics/freeze state for a global + * temporary relation already in use by this backend. The change is undone + * by (sub)transaction rollback. Used for changes such as assigning a new + * relfilenode (TRUNCATE, CLUSTER, REPACK, ALTER TABLE SET TABLESPACE) or + * explicit statistics changes (pg_set_relation_stats()). + */ +void +SetGlobalTempRelPhysState(Oid relid, const GtrRelPhysState *state) +{ + GtrUsageEntry *entry; + SubTransactionId mySubid = GetCurrentSubTransactionId(); + + entry = hash_search(gtr_local_usage, &relid, HASH_FIND, NULL); + if (entry == NULL) + elog(ERROR, "no local state for global temporary relation %u", relid); + + if (entry->physsubid != mySubid) + { + GtrRelPhysStateHistory *hist; + MemoryContext oldcontext = MemoryContextSwitchTo(TopMemoryContext); + + hist = palloc_object(GtrRelPhysStateHistory); + hist->state = entry->phys; + hist->subid = entry->physsubid; + hist->prev = entry->physprev; + + entry->physprev = hist; + entry->physsubid = mySubid; + + MemoryContextSwitchTo(oldcontext); + + EOXactUsageListAdd(relid); + } + + entry->phys = *state; + + /* + * Force this backend's relcache entry for the relation (if any) to be + * rebuilt, so that it picks up the new state. This is transactional, + * matching the way a regular CatalogTupleUpdate() of pg_class would queue + * a relcache invalidation. + */ + CacheInvalidateRelcacheByRelid(relid); +} + +/* + * SetGlobalTempRelPhysStateInPlace + * + * Non-transactionally update the physical/statistics/freeze state for a + * global temporary relation already in use by this backend. The change is + * *not* undone by (sub)transaction rollback, matching the way VACUUM and + * ANALYZE update a permanent relation's pg_class row via a non-transactional + * in-place update. Used for routine relpages/reltuples/relallvisible/ + * relallfrozen/relfrozenxid/relminmxid maintenance by VACUUM, ANALYZE, and + * index build/insertion statistics. + */ +void +SetGlobalTempRelPhysStateInPlace(Oid relid, const GtrRelPhysState *state) +{ + GtrUsageEntry *entry; + + entry = hash_search(gtr_local_usage, &relid, HASH_FIND, NULL); + if (entry == NULL) + elog(ERROR, "no local state for global temporary relation %u", relid); + + entry->phys = *state; + + /* + * Force this backend's relcache entry for the relation (if any) to be + * rebuilt, so that it picks up the new state. Unlike + * SetGlobalTempRelPhysState(), this isn't tied to the transactional + * change made above (there is none --- the change above is immediate and + * not rolled back), but a plain transactional relcache invalidation is + * sufficient here: nothing else caches a copy of this backend-local state + * that could go stale before the next CCI or commit, unlike the + * heap-tuple-based in-place updates this replaces. + */ + CacheInvalidateRelcacheByRelid(relid); +} + +/* + * GetGlobalTempMinFrozenXids + * + * Get the minimum relfrozenxid and relminmxid values across all global + * temporary relations with recorded physical state in this session. If + * there are none, Invalid*Ids are returned. + */ +void +GetGlobalTempMinFrozenXids(TransactionId *min_relfrozenxid, + MultiXactId *min_relminmxid) +{ + HASH_SEQ_STATUS status; + GtrUsageEntry *entry; + + *min_relfrozenxid = InvalidTransactionId; + *min_relminmxid = InvalidMultiXactId; + + if (gtr_local_usage == NULL) + return; + + hash_seq_init(&status, gtr_local_usage); + while ((entry = hash_seq_search(&status)) != NULL) + { + TransactionId relfrozenxid; + MultiXactId relminmxid; + + if (entry->stopped_subid != InvalidSubTransactionId || + !RELKIND_HAS_STORAGE(entry->relkind)) + continue; + + relfrozenxid = entry->phys.relfrozenxid; + relminmxid = entry->phys.relminmxid; + + /* Ignore relations that don't hold unfrozen XIDs */ + if (!TransactionIdIsValid(relfrozenxid) || + !MultiXactIdIsValid(relminmxid)) + continue; + + Assert(TransactionIdIsNormal(relfrozenxid)); + + if (!TransactionIdIsValid(*min_relfrozenxid) || + TransactionIdPrecedes(relfrozenxid, *min_relfrozenxid)) + *min_relfrozenxid = relfrozenxid; + + if (!MultiXactIdIsValid(*min_relminmxid) || + MultiXactIdPrecedes(relminmxid, *min_relminmxid)) + *min_relminmxid = relminmxid; + } +} + /* * TrackGlobalTempRelationStorage * @@ -1026,17 +1372,27 @@ void TrackGlobalTempRelation(Relation relation) { /* - * Record our use of the relation and insert a pg_temp_class tuple for it. - * We arrange things so that the presence of a usage record implies the - * presence of a pg_temp_class tuple and vice versa, so it's sufficient to - * do just one hash table lookup. + * Record our use of the relation and initialize its local + * physical/statistics/freeze state. We arrange things so that the + * presence of a usage record implies the presence of local index validity + * state (for an index), so it's sufficient to do just one hash table + * lookup. */ if (gtr_local_usage == NULL || hash_search(gtr_local_usage, &relation->rd_id, HASH_FIND, NULL) == NULL) { - gtr_record_usage(relation->rd_id, relation->rd_rel->relkind); - InsertPgTempClassTuple(relation); + GtrUsageEntry *entry; + + entry = gtr_record_usage(relation->rd_id, relation->rd_rel->relkind); + + /* + * Initialize the relation's local physical/statistics/freeze state + * unconditionally, even for relkinds without storage (e.g. a + * partitioned table), since reltablespace is still meaningful (as the + * default for future partitions) and must not be left uninitialized. + */ + GtrInitRelPhysState(entry, relation->rd_rel); /* For an index, also insert a pg_temp_index tuple */ if (relation->rd_rel->relkind == RELKIND_INDEX || @@ -1067,7 +1423,7 @@ TrackGlobalTempRelation(Relation relation) !MultiXactIdIsValid(MyProc->tempminmxid)) UpdateTempFrozenXids(); } - Assert(PgTempClassTupleExists(relation->rd_id)); + Assert(IsGlobalTempRelationInUse(relation->rd_id)); } /* @@ -1092,9 +1448,7 @@ ForgetGlobalTempRelation(Oid relid) entry->stopped_subid = GetCurrentSubTransactionId(); EOXactUsageListAdd(relid); - /* Delete its pg_temp_class and pg_temp_index tuples */ - DeletePgTempClassTuple(relid); - + /* Delete its pg_temp_index tuple, if it has one */ if (entry->relkind == RELKIND_INDEX || entry->relkind == RELKIND_PARTITIONED_INDEX) DeletePgTempIndexTuple(relid); @@ -1286,13 +1640,6 @@ ProcessInvalidatedGlobalTempRelations(void) gtr_remove_usage(relid); remove_on_commit_action(relid); - /* Delete the relation's pg_temp_class tuple, if it has one */ - if (PgTempClassTupleExists(relid)) - { - DeletePgTempClassTuple(relid); - tuples_deleted = true; - } - /* For an index, delete its pg_temp_index tuple, if it has one */ if (PgTempIndexTupleExists(relid)) { @@ -1371,7 +1718,8 @@ ProcessInvalidatedGlobalTempRelations(void) * UpdateTempFrozenXids * * Update this backend's tempfrozenxid and tempminmxid values, setting them - * to the minimum relfrozenxid and relminmxid values from pg_temp_class. + * to the minimum relfrozenxid and relminmxid values across all global + * temporary relations with recorded physical state in this session. * * Note: the updates are deferred until main transaction commit. This is * necessary, in case some or all of the changes made in this transaction are @@ -1498,15 +1846,15 @@ AtEOXact_GlobalTempRelation(bool isCommit) /* * Finally, on commit, update tempfrozenxid and tempminmxid, if requested. - * This must be done after AtEOXact_GTCatCache(), so that it sees the - * final state of pg_temp_class. + * This must be done after AtEOXact_UsageCleanup() has run for every entry + * (above), so that it sees the final state. */ if (update_tempfrozenxids && isCommit) { TransactionId min_relfrozenxid; MultiXactId min_relminmxid; - GTCatCacheGetMinFrozenXids(&min_relfrozenxid, &min_relminmxid); + GetGlobalTempMinFrozenXids(&min_relfrozenxid, &min_relminmxid); MyProc->tempfrozenxid = min_relfrozenxid; MyProc->tempminmxid = min_relminmxid; @@ -1712,8 +2060,7 @@ DiscardGlobalTempRelations(void) Oid relid = entry->relid; Relation rel; RelFileNumber newrelfilenumber; - HeapTuple tuple; - Form_pg_temp_class form; + GtrRelPhysState newstate; /* Skip system catalogs */ if (IsCatalogRelationOid(relid)) @@ -1744,21 +2091,15 @@ DiscardGlobalTempRelations(void) /* Forget its ON COMMIT action */ remove_on_commit_action(relid); - /* Update its pg_temp_class entry */ - tuple = GetPgTempClassTuple(relid); - if (!HeapTupleIsValid(tuple)) - elog(ERROR, "could not find tuple for relation %u", relid); - - form = (Form_pg_temp_class) GETSTRUCT(tuple); - form->relfilenode = newrelfilenumber; - form->relpages = 0; - form->reltuples = 0; - form->relallvisible = 0; - form->relallfrozen = 0; - - UpdatePgTempClassTuple(relid, tuple); + /* Update its local physical state */ + newstate = entry->phys; + newstate.relfilenode = newrelfilenumber; + newstate.relpages = 0; + newstate.reltuples = 0; + newstate.relallvisible = 0; + newstate.relallfrozen = 0; - heap_freetuple(tuple); + SetGlobalTempRelPhysState(relid, &newstate); relation_close(rel, NoLock); @@ -1766,8 +2107,9 @@ DiscardGlobalTempRelations(void) } /* - * Make the pg_temp_class changes visible. This will cause the - * relcache entries to get updated, too. + * Advance the command counter, in case anything else in this + * transaction depends on the storage-drop bookkeeping done above + * becoming visible to later commands. */ CommandCounterIncrement(); @@ -1789,3 +2131,49 @@ DiscardGlobalTempRelations(void) discard_subid = GetCurrentSubTransactionId(); } } + +/* + * pg_gtt_relation_state + * + * Expose this backend's local physical/statistics/freeze state for + * global temporary relations with local storage currently in use in + * this session. It never exposes another backend's state. + */ +PG_FUNCTION_INFO_V1(pg_gtt_relation_state); +Datum +pg_gtt_relation_state(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + HASH_SEQ_STATUS status; + GtrUsageEntry *entry; + + InitMaterializedSRF(fcinfo, 0); + + if (gtr_local_usage == NULL) + return (Datum) 0; + + hash_seq_init(&status, gtr_local_usage); + while ((entry = hash_seq_search(&status)) != NULL) + { + Datum values[9] = {0}; + bool nulls[9] = {0}; + + if (entry->stopped_subid != InvalidSubTransactionId || + !RELKIND_HAS_STORAGE(entry->relkind)) + continue; + + values[0] = ObjectIdGetDatum(entry->relid); + values[1] = ObjectIdGetDatum(entry->phys.relfilenode); + values[2] = ObjectIdGetDatum(entry->phys.reltablespace); + values[3] = Int32GetDatum(entry->phys.relpages); + values[4] = Float4GetDatum(entry->phys.reltuples); + values[5] = Int32GetDatum(entry->phys.relallvisible); + values[6] = Int32GetDatum(entry->phys.relallfrozen); + values[7] = TransactionIdGetDatum(entry->phys.relfrozenxid); + values[8] = TransactionIdGetDatum(entry->phys.relminmxid); + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + } + + return (Datum) 0; +} diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 2d24b506e4e..19aa13d5591 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -982,8 +982,9 @@ InsertPgClassTuple(Relation pg_class_desc, values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite); /* - * For global temporary relations, relfrozenxid and relminmxid are stored - * in pg_temp_class. Set them to Invalid in the pg_class tuple. + * For global temporary relations, relfrozenxid and relminmxid are + * backend-local (see global_temp.c). Set them to Invalid in the shared + * pg_class tuple. */ values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index e62b3218a43..9c02e014206 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -37,6 +37,7 @@ #include "catalog/binary_upgrade.h" #include "catalog/catalog.h" #include "catalog/dependency.h" +#include "catalog/global_temp.h" #include "catalog/heap.h" #include "catalog/index.h" #include "catalog/objectaccess.h" @@ -49,7 +50,6 @@ #include "catalog/pg_opclass.h" #include "catalog/pg_operator.h" #include "catalog/pg_tablespace.h" -#include "catalog/pg_temp_class.h" #include "catalog/pg_temp_index.h" #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" @@ -2863,7 +2863,7 @@ FormIndexDatum(IndexInfo *indexInfo, * 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). + * its backend-local physical state to be updated). * * isreindex: recreated a previously-existing index * hasindex: set relhasindex to this value @@ -2873,16 +2873,18 @@ FormIndexDatum(IndexInfo *indexInfo, * updated (using RelationGetNumberOfBlocks() and visibilitymap_count()). * * For a new index on 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. + * pg_class and all the other fields are set in the relation's backend-local + * physical state. 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 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. + * if we find that no change is needed in the pg_class row (or the backend- + * local physical state). 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, @@ -2898,12 +2900,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 is_gtt = RELATION_IS_GLOBAL_TEMP(rel); + GtrRelPhysState gtt_state; bool dirty; - bool temp_dirty; + bool gtt_dirty = false; /* * As a special hack, if we are dealing with an empty table and the @@ -2988,37 +2990,31 @@ index_update_stats(Relation rel, * 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(). + * If not for (1) above, the relation's backend-local physical state could + * be updated normally, and in fact we could work round (1) by simply not + * updating it 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(). */ /* - * For a global temporary relation, need a copy of its pg_temp_class - * tuple. + * For a global temporary relation, need its local physical/statistics + * state. */ - if (RELATION_IS_GLOBAL_TEMP(rel)) + if (is_gtt) { - 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; + if (!GetGlobalTempRelPhysState(relid, >t_state)) + elog(ERROR, "no local state for global temp relation %u", relid); } /* * 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. + * pg_class.relhasindex, and all other fields go in the relation's local + * state, 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)) + if (isreindex && is_gtt) { pg_class = NULL; tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid)); @@ -3046,7 +3042,6 @@ index_update_stats(Relation rel, /* Apply required updates, if any, to copied tuple(s) */ dirty = false; - temp_dirty = false; if (RelationIsValid(pg_class) && rd_rel->relhasindex != hasindex) { rd_rel->relhasindex = hasindex; @@ -3055,14 +3050,40 @@ index_update_stats(Relation rel, if (update_stats) { - 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 (is_gtt) + { + gtt_dirty |= (gtt_state.relpages != (int32) relpages); + gtt_state.relpages = (int32) relpages; + gtt_dirty |= (gtt_state.reltuples != (float4) reltuples); + gtt_state.reltuples = (float4) reltuples; + gtt_dirty |= (gtt_state.relallvisible != (int32) relallvisible); + gtt_state.relallvisible = (int32) relallvisible; + gtt_dirty |= (gtt_state.relallfrozen != (int32) relallfrozen); + gtt_state.relallfrozen = (int32) relallfrozen; + } + else + { + 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; + } + } } /* @@ -3088,13 +3109,8 @@ index_update_stats(Relation rel, CacheInvalidateRelcacheByTuple(tuple); } - if (HeapTupleIsValid(temp_tuple)) - { - if (temp_dirty) - UpdatePgTempClassTupleInPlace(relid, temp_tuple); - - heap_freetuple(temp_tuple); - } + if (gtt_dirty) + SetGlobalTempRelPhysStateInPlace(relid, >t_state); heap_freetuple(tuple); @@ -3750,23 +3766,6 @@ reindex_index(const ReindexStmt *stmt, Oid indexId, pg_rusage_init(&ru0); - /* - * Special case: cannot recreate pg_temp_class_oid_index --- to do so - * would require pg_temp_class to be a mapped relation (to avoid use of - * the index while rebuilding it) and the relmapper does not support - * temporary tables. It might be possible to make this work, but it - * doesn't seem worth the effort, so just punt. - */ - if (indexId == TempClassOidIndexId) - { - ereport(NOTICE, - errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot reindex temporary system index \"%s\", skipping", - get_rel_name(indexId))); - RemoveReindexPending(indexId); - return; - } - /* * Open and lock the parent heap relation. ShareLock is sufficient since * we only need to be sure no schema or data changes are going on. diff --git a/src/backend/catalog/meson.build b/src/backend/catalog/meson.build index 5819b10ff1f..8a8d9bcb287 100644 --- a/src/backend/catalog/meson.build +++ b/src/backend/catalog/meson.build @@ -33,7 +33,6 @@ backend_sources += files( 'pg_shdepend.c', 'pg_subscription.c', 'pg_tablespace.c', - 'pg_temp_class.c', 'pg_temp_index.c', 'pg_type.c', 'storage.c', diff --git a/src/backend/catalog/pg_temp_class.c b/src/backend/catalog/pg_temp_class.c deleted file mode 100644 index 4d69b2b0422..00000000000 --- a/src/backend/catalog/pg_temp_class.c +++ /dev/null @@ -1,270 +0,0 @@ -/*------------------------------------------------------------------------- - * - * pg_temp_class.c - * routines to support manipulation of the pg_temp_class relation - * - * The pg_temp_class system catalog table is a global temporary table that - * stores local overrides to various fields from the pg_class table for the - * duration of the current session. Currently, this is only used for - * global temporary relations, though in the future, it might also be used - * for local temporary relations. - * - * Tuples are first added to pg_temp_class when global temporary relations - * (including pg_temp_class itself) are created or opened for the first - * time in a session. This "first time" might be repeated if the effects - * of a previous "first time" are rolled back. - * - * All pg_temp_class tuples are held in a cache, managed by gtcatcache.c, - * and all updates to pg_temp_class by backend code should go through the - * routines defined here. - * - * Copyright (c) 2026, PostgreSQL Global Development Group - * - * IDENTIFICATION - * src/backend/catalog/pg_temp_class.c - * - *------------------------------------------------------------------------- - */ -#include "postgres.h" - -#include "access/htup_details.h" -#include "catalog/pg_temp_class.h" -#include "utils/gtcatcache.h" -#include "utils/memutils.h" -#include "utils/syscache.h" - -/* Cached copy of the pg_temp_class tuple descriptor */ -static TupleDesc pg_temp_class_tupdesc = NULL; - -/* - * get_pg_temp_class_tupdesc - * - * Returns the tuple descriptor for pg_temp_class. - */ -static TupleDesc -get_pg_temp_class_tupdesc(void) -{ - /* Build the tuple descriptor the first time through */ - if (pg_temp_class_tupdesc == NULL) - { - MemoryContext oldcontext; - TupleDesc tupdesc; - - oldcontext = MemoryContextSwitchTo(TopMemoryContext); - - tupdesc = CreateTemplateTupleDesc(Natts_pg_temp_class); - TupleDescInitEntry(tupdesc, - (AttrNumber) Anum_pg_temp_class_oid, - "oid", OIDOID, -1, 0); - TupleDescInitEntry(tupdesc, - (AttrNumber) Anum_pg_temp_class_relfilenode, - "relfilenode", OIDOID, -1, 0); - 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); - TupleDescInitEntry(tupdesc, - (AttrNumber) Anum_pg_temp_class_relfrozenxid, - "relfrozenxid", XIDOID, -1, 0); - TupleDescInitEntry(tupdesc, - (AttrNumber) Anum_pg_temp_class_relminmxid, - "relminmxid", XIDOID, -1, 0); - TupleDescFinalize(tupdesc); - - MemoryContextSwitchTo(oldcontext); - - /* Cache it for all future use */ - pg_temp_class_tupdesc = tupdesc; - } - return pg_temp_class_tupdesc; -} - -/* - * PgTempClassTupleExists - * - * Test if a pg_temp_class tuple for a global temporary relation exists. - */ -bool -PgTempClassTupleExists(Oid relid) -{ - return GTCatCacheTupleExists(PG_TEMP_CLASS, relid); -} - -/* - * GetPgTempClassTuple - * - * Get the pg_temp_class tuple for a global temporary relation. - * - * Returns NULL if the tuple could not be found. Otherwise, the tuple - * returned should be freed with heap_freetuple(). - */ -HeapTuple -GetPgTempClassTuple(Oid relid) -{ - return GTCatCacheSearch(PG_TEMP_CLASS, relid); -} - -/* - * InsertPgTempClassTuple - * - * Insert a new pg_temp_class tuple for a global temporary relation. - * - * This is called when a global temporary relation is created or accessed for - * the first time in a session. All tuple data is taken from rel->rd_rel. - * - * Note: The new tuple is not written to the database unless and until - * CommandCounterIncrement() is called for a non-read-only command, or the - * (sub)transaction is committed. However, the new tuple *is* visible to all - * the functions defined here. - */ -void -InsertPgTempClassTuple(Relation rel) -{ - Form_pg_class form = rel->rd_rel; - Datum values[Natts_pg_temp_class]; - bool nulls[Natts_pg_temp_class] = {0}; - - 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); - values[Anum_pg_temp_class_relfrozenxid - 1] = TransactionIdGetDatum(form->relfrozenxid); - values[Anum_pg_temp_class_relminmxid - 1] = MultiXactIdGetDatum(form->relminmxid); - - GTCatCacheTupleInsert(PG_TEMP_CLASS, - RelationGetRelid(rel), - rel->rd_rel->relkind, - get_pg_temp_class_tupdesc(), - values, nulls); -} - -/* - * UpdatePgTempClassTuple - * - * Update the pg_temp_class tuple for a global temporary relation. - */ -void -UpdatePgTempClassTuple(Oid relid, HeapTuple newtuple) -{ - GTCatCacheTupleUpdate(PG_TEMP_CLASS, relid, newtuple); -} - -/* - * UpdatePgTempClassTupleInPlace - * - * Do an in-place update of the pg_temp_class tuple for a global temporary - * relation. - */ -void -UpdatePgTempClassTupleInPlace(Oid relid, HeapTuple newtuple) -{ - GTCatCacheTupleUpdateInPlace(PG_TEMP_CLASS, relid, newtuple); -} - -/* - * DeletePgTempClassTuple - * - * Delete the pg_temp_class tuple for a global temporary relation. - */ -void -DeletePgTempClassTuple(Oid relid) -{ - GTCatCacheTupleDelete(PG_TEMP_CLASS, relid); -} - -/* - * GetPgClassAndPgTempClassTuples - * - * Get the pg_class tuple for a relation, and if it's a global temporary - * relation, also get the corresponding pg_temp_class tuple. - * - * If lock_tuple is true, the pg_class tuple will be locked, but not the - * pg_temp_class tuple. - * - * If check_temp is true, an error will be raised if a global temporary - * relation's pg_temp_class tuple is not found. After a global temporary - * relation has been opened, its pg_temp_class tuple should always exist. - * - * Returns NULL if the pg_class tuple could not be found. Otherwise, the - * tuple(s) returned should be freed with heap_freetuple(). - */ -HeapTuple -GetPgClassAndPgTempClassTuples(Oid relid, bool lock_tuple, - HeapTuple *temp_tuple, bool check_temp) -{ - HeapTuple tuple; - - /* Get a copy of the pg_class tuple */ - if (lock_tuple) - tuple = SearchSysCacheLockedCopy1(RELOID, ObjectIdGetDatum(relid)); - else - tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid)); - - if (HeapTupleIsValid(tuple) && - ((Form_pg_class) GETSTRUCT(tuple))->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) - { - /* Get the pg_temp_class tuple, and check it exists, if requested */ - *temp_tuple = GetPgTempClassTuple(relid); - if (check_temp && !HeapTupleIsValid(*temp_tuple)) - elog(ERROR, "cache lookup failed for global temp relation %u", relid); - } - else - *temp_tuple = NULL; - - return tuple; -} - -/* - * GetEffectivePgClassTuple - * - * Get the effective pg_class tuple for a relation. - * - * This will fetch the pg_class tuple for the relation and then, if it's a - * global temporary relation, fetch the corresponding pg_temp_class tuple and - * use the values in it to override the corresponding values in the pg_class - * tuple. Thus, the result represents the effective state of the relation in - * this session. - * - * For a global temporary relation that has not yet been opened in this - * session, there will be no pg_temp_class tuple, and the pg_class tuple will - * be returned unchanged. - * - * Returns NULL if the pg_class tuple could not be found. Otherwise, the - * tuple returned should be freed with heap_freetuple(). - */ -HeapTuple -GetEffectivePgClassTuple(Oid relid) -{ - HeapTuple tuple; - HeapTuple temp_tuple; - Form_pg_class classform; - Form_pg_temp_class temp_classform; - - /* - * Get the pg_class and pg_temp_class tuples. If we have the latter, use - * it to update the former. - */ - tuple = GetPgClassAndPgTempClassTuples(relid, false, &temp_tuple, false); - - if (HeapTupleIsValid(tuple) && HeapTupleIsValid(temp_tuple)) - { - classform = (Form_pg_class) GETSTRUCT(tuple); - temp_classform = (Form_pg_temp_class) GETSTRUCT(temp_tuple); - COPY_PG_TEMP_CLASS_ATTRS(temp_classform, classform); - } - return tuple; -} diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index 2434e5edca4..4da1ff5e1ec 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -1697,10 +1697,11 @@ acquire_inherited_sample_rows(Relation onerel, int elevel, /* * update_attstats() -- update attribute statistics for one relation * - * Statistics are stored in several places: the pg_class/pg_temp_class - * row for the relation has stats about the whole relation, and there is + * Statistics are stored in several places: the pg_class row (or, for + * a global temporary relation, its backend-local physical state) for + * the relation has stats about the whole relation, and there is * a pg_statistic/pg_temp_statistic row for each (non-system) attribute - * that has ever been analyzed. The pg_class/pg_temp_class values are + * that has ever been analyzed. The pg_class/backend-local values are * updated by VACUUM, not here. * * pg_statistic/pg_temp_statistic rows are just added or updated diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 090aab7741b..7975cdf1313 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -52,7 +52,6 @@ #include "catalog/pg_attrdef.h" #include "catalog/pg_constraint.h" #include "catalog/pg_inherits.h" -#include "catalog/pg_temp_class.h" #include "catalog/pg_temp_index.h" #include "catalog/toasting.h" #include "commands/defrem.h" @@ -1283,9 +1282,7 @@ 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; @@ -1478,39 +1475,45 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, pg_rusage_show(&ru0)))); /* - * Update pg_class / pg_temp_class to reflect the correct values of pages - * and tuples. + * Update pg_class (or the relation's backend-local GTT state) to reflect + * the correct values of pages and tuples. */ - relRelation = table_open(RelationRelationId, RowExclusiveLock); - - 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); - - SetEffective_relpages(relform, temp_relform, num_pages, NULL, NULL); - SetEffective_reltuples(relform, temp_relform, num_tuples, NULL, NULL); - - if (HeapTupleIsValid(temp_reltup)) + if (RELATION_IS_GLOBAL_TEMP(NewHeap)) { - UpdatePgTempClassTuple(RelationGetRelid(NewHeap), temp_reltup); - heap_freetuple(temp_reltup); + GtrRelPhysState state; + Oid newheap_relid = RelationGetRelid(NewHeap); + + if (!GetGlobalTempRelPhysState(newheap_relid, &state)) + elog(ERROR, "no local state for global temp relation %u", + newheap_relid); + state.relpages = num_pages; + state.reltuples = num_tuples; + SetGlobalTempRelPhysState(newheap_relid, &state); } else { + relRelation = table_open(RelationRelationId, RowExclusiveLock); + + reltup = SearchSysCacheCopy1(RELOID, + ObjectIdGetDatum(RelationGetRelid(NewHeap))); + if (!HeapTupleIsValid(reltup)) + elog(ERROR, "cache lookup failed for relation %u", + RelationGetRelid(NewHeap)); + relform = (Form_pg_class) GETSTRUCT(reltup); + + relform->relpages = (int32) num_pages; + relform->reltuples = (float4) num_tuples; + /* Don't update the stats for pg_class. See swap_relation_files. */ if (RelationGetRelid(OldHeap) != RelationRelationId) CatalogTupleUpdate(relRelation, &reltup->t_self, reltup); else CacheInvalidateRelcacheByTuple(reltup); - } - /* Clean up. */ - heap_freetuple(reltup); - table_close(relRelation, RowExclusiveLock); + /* Clean up. */ + heap_freetuple(reltup); + table_close(relRelation, RowExclusiveLock); + } /* Make the update visible */ CommandCounterIncrement(); @@ -1552,43 +1555,49 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class, { Relation relRelation; HeapTuple reltup1, - reltup2, - temp_reltup1, - temp_reltup2; + reltup2; Form_pg_class relform1, relform2; - Form_pg_temp_class temp_relform1, - temp_relform2; RelFileNumber relfilenumber1, relfilenumber2; RelFileNumber swaptemp; char swptmpchr; Oid relam1, relam2; + bool is_gtt; + GtrRelPhysState state1, + state2; /* - * We need writable copies of both pg_class tuples, and the corresponding - * pg_temp_class tuples, if they're global temporary relations. + * We need writable copies of both pg_class tuples, and, if they're global + * temporary relations, their local physical/statistics/freeze state. */ relRelation = table_open(RelationRelationId, RowExclusiveLock); - reltup1 = GetPgClassAndPgTempClassTuples(r1, false, &temp_reltup1, true); + reltup1 = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(r1)); if (!HeapTupleIsValid(reltup1)) elog(ERROR, "cache lookup failed for relation %u", r1); relform1 = (Form_pg_class) GETSTRUCT(reltup1); - temp_relform1 = (Form_pg_temp_class) GETSTRUCT_SAFE(temp_reltup1); - reltup2 = GetPgClassAndPgTempClassTuples(r2, false, &temp_reltup2, true); + reltup2 = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(r2)); if (!HeapTupleIsValid(reltup2)) elog(ERROR, "cache lookup failed for relation %u", r2); relform2 = (Form_pg_class) GETSTRUCT(reltup2); - temp_relform2 = (Form_pg_temp_class) GETSTRUCT_SAFE(temp_reltup2); - if (HeapTupleIsValid(temp_reltup1) != HeapTupleIsValid(temp_reltup2)) + is_gtt = (relform1->relpersistence == RELPERSISTENCE_GLOBAL_TEMP); + if (is_gtt != (relform2->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)) elog(ERROR, "relkind mismatch: cannot swap global temporary relation with a relation that is not global temporary"); - relfilenumber1 = GetEffective_relfilenode(relform1, temp_relform1); - relfilenumber2 = GetEffective_relfilenode(relform2, temp_relform2); + if (is_gtt) + { + if (!GetGlobalTempRelPhysState(r1, &state1)) + elog(ERROR, "no local state for global temp relation %u", r1); + if (!GetGlobalTempRelPhysState(r2, &state2)) + elog(ERROR, "no local state for global temp relation %u", r2); + } + + relfilenumber1 = is_gtt ? state1.relfilenode : relform1->relfilenode; + relfilenumber2 = is_gtt ? state2.relfilenode : relform2->relfilenode; relam1 = relform1->relam; relam2 = relform2->relam; @@ -1601,14 +1610,24 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class, */ Assert(!target_is_pg_class); - SetEffective_relfilenode(relform1, temp_relform1, relfilenumber2); - SetEffective_relfilenode(relform2, temp_relform2, relfilenumber1); + if (is_gtt) + { + state1.relfilenode = relfilenumber2; + state2.relfilenode = relfilenumber1; + + swaptemp = state1.reltablespace; + state1.reltablespace = state2.reltablespace; + state2.reltablespace = swaptemp; + } + else + { + relform1->relfilenode = relfilenumber2; + relform2->relfilenode = relfilenumber1; - swaptemp = GetEffective_reltablespace(relform1, temp_relform1); - SetEffective_reltablespace(relform1, temp_relform1, - GetEffective_reltablespace(relform2, - temp_relform2)); - SetEffective_reltablespace(relform2, temp_relform2, swaptemp); + swaptemp = relform1->reltablespace; + relform1->reltablespace = relform2->reltablespace; + relform2->reltablespace = swaptemp; + } swaptemp = relform1->relam; relform1->relam = relform2->relam; @@ -1719,47 +1738,57 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class, /* * Set rel1's frozen Xid and minimum MultiXid. For a global temporary - * relation, the supplied values are set in pg_temp_class, instead of - * pg_class. + * relation, the supplied values are set in its local physical state, + * instead of pg_class. */ if (relform1->relkind != RELKIND_INDEX) { Assert(!TransactionIdIsValid(frozenXid) || TransactionIdIsNormal(frozenXid)); - SetEffective_relfrozenxid(relform1, temp_relform1, frozenXid, NULL, NULL); - SetEffective_relminmxid(relform1, temp_relform1, cutoffMulti, NULL, NULL); + if (is_gtt) + { + state1.relfrozenxid = frozenXid; + state1.relminmxid = cutoffMulti; + } + else + { + relform1->relfrozenxid = frozenXid; + relform1->relminmxid = cutoffMulti; + } } /* swap size statistics too, since new rel has freshly-updated stats */ + if (is_gtt) { - int32 swap_pages; - float4 swap_tuples; - int32 swap_allvisible; - int32 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); + int32 swap_pages = state1.relpages; + float4 swap_tuples = state1.reltuples; + int32 swap_allvisible = state1.relallvisible; + int32 swap_allfrozen = state1.relallfrozen; + + state1.relpages = state2.relpages; + state2.relpages = swap_pages; + state1.reltuples = state2.reltuples; + state2.reltuples = swap_tuples; + state1.relallvisible = state2.relallvisible; + state2.relallvisible = swap_allvisible; + state1.relallfrozen = state2.relallfrozen; + state2.relallfrozen = swap_allfrozen; + } + else + { + int32 swap_pages = relform1->relpages; + float4 swap_tuples = relform1->reltuples; + int32 swap_allvisible = relform1->relallvisible; + int32 swap_allfrozen = relform1->relallfrozen; + + relform1->relpages = relform2->relpages; + relform2->relpages = swap_pages; + relform1->reltuples = relform2->reltuples; + relform2->reltuples = swap_tuples; + relform1->relallvisible = relform2->relallvisible; + relform2->relallvisible = swap_allvisible; + relform1->relallfrozen = relform2->relallfrozen; + relform2->relallfrozen = swap_allfrozen; } /* @@ -1790,14 +1819,12 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class, } /* - * For global temporary relations, update the tuples in pg_temp_class. + * For global temporary relations, update their local physical state. */ - if (HeapTupleIsValid(temp_reltup1) && HeapTupleIsValid(temp_reltup2)) + if (is_gtt) { - UpdatePgTempClassTuple(r1, temp_reltup1); - UpdatePgTempClassTuple(r2, temp_reltup2); - heap_freetuple(temp_reltup1); - heap_freetuple(temp_reltup2); + SetGlobalTempRelPhysState(r1, &state1); + SetGlobalTempRelPhysState(r2, &state2); } /* @@ -2226,16 +2253,6 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - /* - * Silently skip pg_temp_class --- it does not support relfilenode - * changes, because that would require it to be a mapped relation, - * and the relmapper does not support temporary tables. It might - * be possible to make this work, but it doesn't seem worth the - * effort. - */ - if (index->indrelid == TempRelationRelationId) - continue; - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; @@ -2280,16 +2297,6 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) class = (Form_pg_class) GETSTRUCT(tuple); - /* - * Silently skip pg_temp_class --- it does not support relfilenode - * changes, because that would require it to be a mapped relation, - * and the relmapper does not support temporary tables. It might - * be possible to make this work, but it doesn't seem worth the - * effort. - */ - if (class->oid == TempRelationRelationId) - continue; - /* Can only process plain tables and matviews */ if (class->relkind != RELKIND_RELATION && class->relkind != RELKIND_MATVIEW) @@ -2558,20 +2565,6 @@ process_single_relation(RepackStmt *stmt, LOCKMODE lockmode, bool isTopLevel, errmsg("cannot execute %s on temporary tables of other sessions", RepackCommandAsString(stmt->command))); - /* - * Reject clustering pg_temp_class --- it does not support relfilenode - * changes, because that would require it to be a mapped relation, and the - * relmapper does not support temporary tables. It might be possible to - * make this work, but it doesn't seem worth the effort. - */ - if (tableOid == TempRelationRelationId) - ereport(ERROR, - errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - /*- translator: first %s is name of a SQL command, eg. REPACK */ - errmsg("cannot execute %s on temporary system catalog \"%s\"", - RepackCommandAsString(stmt->command), - RelationGetRelationName(rel))); - /* * For partitioned tables, let caller handle this. Otherwise, process it * here and we're done. diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 120cb37ef33..750ff5997c8 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -53,7 +53,6 @@ #include "catalog/pg_rewrite.h" #include "catalog/pg_statistic_ext.h" #include "catalog/pg_tablespace.h" -#include "catalog/pg_temp_class.h" #include "catalog/pg_temp_index.h" #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" @@ -3908,8 +3907,8 @@ CheckRelationTableSpaceMove(Relation rel, Oid newTableSpaceId) /* * SetRelationTableSpace - * Set new reltablespace and relfilenumber in pg_class (and/or - * pg_temp_class for a global temporary relation). + * Set new reltablespace and relfilenumber in pg_class (and, for a + * global temporary relation, its backend-local physical state). * * newTableSpaceId is the new tablespace for the relation, and * newRelFilenumber its new filenumber. If newRelFilenumber is @@ -3929,46 +3928,58 @@ SetRelationTableSpace(Relation rel, { Relation pg_class; HeapTuple tuple; - HeapTuple temp_tuple; ItemPointerData otid; Form_pg_class rd_rel; - Form_pg_temp_class temp_rd_rel; Oid reloid = RelationGetRelid(rel); + bool is_gtt = RELATION_IS_GLOBAL_TEMP(rel); + GtrRelPhysState gtt_state; + Oid newtblspc; Assert(CheckRelationTableSpaceMove(rel, newTableSpaceId)); /* * Get a modifiable copy of the relation's pg_class row and, for a global - * temporary relation, its pg_temp_class row. + * temporary relation, its local physical state. */ pg_class = table_open(RelationRelationId, RowExclusiveLock); - tuple = GetPgClassAndPgTempClassTuples(reloid, true, &temp_tuple, true); + tuple = SearchSysCacheLockedCopy1(RELOID, ObjectIdGetDatum(reloid)); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for relation %u", reloid); otid = tuple->t_self; rd_rel = (Form_pg_class) GETSTRUCT(tuple); - temp_rd_rel = (Form_pg_temp_class) GETSTRUCT_SAFE(temp_tuple); + + if (is_gtt) + { + if (!GetGlobalTempRelPhysState(reloid, >t_state)) + elog(ERROR, "no local state for global temp relation %u", reloid); + } /* - * Update the pg_class and/or pg_temp_class rows. For global temporary - * relations, the new tablespace is set in both pg_class and pg_temp_class - * so that the change is made in the current session and for all future - * sessions. Other current sessions using the relation are not affected. + * Update the pg_class row and, for a global temporary relation, its local + * physical state. For a global temporary relation, the new tablespace is + * set in both places, so that the change is made in the current session + * and for all future sessions. Other current sessions using the relation + * are not affected. */ - SetEffective_reltablespace(rd_rel, temp_rd_rel, - newTableSpaceId == MyDatabaseTableSpace ? - InvalidOid : newTableSpaceId); + newtblspc = newTableSpaceId == MyDatabaseTableSpace ? + InvalidOid : newTableSpaceId; + rd_rel->reltablespace = newtblspc; + if (is_gtt) + gtt_state.reltablespace = newtblspc; + if (RelFileNumberIsValid(newRelFilenumber)) - SetEffective_relfilenode(rd_rel, temp_rd_rel, newRelFilenumber); + { + if (is_gtt) + gtt_state.relfilenode = newRelFilenumber; + else + rd_rel->relfilenode = newRelFilenumber; + } CatalogTupleUpdate(pg_class, &otid, tuple); UnlockTuple(pg_class, &otid, InplaceUpdateTupleLock); - if (HeapTupleIsValid(temp_tuple)) - { - UpdatePgTempClassTuple(reloid, temp_tuple); - heap_freetuple(temp_tuple); - } + if (is_gtt) + SetGlobalTempRelPhysState(reloid, >t_state); /* * Record dependency on tablespace. This is required for relations that diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 2bda20b1826..fa1ca514e02 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -39,7 +39,6 @@ #include "catalog/namespace.h" #include "catalog/pg_database.h" #include "catalog/pg_inherits.h" -#include "catalog/pg_temp_class.h" #include "commands/async.h" #include "commands/defrem.h" #include "commands/progress.h" @@ -1169,7 +1168,7 @@ vacuum_get_cutoffs(Relation rel, const VacuumParams *params, freeze_table_age = params->freeze_table_age; multixact_freeze_table_age = params->multixact_freeze_table_age; - /* Set pg_class / pg_temp_class fields in cutoffs */ + /* Set pg_class fields (or backend-local GTT state) in cutoffs */ cutoffs->relfrozenxid = rel->rd_rel->relfrozenxid; cutoffs->relminmxid = rel->rd_rel->relminmxid; @@ -1437,10 +1436,12 @@ 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 (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. + * row (and its backend-local physical state, 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 + * for the corresponding backend-local state of a global temporary + * relation. * * We violate transaction semantics here by overwriting the rel's * existing pg_class tuple with the new values. This is reasonably @@ -1450,19 +1451,20 @@ vac_estimate_reltuples(Relation relation, * 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. Likewise for pg_temp_class, which is also updated for a global - * temporary relation. + * are. The backend-local physical state of a global temporary + * relation is updated the same way, for consistency, even though it + * isn't subject to MVCC visibility concerns. * * 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. 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. + * marked with xmin = our xid. This isn't a problem for a global + * temporary relation's backend-local state, since no other session can + * see it, but it still makes sense to update it the same + * non-transactional way, for consistency. * - * In addition to fundamentally nontransactional statistics such as - * relpages and relallvisible, we try to maintain certain lazily-updated + * In addition to fundamentally nontransactional statistics such as + * relpages and relallvisible, we try to maintain certain lazily-updated * DDL flags such as relhasindex, by clearing them if no longer correct. * It's safe to do this in VACUUM, which can't run in parallel with * CREATE INDEX/RULE/TRIGGER and can't be part of a transaction block. @@ -1475,7 +1477,8 @@ vac_estimate_reltuples(Relation relation, * always allowable. * * Note: num_tuples should count only *live* tuples, since reltuples in - * pg_class and pg_temp_class is defined that way. + * pg_class (and the equivalent backend-local GTT state) is defined that + * way. * * This routine is shared by VACUUM and ANALYZE. */ @@ -1493,32 +1496,25 @@ 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 is_gtt = RELATION_IS_GLOBAL_TEMP(relation); + GtrRelPhysState gtt_state; bool dirty, - temp_dirty, + gtt_dirty = false, futurexid, futuremxid; TransactionId oldfrozenxid; MultiXactId oldminmulti; /* - * For a global temporary relation, need a copy of its pg_temp_class - * tuple. + * For a global temporary relation, need its local physical/statistics + * state. */ - if (RELATION_IS_GLOBAL_TEMP(relation)) + if (is_gtt) { - 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; + if (!GetGlobalTempRelPhysState(relid, >t_state)) + elog(ERROR, "no local state for global temp relation %u", relid); } /* Fetch a copy of the pg_class tuple to scribble on */ @@ -1538,17 +1534,40 @@ vac_update_relstats(Relation relation, /* Apply statistical updates, if any, to copied tuple(s) */ dirty = false; - 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); + if (is_gtt) + { + gtt_dirty |= (gtt_state.relpages != (int32) num_pages); + gtt_state.relpages = (int32) num_pages; + gtt_dirty |= (gtt_state.reltuples != (float4) num_tuples); + gtt_state.reltuples = (float4) num_tuples; + gtt_dirty |= (gtt_state.relallvisible != (int32) num_all_visible_pages); + gtt_state.relallvisible = (int32) num_all_visible_pages; + gtt_dirty |= (gtt_state.relallfrozen != (int32) num_all_frozen_pages); + gtt_state.relallfrozen = (int32) num_all_frozen_pages; + } + else + { + 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; + } + } /* Apply DDL updates, but not inside an outer transaction (see above) */ @@ -1587,12 +1606,13 @@ vac_update_relstats(Relation relation, * to be "in the future". * * For a global temporary relation, frozenxid is only valid for the data - * in our local instance of the relation, and is stored in pg_temp_class, - * instead of pg_class. This contributes towards tempfrozenxid for this - * backend and allows vac_update_datfrozenxid() to advance datfrozenxid - * once every backend accessing the relation has vacuumed it. + * in our local instance of the relation, and is stored in this backend's + * local physical state, instead of pg_class. This contributes towards + * tempfrozenxid for this backend and allows vac_update_datfrozenxid() to + * advance datfrozenxid once every backend accessing the relation has + * vacuumed it. */ - oldfrozenxid = GetEffective_relfrozenxid(pgcform, temp_pgcform); + oldfrozenxid = is_gtt ? gtt_state.relfrozenxid : pgcform->relfrozenxid; futurexid = false; if (frozenxid_updated) *frozenxid_updated = false; @@ -1607,15 +1627,23 @@ vac_update_relstats(Relation relation, if (update) { - SetEffective_relfrozenxid(pgcform, temp_pgcform, frozenxid, - &dirty, &temp_dirty); + if (is_gtt) + { + gtt_state.relfrozenxid = frozenxid; + gtt_dirty = true; + } + else + { + pgcform->relfrozenxid = frozenxid; + dirty = true; + } if (frozenxid_updated) *frozenxid_updated = true; } } /* Similarly for relminmxid */ - oldminmulti = GetEffective_relminmxid(pgcform, temp_pgcform); + oldminmulti = is_gtt ? gtt_state.relminmxid : pgcform->relminmxid; futuremxid = false; if (minmulti_updated) *minmulti_updated = false; @@ -1630,8 +1658,16 @@ vac_update_relstats(Relation relation, if (update) { - SetEffective_relminmxid(pgcform, temp_pgcform, minmulti, - &dirty, &temp_dirty); + if (is_gtt) + { + gtt_state.relminmxid = minmulti; + gtt_dirty = true; + } + else + { + pgcform->relminmxid = minmulti; + dirty = true; + } if (minmulti_updated) *minmulti_updated = true; } @@ -1643,13 +1679,8 @@ vac_update_relstats(Relation relation, else systable_inplace_update_cancel(inplace_state); - if (HeapTupleIsValid(temp_ctup)) - { - if (temp_dirty) - UpdatePgTempClassTupleInPlace(relid, temp_ctup); - - heap_freetuple(temp_ctup); - } + if (gtt_dirty) + SetGlobalTempRelPhysStateInPlace(relid, >t_state); heap_freetuple(ctup); @@ -1809,9 +1840,9 @@ vac_update_datfrozenxid(void) * * We exclude global temporary relations here too because, although * they can hold unfrozen XIDs, their relfrozenxid and relminmxid - * values are set in pg_temp_class, instead of pg_class, and those - * values contribute to tempfrozenxid and tempminmxid, which we - * account for below. + * values are backend-local (not stored in this shared pg_class row), + * and those values contribute to tempfrozenxid and tempminmxid, which + * we account for below. */ if ((classForm->relkind != RELKIND_RELATION && classForm->relkind != RELKIND_MATVIEW && @@ -1875,8 +1906,9 @@ vac_update_datfrozenxid(void) /* * Account for tempfrozenxid and tempminmxid from all backends connected - * to our database. This amounts to min(pg_temp_class.relfrozenxid) and - * min(pg_temp_class.relminmxid) over all those backends. + * to our database. This amounts to the minimum relfrozenxid and + * relminmxid, respectively, across all global temporary relations with + * recorded physical state in each of those backends. */ vac_get_min_tempfrozenxids(&min_tempfrozenxid, &min_tempminmxid); @@ -2320,23 +2352,6 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, return false; } - /* - * VACUUM FULL on pg_temp_class is not supported --- it does not support - * relfilenode changes, because that would require it to be a mapped - * relation, and the relmapper does not support temporary tables. It might - * be possible to make this work, but it doesn't seem worth the effort, so - * do an "aggressive" VACUUM FREEZE instead. - */ - if (relid == TempRelationRelationId && (params.options & VACOPT_FULL)) - { - params.options &= ~VACOPT_FULL; - params.options |= VACOPT_FREEZE; - params.freeze_min_age = 0; - params.freeze_table_age = 0; - params.multixact_freeze_min_age = 0; - params.multixact_freeze_table_age = 0; - } - /* * Silently ignore partitioned tables as there is no work to be done. The * useful work is on their child partitions, which have been queued up for diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 684c2af9fff..edf7d3d0065 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -39,7 +39,6 @@ #include "catalog/pg_opclass.h" #include "catalog/pg_operator.h" #include "catalog/pg_statistic_ext.h" -#include "catalog/pg_temp_class.h" #include "catalog/pg_type.h" #include "commands/comment.h" #include "commands/defrem.h" diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c index 1d8d8050f4f..87e23267bc3 100644 --- a/src/backend/statistics/relation_stats.c +++ b/src/backend/statistics/relation_stats.c @@ -20,9 +20,9 @@ #include #include "access/heapam.h" +#include "catalog/global_temp.h" #include "catalog/indexing.h" #include "catalog/namespace.h" -#include "catalog/pg_temp_class.h" #include "nodes/makefuncs.h" #include "statistics/statistics.h" #include "statistics/stat_utils.h" @@ -121,10 +121,9 @@ relation_statistics_update_internal(Oid reloid, Relation crel; HeapTuple ctup; Form_pg_class pgcform; - HeapTuple temp_ctup; - Form_pg_temp_class temp_pgcform; + bool is_gtt; + GtrRelPhysState state; bool dirty; - bool temp_dirty; bool result = true; if (!statvalues->relpages.isnull) @@ -179,56 +178,86 @@ relation_statistics_update_internal(Oid reloid, pgcform = (Form_pg_class) GETSTRUCT(ctup); /* - * For a global temporary table, need to update the pg_temp_class tuple - * instead. Force it into existence by opening the relation. + * For a global temporary table, need to update its backend-local + * physical/statistics state instead. Force it into existence by opening + * the relation. */ - if (pgcform->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + is_gtt = (pgcform->relpersistence == RELPERSISTENCE_GLOBAL_TEMP); + if (is_gtt) { Relation rel; rel = relation_open(reloid, AccessShareLock); relation_close(rel, AccessShareLock); - temp_ctup = GetPgTempClassTuple(reloid); - if (!HeapTupleIsValid(temp_ctup)) - elog(ERROR, "pg_temp_class entry for relid %u not found", reloid); - - temp_pgcform = (Form_pg_temp_class) GETSTRUCT(temp_ctup); - } - else - { - temp_ctup = NULL; - temp_pgcform = NULL; + if (!GetGlobalTempRelPhysState(reloid, &state)) + elog(ERROR, "no local state for global temp relation %u", reloid); } dirty = false; - temp_dirty = false; if (update_relpages) - SetEffective_relpages(pgcform, temp_pgcform, relpages, - &dirty, &temp_dirty); + { + if (is_gtt) + { + dirty |= (state.relpages != relpages); + state.relpages = relpages; + } + else if (pgcform->relpages != relpages) + { + pgcform->relpages = relpages; + dirty = true; + } + } if (update_reltuples) - SetEffective_reltuples(pgcform, temp_pgcform, reltuples, - &dirty, &temp_dirty); + { + if (is_gtt) + { + dirty |= (state.reltuples != reltuples); + state.reltuples = reltuples; + } + else if (pgcform->reltuples != reltuples) + { + pgcform->reltuples = reltuples; + dirty = true; + } + } if (update_relallvisible) - SetEffective_relallvisible(pgcform, temp_pgcform, relallvisible, - &dirty, &temp_dirty); + { + if (is_gtt) + { + dirty |= (state.relallvisible != relallvisible); + state.relallvisible = relallvisible; + } + else if (pgcform->relallvisible != relallvisible) + { + pgcform->relallvisible = relallvisible; + dirty = true; + } + } if (update_relallfrozen) - SetEffective_relallfrozen(pgcform, temp_pgcform, relallfrozen, - &dirty, &temp_dirty); + { + if (is_gtt) + { + dirty |= (state.relallfrozen != relallfrozen); + state.relallfrozen = relallfrozen; + } + else if (pgcform->relallfrozen != relallfrozen) + { + pgcform->relallfrozen = relallfrozen; + dirty = true; + } + } if (dirty) - CatalogTupleUpdate(crel, &ctup->t_self, ctup); - - if (HeapTupleIsValid(temp_ctup)) { - if (temp_dirty) - UpdatePgTempClassTuple(reloid, temp_ctup); - - heap_freetuple(temp_ctup); + if (is_gtt) + SetGlobalTempRelPhysState(reloid, &state); + else + CatalogTupleUpdate(crel, &ctup->t_self, ctup); } heap_freetuple(ctup); diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c index df1961accb2..bdf2e484c64 100644 --- a/src/backend/utils/adt/dbsize.c +++ b/src/backend/utils/adt/dbsize.c @@ -19,7 +19,6 @@ #include "catalog/pg_authid.h" #include "catalog/pg_database.h" #include "catalog/pg_tablespace.h" -#include "catalog/pg_temp_class.h" #include "commands/tablespace.h" #include "miscadmin.h" #include "storage/fd.h" diff --git a/src/backend/utils/cache/gtcatcache.c b/src/backend/utils/cache/gtcatcache.c index de843b95bc3..deed83585f6 100644 --- a/src/backend/utils/cache/gtcatcache.c +++ b/src/backend/utils/cache/gtcatcache.c @@ -32,8 +32,7 @@ * tuple after rollback of initialization of a global temporary sequence. * * In addition, this design delays the point at which we have to actually - * open the underlying catalog tables, which solves the "chicken and egg" - * bootstrapping problem when opening pg_temp_class for the first time. + * open the underlying pg_temp_index catalog table. * * Copyright (c) 2026, PostgreSQL Global Development Group * @@ -50,8 +49,9 @@ #include "access/parallel.h" #include "access/table.h" #include "access/xact.h" +#include "access/xlog.h" #include "catalog/indexing.h" -#include "catalog/pg_temp_class.h" +#include "catalog/pg_class.h" #include "catalog/pg_temp_index.h" #include "utils/fmgroids.h" #include "utils/gtcatcache.h" @@ -133,17 +133,6 @@ static MemoryContext gt_cat_cache_tupctx; /* The actual caches (the hash tables are lazily built) */ static GTCatCache gt_cat_cache[NUM_GT_CAT_CACHES] = { - /* PG_TEMP_CLASS */ - { - .name = "pg_temp_class cache", - .catalog_relid = TempRelationRelationId, - .index_relid = TempClassOidIndexId, - .key_attno = Anum_pg_temp_class_oid, - .cacheid = TEMPRELOID, - .hashtable = NULL, - .eoxact_list_len = 0, - .eoxact_list_overflowed = false, - }, /* PG_TEMP_INDEX */ { .name = "pg_temp_index cache", @@ -837,62 +826,6 @@ GTCatCacheTupleDelete(GTCatCacheIdentifier cacheId, Oid relid) } } -/* - * GTCatCacheGetMinFrozenXids - * - * Get the minimum relfrozenxid and relminmxid values from all pg_temp_class - * cache entries. If there are no pg_temp_class entries (no global temporary - * relations have been used in this session), then Invalid*Ids are returned. - */ -void -GTCatCacheGetMinFrozenXids(TransactionId *min_relfrozenxid, - MultiXactId *min_relminmxid) -{ - GTCatCache *cache = >_cat_cache[PG_TEMP_CLASS]; - - /* Defaults, if no global temporary relations are being used */ - *min_relfrozenxid = InvalidTransactionId; - *min_relminmxid = InvalidMultiXactId; - - if (cache->hashtable != NULL) - { - HASH_SEQ_STATUS status; - GTCatCacheEntry *entry; - - /* Scan all pg_temp_class entries and update the minimum xid values */ - hash_seq_init(&status, cache->hashtable); - while ((entry = hash_seq_search(&status)) != NULL) - { - if (!entry->deleted) - { - Form_pg_temp_class temp_form; - TransactionId relfrozenxid; - MultiXactId relminmxid; - - temp_form = (Form_pg_temp_class) GETSTRUCT(entry->tuple); - relfrozenxid = temp_form->relfrozenxid; - relminmxid = (MultiXactId) temp_form->relminmxid; - - /* Ignore relations that don't hold unfrozen XIDs */ - if (!TransactionIdIsValid(relfrozenxid) || - !MultiXactIdIsValid(relminmxid)) - continue; - - /* Update the minimum xid values */ - Assert(TransactionIdIsNormal(relfrozenxid)); - - if (!TransactionIdIsValid(*min_relfrozenxid) || - TransactionIdPrecedes(relfrozenxid, *min_relfrozenxid)) - *min_relfrozenxid = relfrozenxid; - - if (!MultiXactIdIsValid(*min_relminmxid) || - MultiXactIdPrecedes(relminmxid, *min_relminmxid)) - *min_relminmxid = relminmxid; - } - } - } -} - /* * GTCatCacheFlush * diff --git a/src/backend/utils/cache/inval.c b/src/backend/utils/cache/inval.c index 2adb9fc48b1..97f1adee6df 100644 --- a/src/backend/utils/cache/inval.c +++ b/src/backend/utils/cache/inval.c @@ -51,8 +51,8 @@ * PrepareToInvalidateCacheTuple() routine provides the knowledge of which * catcaches may need invalidation for a given tuple. * - * Also, whenever we see an operation on a pg_class, pg_temp_class, - * pg_attribute, or pg_index tuple, we register a relcache flush operation + * Also, whenever we see an operation on a pg_class, pg_attribute, or + * pg_index tuple, we register a relcache flush operation * for the relation described by that tuple (as specified in * CacheInvalidateHeapTuple()). Likewise for pg_constraint tuples for * foreign keys on relations. @@ -120,7 +120,6 @@ #include "access/xloginsert.h" #include "catalog/catalog.h" #include "catalog/pg_constraint.h" -#include "catalog/pg_temp_class.h" #include "miscadmin.h" #include "storage/procnumber.h" #include "storage/sinval.h" @@ -1495,13 +1494,6 @@ CacheInvalidateHeapTupleCommon(Relation relation, else databaseId = MyDatabaseId; } - else if (tupleRelId == TempRelationRelationId) - { - Form_pg_temp_class temp_classtup = (Form_pg_temp_class) GETSTRUCT(tuple); - - relationId = temp_classtup->oid; - databaseId = MyDatabaseId; - } else if (tupleRelId == AttributeRelationId) { Form_pg_attribute atttup = (Form_pg_attribute) GETSTRUCT(tuple); diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index e1330c31118..6402df39005 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -18,6 +18,7 @@ #include "access/hash.h" #include "access/htup_details.h" #include "bootstrap/bootstrap.h" +#include "catalog/global_temp.h" #include "catalog/namespace.h" #include "catalog/pg_am.h" #include "catalog/pg_amop.h" @@ -40,7 +41,6 @@ #include "catalog/pg_range.h" #include "catalog/pg_statistic.h" #include "catalog/pg_subscription.h" -#include "catalog/pg_temp_class.h" #include "catalog/pg_temp_index.h" #include "catalog/pg_temp_statistic.h" #include "catalog/pg_transform.h" @@ -2383,14 +2383,10 @@ get_rel_tablespace(Oid relid) /* Global temporary relations may override reltablespace locally */ if (reltup->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) { - HeapTuple temp_tp; + GtrRelPhysState state; - temp_tp = GetPgTempClassTuple(relid); - if (HeapTupleIsValid(temp_tp)) - { - result = ((Form_pg_temp_class) GETSTRUCT(temp_tp))->reltablespace; - heap_freetuple(temp_tp); - } + if (GetGlobalTempRelPhysState(relid, &state)) + result = state.reltablespace; } ReleaseSysCache(tp); return result; diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 091a3a59e16..49198b1c74c 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -62,7 +62,6 @@ #include "catalog/pg_statistic_ext.h" #include "catalog/pg_subscription.h" #include "catalog/pg_tablespace.h" -#include "catalog/pg_temp_class.h" #include "catalog/pg_temp_index.h" #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" @@ -341,9 +340,10 @@ static void unlink_initfile(const char *initfilename, int elevel); * an attribute were to be added after scanning pg_class and before * scanning pg_attribute, relnatts wouldn't match. * - * If targetRelId is a global temporary relation, pg_temp_class is - * also scanned, and if a matching tuple is found, its attributes are - * used to override the corresponding attributes from pg_class. + * If targetRelId is a global temporary relation, its backend-local + * physical/statistics/freeze state (see global_temp.c) is also + * consulted, and if found, is used to override the corresponding + * attributes from pg_class. * * NB: the returned tuple has been copied into palloc'd storage * and must eventually be freed with heap_freetuple. @@ -413,8 +413,8 @@ ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_historic) table_close(pg_class_desc, AccessShareLock); /* - * For global temporary relations, also scan pg_temp_class and apply any - * session-specific overrides to the pg_class tuple. + * For global temporary relations, also apply any session-local overrides + * of the physical/statistics/freeze fields to the pg_class tuple. */ if (HeapTupleIsValid(pg_class_tuple)) { @@ -424,17 +424,18 @@ ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_historic) if (pg_class_form->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) { - HeapTuple pg_temp_class_tuple; + GtrRelPhysState state; - pg_temp_class_tuple = GetPgTempClassTuple(targetRelId); - - if (HeapTupleIsValid(pg_temp_class_tuple)) + if (GetGlobalTempRelPhysState(targetRelId, &state)) { - Form_pg_temp_class pg_temp_class_form; - - pg_temp_class_form = (Form_pg_temp_class) GETSTRUCT(pg_temp_class_tuple); - COPY_PG_TEMP_CLASS_ATTRS(pg_temp_class_form, pg_class_form); - heap_freetuple(pg_temp_class_tuple); + pg_class_form->relfilenode = state.relfilenode; + pg_class_form->reltablespace = state.reltablespace; + pg_class_form->relpages = state.relpages; + pg_class_form->reltuples = state.reltuples; + pg_class_form->relallvisible = state.relallvisible; + pg_class_form->relallfrozen = state.relallfrozen; + pg_class_form->relfrozenxid = state.relfrozenxid; + pg_class_form->relminmxid = state.relminmxid; } } } @@ -442,6 +443,56 @@ ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_historic) return pg_class_tuple; } +/* + * GetEffectivePgClassTuple + * + * Get the effective pg_class tuple for a relation, without going + * through the relcache: fetches the pg_class tuple for the relation + * and then, if it's a global temporary relation that this backend has + * used, overrides its relfilenode/reltablespace/relpages/reltuples/ + * relallvisible/relallfrozen/relfrozenxid/relminmxid fields with this + * backend's local physical/statistics/freeze state. Thus, the result + * represents the effective state of the relation in this session. + * + * For a global temporary relation that has not yet been used in this + * session, there is no local state, and the pg_class tuple is returned + * unchanged. + * + * Returns NULL if the pg_class tuple could not be found. Otherwise, + * the tuple returned should be freed with heap_freetuple(). + */ +HeapTuple +GetEffectivePgClassTuple(Oid relid) +{ + HeapTuple tuple; + Form_pg_class classform; + + tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(tuple)) + return NULL; + + classform = (Form_pg_class) GETSTRUCT(tuple); + + if (classform->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + { + GtrRelPhysState state; + + if (GetGlobalTempRelPhysState(relid, &state)) + { + classform->relfilenode = state.relfilenode; + classform->reltablespace = state.reltablespace; + classform->relpages = state.relpages; + classform->reltuples = state.reltuples; + classform->relallvisible = state.relallvisible; + classform->relallfrozen = state.relallfrozen; + classform->relfrozenxid = state.relfrozenxid; + classform->relminmxid = state.relminmxid; + } + } + + return tuple; +} + /* * AllocateRelationDesc * @@ -3893,12 +3944,12 @@ RelationSetNewRelfilenumber(Relation relation, char persistence) Relation pg_class; ItemPointerData otid; HeapTuple tuple; - HeapTuple temp_tuple; Form_pg_class classform; - Form_pg_temp_class temp_classform; MultiXactId minmulti = InvalidMultiXactId; TransactionId freezeXid = InvalidTransactionId; RelFileLocator newrlocator; + bool is_gtt = RELATION_IS_GLOBAL_TEMP(relation); + GtrRelPhysState gtt_state; if (!IsBinaryUpgrade) { @@ -3933,18 +3984,24 @@ RelationSetNewRelfilenumber(Relation relation, char persistence) /* * Get a writable copy of the relation's pg_class tuple and, for a global - * temporary relation, its pg_temp_class tuple. + * temporary relation, its local physical state. */ pg_class = table_open(RelationRelationId, RowExclusiveLock); - tuple = GetPgClassAndPgTempClassTuples(RelationGetRelid(relation), true, - &temp_tuple, true); + tuple = SearchSysCacheLockedCopy1(RELOID, + ObjectIdGetDatum(RelationGetRelid(relation))); if (!HeapTupleIsValid(tuple)) elog(ERROR, "could not find tuple for relation %u", RelationGetRelid(relation)); otid = tuple->t_self; classform = (Form_pg_class) GETSTRUCT(tuple); - temp_classform = (Form_pg_temp_class) GETSTRUCT_SAFE(temp_tuple); + + if (is_gtt) + { + if (!GetGlobalTempRelPhysState(RelationGetRelid(relation), >t_state)) + elog(ERROR, "no local state for global temp relation %u", + RelationGetRelid(relation)); + } /* * Schedule unlinking of the old storage at transaction commit, except @@ -4050,27 +4107,47 @@ RelationSetNewRelfilenumber(Relation relation, char persistence) } else { - /* Normal case, update pg_class or pg_temp_class entry (not both) */ - SetEffective_relfilenode(classform, temp_classform, newrelfilenumber); + /* Normal case, update pg_class, or local GTT state (not both) */ + if (is_gtt) + gtt_state.relfilenode = newrelfilenumber; + else + classform->relfilenode = newrelfilenumber; /* relpages etc. never change for sequences */ if (relation->rd_rel->relkind != RELKIND_SEQUENCE) { /* 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); + if (is_gtt) + { + gtt_state.relpages = 0; + gtt_state.reltuples = -1; + gtt_state.relallvisible = 0; + gtt_state.relallfrozen = 0; + } + else + { + classform->relpages = 0; + classform->reltuples = -1; + classform->relallvisible = 0; + classform->relallfrozen = 0; + } + } + if (is_gtt) + { + gtt_state.relfrozenxid = freezeXid; + gtt_state.relminmxid = minmulti; + } + else + { + classform->relfrozenxid = freezeXid; + classform->relminmxid = minmulti; } - SetEffective_relfrozenxid(classform, temp_classform, freezeXid, NULL, NULL); - SetEffective_relminmxid(classform, temp_classform, minmulti, NULL, NULL); /* relpersistence can only change for permanent relations */ - if (HeapTupleIsValid(temp_tuple)) + if (is_gtt) { Assert(classform->relpersistence == persistence); - UpdatePgTempClassTuple(RelationGetRelid(relation), temp_tuple); - heap_freetuple(temp_tuple); + SetGlobalTempRelPhysState(RelationGetRelid(relation), >t_state); /* Update this backend's tempfrozenxid and tempminmxid */ UpdateTempFrozenXids(); @@ -4088,8 +4165,9 @@ RelationSetNewRelfilenumber(Relation relation, char persistence) table_close(pg_class, RowExclusiveLock); /* - * Make the pg_class/pg_temp_class row change or relation map change - * visible. This will cause the relcache entry to get updated, too. + * Make the pg_class row change, local GTT state change, or relation map + * change visible. This will cause the relcache entry to get updated, + * too. */ CommandCounterIncrement(); diff --git a/src/include/catalog/Makefile b/src/include/catalog/Makefile index 71ea60228d0..6175767ef17 100644 --- a/src/include/catalog/Makefile +++ b/src/include/catalog/Makefile @@ -87,7 +87,6 @@ CATALOG_HEADERS := \ pg_propgraph_label.h \ pg_propgraph_label_property.h \ pg_propgraph_property.h \ - pg_temp_class.h \ pg_temp_index.h \ pg_temp_statistic.h \ pg_temp_statistic_ext_data.h diff --git a/src/include/catalog/global_temp.h b/src/include/catalog/global_temp.h index db9243f6532..dec787db607 100644 --- a/src/include/catalog/global_temp.h +++ b/src/include/catalog/global_temp.h @@ -16,6 +16,26 @@ #include "storage/relfilelocator.h" #include "utils/rel.h" +/* + * GtrRelPhysState + * + * Physical storage location and statistics/freeze metadata for a global + * temporary relation, as seen by this backend: session-specific overrides + * for a subset of pg_class's fields. See the file header comment in + * global_temp.c for details. + */ +typedef struct GtrRelPhysState +{ + Oid relfilenode; + Oid reltablespace; + int32 relpages; + float4 reltuples; + int32 relallvisible; + int32 relallfrozen; + TransactionId relfrozenxid; + MultiXactId relminmxid; +} GtrRelPhysState; + extern void TrackGlobalTempRelationStorage(Oid relid, RelFileLocator rlocator, ProcNumber backend, bool create, bool register_delete); @@ -36,4 +56,11 @@ extern bool IsOtherUsingGlobalTempRelation(Oid relid); extern List *GetAllGlobalTempRelationsInUse(Oid dbId); extern void DiscardGlobalTempRelations(void); +extern bool GetGlobalTempRelPhysState(Oid relid, GtrRelPhysState *state); +extern void SetGlobalTempRelPhysState(Oid relid, const GtrRelPhysState *state); +extern void SetGlobalTempRelPhysStateInPlace(Oid relid, + const GtrRelPhysState *state); +extern void GetGlobalTempMinFrozenXids(TransactionId *min_relfrozenxid, + MultiXactId *min_relminmxid); + #endif /* GLOBAL_TEMP_H */ diff --git a/src/include/catalog/meson.build b/src/include/catalog/meson.build index d6ea84a2d46..d0a7772996c 100644 --- a/src/include/catalog/meson.build +++ b/src/include/catalog/meson.build @@ -74,7 +74,6 @@ catalog_headers = [ 'pg_propgraph_label.h', 'pg_propgraph_label_property.h', 'pg_propgraph_property.h', - 'pg_temp_class.h', 'pg_temp_index.h', 'pg_temp_statistic.h', 'pg_temp_statistic_ext_data.h', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 854ec786ae7..d1270e2d00a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5706,6 +5706,14 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{cmdtype,pid,datid,relid,param1,param2,param3,param4,param5,param6,param7,param8,param9,param10,param11,param12,param13,param14,param15,param16,param17,param18,param19,param20}', prosrc => 'pg_stat_get_progress_info' }, +{ oid => '8110', descr => 'session-local physical/statistics/freeze state for global temporary relations in use by this backend', + proname => 'pg_gtt_relation_state', prorows => '10', proretset => 't', + provolatile => 's', proparallel => 'r', prorettype => 'record', + proargtypes => '', + proallargtypes => '{oid,oid,oid,int4,float4,int4,int4,xid,xid}', + proargmodes => '{o,o,o,o,o,o,o,o,o}', + proargnames => '{relid,relfilenode,reltablespace,relpages,reltuples,relallvisible,relallfrozen,relfrozenxid,relminmxid}', + prosrc => 'pg_gtt_relation_state' }, { oid => '3099', descr => 'statistics: information about currently active replication', proname => 'pg_stat_get_wal_senders', prorows => '10', proisstrict => 'f', diff --git a/src/include/catalog/pg_temp_class.h b/src/include/catalog/pg_temp_class.h deleted file mode 100644 index 19d56e0a397..00000000000 --- a/src/include/catalog/pg_temp_class.h +++ /dev/null @@ -1,381 +0,0 @@ -/*------------------------------------------------------------------------- - * - * pg_temp_class.h - * definition of the "temporary relation" system catalog (pg_temp_class) - * - * This is a global temporary system catalog table storing session-specific - * information about temporary relations. Currently, it is only used for - * global temporary relations. The attributes are a subset of those from - * pg_class, and their values take precedence over the values from pg_class. - * - * Portions Copyright (c) 2026, PostgreSQL Global Development Group - * - * src/include/catalog/pg_temp_class.h - * - * NOTES - * The Catalog.pm module reads this file and derives schema - * information. - * - *------------------------------------------------------------------------- - */ -#ifndef PG_TEMP_CLASS_H -#define PG_TEMP_CLASS_H - -#include "access/htup.h" -#include "catalog/genbki.h" -#include "catalog/pg_class.h" -#include "catalog/pg_temp_class_d.h" /* IWYU pragma: export */ -#include "utils/rel.h" - -/* ---------------- - * pg_temp_class definition. cpp turns this into - * typedef struct FormData_pg_temp_class - * ---------------- - */ -BEGIN_CATALOG_STRUCT - -CATALOG(pg_temp_class,8082,TempRelationRelationId) BKI_TEMP_RELATION -{ - /* oid */ - Oid oid BKI_LOOKUP(pg_class); - - /* identifier of physical storage file */ - /* relfilenode == 0 means it is a "mapped" relation, see relmapper.c */ - Oid relfilenode BKI_DEFAULT(0); - - /* 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); - - /* all Xids < this are frozen in this rel */ - TransactionId relfrozenxid BKI_DEFAULT(3); /* FirstNormalTransactionId */ - - /* all multixacts in this rel are >= this; it is really a MultiXactId */ - TransactionId relminmxid BKI_DEFAULT(1); /* FirstMultiXactId */ -} FormData_pg_temp_class; - -END_CATALOG_STRUCT - -/* ---------------- - * Form_pg_temp_class corresponds to a pointer to a tuple with - * the format of pg_temp_class relation. - * ---------------- - */ -typedef FormData_pg_temp_class *Form_pg_temp_class; - -DECLARE_UNIQUE_INDEX_PKEY(pg_temp_class_oid_index, 8083, TempClassOidIndexId, pg_temp_class, btree(oid oid_ops)); - -MAKE_SYSCACHE(TEMPRELOID, pg_temp_class_oid_index, 128); - -/* - * Copy all pg_temp_class attributes from "source" to "target", where the - * source and target may be of type Form_pg_class or Form_pg_temp_class. - * - * Beware of multiple evaluations of arguments! - */ -#define COPY_PG_TEMP_CLASS_ATTRS(source, target) \ - do { \ - (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; \ - (target)->relfrozenxid = (source)->relfrozenxid; \ - (target)->relminmxid = (source)->relminmxid; \ - } while (0) - -/* - * Get the effective value of relfilenode from pg_class and pg_temp_class - * tuple data. The value from pg_temp_class (if present) takes precedence. - */ -static inline Oid -GetEffective_relfilenode(Form_pg_class cf, Form_pg_temp_class tf) -{ - return tf != NULL ? tf->relfilenode : cf->relfilenode; -} - -/* - * Get the effective value of reltablespace from pg_class and pg_temp_class - * tuple data. The value from pg_temp_class (if present) takes precedence. - */ -static inline Oid -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; -} - -/* - * Get the effective value of relfrozenxid from pg_class and pg_temp_class - * tuple data. The value from pg_temp_class (if present) takes precedence. - */ -static inline TransactionId -GetEffective_relfrozenxid(Form_pg_class cf, Form_pg_temp_class tf) -{ - return tf != NULL ? tf->relfrozenxid : cf->relfrozenxid; -} - -/* - * Get the effective value of relminmxid from pg_class and pg_temp_class tuple - * data. The value from pg_temp_class (if present) takes precedence. - */ -static inline MultiXactId -GetEffective_relminmxid(Form_pg_class cf, Form_pg_temp_class tf) -{ - return tf != NULL ? tf->relminmxid : cf->relminmxid; -} - -/* - * 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 - * the pg_temp_class tuple form data is non-NULL. - */ -static inline void -SetEffective_relfilenode(Form_pg_class cf, Form_pg_temp_class tf, Oid val) -{ - if (tf != NULL) - tf->relfilenode = val; - else - cf->relfilenode = val; -} - -/* - * Set the effective value of reltablespace in tuple form data from pg_class - * and pg_temp_class. The value is set in pg_temp_class as well as pg_class, - * if the pg_temp_class tuple form data is non-NULL. - */ -static inline void -SetEffective_reltablespace(Form_pg_class cf, Form_pg_temp_class tf, Oid val) -{ - /* NB: Value is set *both* locally and globally */ - cf->reltablespace = val; - if (tf != NULL) - 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; - } -} - -/* - * Set the effective value of relfrozenxid 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_relfrozenxid(Form_pg_class cf, Form_pg_temp_class tf, - TransactionId val, bool *cdirty, bool *tdirty) -{ - if (tf != NULL) - { - if (val != tf->relfrozenxid) - { - tf->relfrozenxid = val; - if (tdirty != NULL) - *tdirty = true; - } - } - else if (val != cf->relfrozenxid) - { - cf->relfrozenxid = val; - if (cdirty != NULL) - *cdirty = true; - } -} - -/* - * Set the effective value of relminmxid 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_relminmxid(Form_pg_class cf, Form_pg_temp_class tf, - MultiXactId val, bool *cdirty, bool *tdirty) -{ - if (tf != NULL) - { - if (val != tf->relminmxid) - { - tf->relminmxid = val; - if (tdirty != NULL) - *tdirty = true; - } - } - else if (val != cf->relminmxid) - { - cf->relminmxid = val; - if (cdirty != NULL) - *cdirty = true; - } -} - -extern bool PgTempClassTupleExists(Oid relid); -extern HeapTuple GetPgTempClassTuple(Oid relid); -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, - HeapTuple *temp_tuple, - bool check_temp); -extern HeapTuple GetEffectivePgClassTuple(Oid relid); - -#endif /* PG_TEMP_CLASS_H */ diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h index 404d825d477..ddfe809286d 100644 --- a/src/include/commands/vacuum.h +++ b/src/include/commands/vacuum.h @@ -265,7 +265,8 @@ typedef struct VacuumParams struct VacuumCutoffs { /* - * Existing pg_class / pg_temp_class fields at start of VACUUM + * Existing pg_class fields (or backend-local GTT state) at start of + * VACUUM */ TransactionId relfrozenxid; MultiXactId relminmxid; diff --git a/src/include/utils/gtcatcache.h b/src/include/utils/gtcatcache.h index b76911fd266..c0921dc60e7 100644 --- a/src/include/utils/gtcatcache.h +++ b/src/include/utils/gtcatcache.h @@ -20,7 +20,6 @@ */ typedef enum GTCatCacheIdentifier { - PG_TEMP_CLASS, PG_TEMP_INDEX, } GTCatCacheIdentifier; @@ -36,8 +35,6 @@ extern void GTCatCacheTupleUpdate(GTCatCacheIdentifier cacheId, Oid relid, extern void GTCatCacheTupleUpdateInPlace(GTCatCacheIdentifier cacheId, Oid relid, HeapTuple newtuple); extern void GTCatCacheTupleDelete(GTCatCacheIdentifier cacheId, Oid relid); -extern void GTCatCacheGetMinFrozenXids(TransactionId *min_relfrozenxid, - MultiXactId *min_relminmxid); extern void GTCatCacheFlush(void); extern void AtEOXact_GTCatCache(bool isCommit); extern void AtEOSubXact_GTCatCache(bool isCommit, SubTransactionId mySubid, diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h index ed09a699e8b..8411f0ecadd 100644 --- a/src/include/utils/relcache.h +++ b/src/include/utils/relcache.h @@ -14,6 +14,7 @@ #ifndef RELCACHE_H #define RELCACHE_H +#include "access/htup.h" #include "access/tupdesc.h" #include "common/relpath.h" #include "nodes/bitmapset.h" @@ -131,6 +132,13 @@ extern Relation RelationBuildLocalRelation(const char *relname, extern void RelationSetNewRelfilenumber(Relation relation, char persistence); extern void RelationAssumeNewRelfilelocator(Relation relation); +/* + * Get the effective pg_class tuple for a relation, taking into account + * backend-local global temporary relation state, without opening the + * relation. See the comment in relcache.c for details. + */ +extern HeapTuple GetEffectivePgClassTuple(Oid relid); + /* * Routines for flushing/rebuilding relcache entries in various scenarios */ diff --git a/src/test/isolation/expected/global-temp.out b/src/test/isolation/expected/global-temp.out index ac88add9e00..8f518a4c7e3 100644 --- a/src/test/isolation/expected/global-temp.out +++ b/src/test/isolation/expected/global-temp.out @@ -638,7 +638,7 @@ step get_tblspace1: regexp_replace(pg_relation_filepath('tmp'), '(\d+)', 'NNN', 'g') FROM pg_class c JOIN pg_tablespace s1 ON s1.oid = c.reltablespace - LEFT JOIN pg_temp_class t ON t.oid = c.oid + LEFT JOIN pg_gtt_relation_state() t ON t.relid = c.oid JOIN pg_tablespace s2 ON s2.oid = t.reltablespace WHERE c.relname = 'tmp'; @@ -652,7 +652,7 @@ step get_tblspace2: regexp_replace(pg_relation_filepath('tmp'), '(\d+)', 'NNN', 'g') FROM pg_class c JOIN pg_tablespace s1 ON s1.oid = c.reltablespace - LEFT JOIN pg_temp_class t ON t.oid = c.oid + LEFT JOIN pg_gtt_relation_state() t ON t.relid = c.oid LEFT JOIN pg_tablespace s2 ON s2.oid = t.reltablespace WHERE c.relname = 'tmp'; @@ -681,7 +681,7 @@ step ext_stats1: CREATE STATISTICS tmp2_stats ON key, val FROM tmp2; step ins1_2: INSERT INTO tmp2 VALUES (1, 's1'); step analyze1: ANALYZE tmp2; step cat1: - SELECT (SELECT count(*) FROM pg_temp_class WHERE oid >= 12000), + SELECT (SELECT count(*) FROM pg_gtt_relation_state() WHERE relid >= 12000), (SELECT count(*) FROM pg_temp_statistic), (SELECT count(*) FROM pg_temp_statistic_ext_data); @@ -692,7 +692,7 @@ count|count|count step drop1: DROP TABLE tmp2; step cat1: - SELECT (SELECT count(*) FROM pg_temp_class WHERE oid >= 12000), + SELECT (SELECT count(*) FROM pg_gtt_relation_state() WHERE relid >= 12000), (SELECT count(*) FROM pg_temp_statistic), (SELECT count(*) FROM pg_temp_statistic_ext_data); @@ -708,7 +708,7 @@ step ext_stats1: CREATE STATISTICS tmp2_stats ON key, val FROM tmp2; step ins1_2: INSERT INTO tmp2 VALUES (1, 's1'); step analyze1: ANALYZE tmp2; step cat1: - SELECT (SELECT count(*) FROM pg_temp_class WHERE oid >= 12000), + SELECT (SELECT count(*) FROM pg_gtt_relation_state() WHERE relid >= 12000), (SELECT count(*) FROM pg_temp_statistic), (SELECT count(*) FROM pg_temp_statistic_ext_data); @@ -719,7 +719,7 @@ count|count|count step drop2: DROP TABLE tmp2; step cat1: - SELECT (SELECT count(*) FROM pg_temp_class WHERE oid >= 12000), + SELECT (SELECT count(*) FROM pg_gtt_relation_state() WHERE relid >= 12000), (SELECT count(*) FROM pg_temp_statistic), (SELECT count(*) FROM pg_temp_statistic_ext_data); @@ -735,7 +735,7 @@ step ext_stats1: CREATE STATISTICS tmp2_stats ON key, val FROM tmp2; step ins1_2: INSERT INTO tmp2 VALUES (1, 's1'); step analyze1: ANALYZE tmp2; step cat1: - SELECT (SELECT count(*) FROM pg_temp_class WHERE oid >= 12000), + SELECT (SELECT count(*) FROM pg_gtt_relation_state() WHERE relid >= 12000), (SELECT count(*) FROM pg_temp_statistic), (SELECT count(*) FROM pg_temp_statistic_ext_data); @@ -747,7 +747,7 @@ count|count|count step b1: BEGIN; step drop2: DROP TABLE tmp2; step cat1: - SELECT (SELECT count(*) FROM pg_temp_class WHERE oid >= 12000), + SELECT (SELECT count(*) FROM pg_gtt_relation_state() WHERE relid >= 12000), (SELECT count(*) FROM pg_temp_statistic), (SELECT count(*) FROM pg_temp_statistic_ext_data); @@ -758,7 +758,7 @@ count|count|count step r1: ROLLBACK; step cat1: - SELECT (SELECT count(*) FROM pg_temp_class WHERE oid >= 12000), + SELECT (SELECT count(*) FROM pg_gtt_relation_state() WHERE relid >= 12000), (SELECT count(*) FROM pg_temp_statistic), (SELECT count(*) FROM pg_temp_statistic_ext_data); @@ -775,7 +775,7 @@ step ins1_2: INSERT INTO tmp2 VALUES (1, 's1'); step analyze1: ANALYZE tmp2; step b1: BEGIN; step cat1: - SELECT (SELECT count(*) FROM pg_temp_class WHERE oid >= 12000), + SELECT (SELECT count(*) FROM pg_gtt_relation_state() WHERE relid >= 12000), (SELECT count(*) FROM pg_temp_statistic), (SELECT count(*) FROM pg_temp_statistic_ext_data); @@ -787,7 +787,7 @@ count|count|count step sp1: SAVEPOINT sp; step drop2: DROP TABLE tmp2; step cat1: - SELECT (SELECT count(*) FROM pg_temp_class WHERE oid >= 12000), + SELECT (SELECT count(*) FROM pg_gtt_relation_state() WHERE relid >= 12000), (SELECT count(*) FROM pg_temp_statistic), (SELECT count(*) FROM pg_temp_statistic_ext_data); @@ -798,7 +798,7 @@ count|count|count step rsp1: ROLLBACK TO SAVEPOINT sp; step cat1: - SELECT (SELECT count(*) FROM pg_temp_class WHERE oid >= 12000), + SELECT (SELECT count(*) FROM pg_gtt_relation_state() WHERE relid >= 12000), (SELECT count(*) FROM pg_temp_statistic), (SELECT count(*) FROM pg_temp_statistic_ext_data); @@ -809,7 +809,7 @@ count|count|count step r1: ROLLBACK; step cat1: - SELECT (SELECT count(*) FROM pg_temp_class WHERE oid >= 12000), + SELECT (SELECT count(*) FROM pg_gtt_relation_state() WHERE relid >= 12000), (SELECT count(*) FROM pg_temp_statistic), (SELECT count(*) FROM pg_temp_statistic_ext_data); diff --git a/src/test/isolation/expected/vacuum-global-temp.out b/src/test/isolation/expected/vacuum-global-temp.out index 0028fc9bfcc..66bbcb3fd61 100644 --- a/src/test/isolation/expected/vacuum-global-temp.out +++ b/src/test/isolation/expected/vacuum-global-temp.out @@ -9,7 +9,7 @@ step create: BEGIN ATOMIC DELETE FROM saved_xids; INSERT INTO saved_xids VALUES ( - (SELECT min(relfrozenxid::text::bigint) FROM pg_temp_class WHERE relfrozenxid != 0), + (SELECT min(relfrozenxid::text::bigint) FROM pg_gtt_relation_state() WHERE relfrozenxid != 0), (SELECT min(relfrozenxid::text::bigint) FROM pg_class WHERE relfrozenxid != 0), (SELECT datfrozenxid::text::bigint FROM pg_database WHERE datname = current_database()) ); @@ -30,7 +30,7 @@ step create: BEGIN ATOMIC WITH new_xids(new_local_xid, new_global_xid, new_db_xid) AS ( SELECT - (SELECT min(relfrozenxid::text::bigint) FROM pg_temp_class WHERE relfrozenxid != 0), + (SELECT min(relfrozenxid::text::bigint) FROM pg_gtt_relation_state() WHERE relfrozenxid != 0), (SELECT min(relfrozenxid::text::bigint) FROM pg_class WHERE relfrozenxid != 0), (SELECT datfrozenxid::text::bigint FROM pg_database WHERE datname = current_database()) ) diff --git a/src/test/isolation/specs/global-temp.spec b/src/test/isolation/specs/global-temp.spec index fe392c181e7..529a099af7f 100644 --- a/src/test/isolation/specs/global-temp.spec +++ b/src/test/isolation/specs/global-temp.spec @@ -40,7 +40,7 @@ step seltype1 { SELECT key, pg_typeof(key), val FROM tmp2; } step ext_stats1 { CREATE STATISTICS tmp2_stats ON key, val FROM tmp2; } step analyze1 { ANALYZE tmp2; } step cat1 { - SELECT (SELECT count(*) FROM pg_temp_class WHERE oid >= 12000), + SELECT (SELECT count(*) FROM pg_gtt_relation_state() WHERE relid >= 12000), (SELECT count(*) FROM pg_temp_statistic), (SELECT count(*) FROM pg_temp_statistic_ext_data); } @@ -69,7 +69,7 @@ step get_tblspace1 { regexp_replace(pg_relation_filepath('tmp'), '(\d+)', 'NNN', 'g') FROM pg_class c JOIN pg_tablespace s1 ON s1.oid = c.reltablespace - LEFT JOIN pg_temp_class t ON t.oid = c.oid + LEFT JOIN pg_gtt_relation_state() t ON t.relid = c.oid JOIN pg_tablespace s2 ON s2.oid = t.reltablespace WHERE c.relname = 'tmp'; } @@ -108,7 +108,7 @@ step get_tblspace2 { regexp_replace(pg_relation_filepath('tmp'), '(\d+)', 'NNN', 'g') FROM pg_class c JOIN pg_tablespace s1 ON s1.oid = c.reltablespace - LEFT JOIN pg_temp_class t ON t.oid = c.oid + LEFT JOIN pg_gtt_relation_state() t ON t.relid = c.oid LEFT JOIN pg_tablespace s2 ON s2.oid = t.reltablespace WHERE c.relname = 'tmp'; } diff --git a/src/test/isolation/specs/vacuum-global-temp.spec b/src/test/isolation/specs/vacuum-global-temp.spec index 8d765703aad..a24b2ed8445 100644 --- a/src/test/isolation/specs/vacuum-global-temp.spec +++ b/src/test/isolation/specs/vacuum-global-temp.spec @@ -14,7 +14,7 @@ step create { BEGIN ATOMIC DELETE FROM saved_xids; INSERT INTO saved_xids VALUES ( - (SELECT min(relfrozenxid::text::bigint) FROM pg_temp_class WHERE relfrozenxid != 0), + (SELECT min(relfrozenxid::text::bigint) FROM pg_gtt_relation_state() WHERE relfrozenxid != 0), (SELECT min(relfrozenxid::text::bigint) FROM pg_class WHERE relfrozenxid != 0), (SELECT datfrozenxid::text::bigint FROM pg_database WHERE datname = current_database()) ); @@ -35,7 +35,7 @@ step create { BEGIN ATOMIC WITH new_xids(new_local_xid, new_global_xid, new_db_xid) AS ( SELECT - (SELECT min(relfrozenxid::text::bigint) FROM pg_temp_class WHERE relfrozenxid != 0), + (SELECT min(relfrozenxid::text::bigint) FROM pg_gtt_relation_state() WHERE relfrozenxid != 0), (SELECT min(relfrozenxid::text::bigint) FROM pg_class WHERE relfrozenxid != 0), (SELECT datfrozenxid::text::bigint FROM pg_database WHERE datname = current_database()) ) diff --git a/src/test/regress/expected/global_temp.out b/src/test/regress/expected/global_temp.out index 67ef6a13a7f..f2946633eea 100644 --- a/src/test/regress/expected/global_temp.out +++ b/src/test/regress/expected/global_temp.out @@ -61,10 +61,10 @@ RESET ROLE; GRANT pg_read_all_stats TO regress_global_temp_user; SET ROLE regress_global_temp_user; SELECT tempfrozenxid::text::bigint - (SELECT min(relfrozenxid::text::bigint) - FROM pg_temp_class + FROM pg_gtt_relation_state() WHERE relfrozenxid::text != '0'), tempminmxid::text::bigint - (SELECT min(relminmxid::text::bigint) - FROM pg_temp_class + FROM pg_gtt_relation_state() WHERE relminmxid::text != '0') FROM pg_stat_activity WHERE pid = pg_backend_pid(); @@ -87,34 +87,26 @@ SELECT * FROM tmp1; \c SET search_path = global_temp_tests; -SELECT oid::regclass FROM pg_temp_class ORDER BY 1; - oid ---------------------------------------- - pg_temp_class - pg_temp_class_oid_index - pg_temp_statistic - pg_temp_statistic_relid_att_inh_index - pg_temp_index - pg_temp_index_indexrelid_index -(6 rows) +SELECT relid::regclass FROM pg_gtt_relation_state() ORDER BY 1; + relid +------- +(0 rows) SELECT * FROM tmp1; a | b | c ---+---+--- (0 rows) -SELECT oid::regclass FROM pg_temp_class ORDER BY 1; - oid +SELECT relid::regclass FROM pg_gtt_relation_state() ORDER BY 1; + relid --------------------------------------- - pg_temp_class - pg_temp_class_oid_index pg_temp_statistic pg_temp_statistic_relid_att_inh_index pg_temp_index pg_temp_index_indexrelid_index tmp1 tmp1_pkey -(8 rows) +(6 rows) -- Test pg_relation_filenode() matches global relfilenode SELECT relfilenode = pg_relation_filenode('tmp1'::regclass) AS ok @@ -164,12 +156,12 @@ REINDEX INDEX CONCURRENTLY tmp1_b_idx; REINDEX TABLE CONCURRENTLY tmp1; -- Test REINDEX -- relfilenode only changes locally SELECT c.relfilenode AS global_relfilenode, t.relfilenode AS local_relfilenode - FROM pg_class c LEFT JOIN pg_temp_class t ON t.oid = c.oid + FROM pg_class c LEFT JOIN pg_gtt_relation_state() t ON t.relid = c.oid WHERE c.relname = 'tmp1_b_idx' \gset REINDEX INDEX tmp1_b_idx; SELECT CASE WHEN c.relfilenode = :global_relfilenode THEN 'unchanged' ELSE 'changed' END AS global_relfilenode, CASE WHEN t.relfilenode = :local_relfilenode THEN 'unchange' ELSE 'changed' END AS local_relfilenode - FROM pg_class c LEFT JOIN pg_temp_class t ON t.oid = c.oid + FROM pg_class c LEFT JOIN pg_gtt_relation_state() t ON t.relid = c.oid WHERE c.relname = 'tmp1_b_idx'; global_relfilenode | local_relfilenode --------------------+------------------- @@ -177,11 +169,6 @@ SELECT CASE WHEN c.relfilenode = :global_relfilenode THEN 'unchanged' ELSE 'chan (1 row) DROP INDEX CONCURRENTLY tmp1_b_idx; --- REINDEX not allowed on pg_temp_class -REINDEX INDEX pg_temp_class_oid_index; -NOTICE: cannot reindex temporary system index "pg_temp_class_oid_index", skipping -REINDEX TABLE pg_temp_class; -NOTICE: cannot reindex temporary system index "pg_temp_class_oid_index", skipping -- Test ON COMMIT DELETE ROWS CREATE GLOBAL TEMP TABLE tmp2 (a int) ON COMMIT DELETE ROWS; BEGIN; @@ -416,7 +403,7 @@ SELECT c.reltablespace AS global_tablespace, t.reltablespace AS local_tablespace, regexp_replace(pg_relation_filepath('tmp2'), '(\d+)', 'NNN', 'g') FROM pg_class c - LEFT JOIN pg_temp_class t ON t.oid = c.oid + LEFT JOIN pg_gtt_relation_state() t ON t.relid = c.oid WHERE c.relname = 'tmp2'; global_tablespace | local_tablespace | regexp_replace -------------------+------------------+------------------- @@ -434,7 +421,7 @@ SELECT s1.spcname AS global_tablespace, s2.spcname AS local_tablespace, regexp_replace(pg_relation_filepath('tmp2'), '(\d+)', 'NNN', 'g') FROM pg_class c JOIN pg_tablespace s1 ON s1.oid = c.reltablespace - LEFT JOIN pg_temp_class t ON t.oid = c.oid + LEFT JOIN pg_gtt_relation_state() t ON t.relid = c.oid JOIN pg_tablespace s2 ON s2.oid = t.reltablespace WHERE c.relname = 'tmp2'; global_tablespace | local_tablespace | regexp_replace @@ -532,57 +519,49 @@ SELECT * FROM tmp1; -- Test CLUSTER -- relfilenode only changes locally SELECT c.relfilenode AS global_relfilenode, t.relfilenode AS local_relfilenode - FROM pg_class c LEFT JOIN pg_temp_class t ON t.oid = c.oid + FROM pg_class c LEFT JOIN pg_gtt_relation_state() t ON t.relid = c.oid WHERE c.relname = 'tmp1' \gset CLUSTER tmp1 USING tmp1_pkey; SELECT CASE WHEN c.relfilenode = :global_relfilenode THEN 'unchanged' ELSE 'changed' END AS global_relfilenode, CASE WHEN t.relfilenode = :local_relfilenode THEN 'unchange' ELSE 'changed' END AS local_relfilenode - FROM pg_class c LEFT JOIN pg_temp_class t ON t.oid = c.oid + FROM pg_class c LEFT JOIN pg_gtt_relation_state() t ON t.relid = c.oid WHERE c.relname = 'tmp1'; global_relfilenode | local_relfilenode --------------------+------------------- unchanged | changed (1 row) --- CLUSTER not allowed on pg_temp_class -CLUSTER pg_temp_class; -- fail -ERROR: cannot execute CLUSTER on temporary system catalog "pg_temp_class" -- Test REPACK -- relfilenode only changes locally SELECT c.relfilenode AS global_relfilenode, t.relfilenode AS local_relfilenode - FROM pg_class c LEFT JOIN pg_temp_class t ON t.oid = c.oid + FROM pg_class c LEFT JOIN pg_gtt_relation_state() t ON t.relid = c.oid WHERE c.relname = 'tmp1' \gset REPACK tmp1; SELECT CASE WHEN c.relfilenode = :global_relfilenode THEN 'unchanged' ELSE 'changed' END AS global_relfilenode, CASE WHEN t.relfilenode = :local_relfilenode THEN 'unchange' ELSE 'changed' END AS local_relfilenode - FROM pg_class c LEFT JOIN pg_temp_class t ON t.oid = c.oid + FROM pg_class c LEFT JOIN pg_gtt_relation_state() t ON t.relid = c.oid WHERE c.relname = 'tmp1'; global_relfilenode | local_relfilenode --------------------+------------------- unchanged | changed (1 row) --- REPACK not allowed on pg_temp_class -REPACK pg_temp_class; -- fail -ERROR: cannot execute REPACK on temporary system catalog "pg_temp_class" -- Test VACUUM FULL -- relfilenode only changes locally SELECT c.relfilenode AS global_relfilenode, t.relfilenode AS local_relfilenode - FROM pg_class c LEFT JOIN pg_temp_class t ON t.oid = c.oid + FROM pg_class c LEFT JOIN pg_gtt_relation_state() t ON t.relid = c.oid WHERE c.relname = 'tmp1' \gset VACUUM FULL tmp1; SELECT CASE WHEN c.relfilenode = :global_relfilenode THEN 'unchanged' ELSE 'changed' END AS global_relfilenode, CASE WHEN t.relfilenode = :local_relfilenode THEN 'unchange' ELSE 'changed' END AS local_relfilenode - FROM pg_class c LEFT JOIN pg_temp_class t ON t.oid = c.oid + FROM pg_class c LEFT JOIN pg_gtt_relation_state() t ON t.relid = c.oid WHERE c.relname = 'tmp1'; global_relfilenode | local_relfilenode --------------------+------------------- unchanged | changed (1 row) --- VACUUM FULL not allowed on pg_temp_class -VACUUM FULL pg_temp_class; -- silently ignored -- Test pg_relation_filenode() now matches local relfilenode SELECT relfilenode = pg_relation_filenode('tmp1'::regclass) AS ok - FROM pg_temp_class WHERE oid = 'tmp1'::regclass; + FROM pg_gtt_relation_state() WHERE relid = 'tmp1'::regclass; ok ---- t @@ -592,7 +571,7 @@ SELECT relfilenode = pg_relation_filenode('tmp1'::regclass) AS ok \c SET search_path = global_temp_tests; SELECT EXISTS (SELECT 1 FROM pg_class t WHERE t.oid = c.reltoastrelid), - EXISTS (SELECT 1 FROM pg_temp_class t WHERE t.oid = c.reltoastrelid) + EXISTS (SELECT 1 FROM pg_gtt_relation_state() t WHERE t.relid = c.reltoastrelid) FROM pg_class c WHERE oid = 'tmp1'::regclass; exists | exists @@ -602,7 +581,7 @@ WHERE oid = 'tmp1'::regclass; VACUUM tmp1; SELECT EXISTS (SELECT 1 FROM pg_class t WHERE t.oid = c.reltoastrelid), - EXISTS (SELECT 1 FROM pg_temp_class t WHERE t.oid = c.reltoastrelid) + EXISTS (SELECT 1 FROM pg_gtt_relation_state() t WHERE t.relid = c.reltoastrelid) FROM pg_class c WHERE oid = 'tmp1'::regclass; exists | exists @@ -614,7 +593,7 @@ WHERE oid = 'tmp1'::regclass; SET search_path = global_temp_tests; VACUUM FULL tmp1; SELECT EXISTS (SELECT 1 FROM pg_class t WHERE t.oid = c.reltoastrelid), - EXISTS (SELECT 1 FROM pg_temp_class t WHERE t.oid = c.reltoastrelid) + EXISTS (SELECT 1 FROM pg_gtt_relation_state() t WHERE t.relid = c.reltoastrelid) FROM pg_class c WHERE oid = 'tmp1'::regclass; exists | exists @@ -622,7 +601,7 @@ WHERE oid = 'tmp1'::regclass; t | t (1 row) --- Test subtransaction rollback of pending pg_temp_class inserts +-- Test subtransaction rollback of pending local relation state \c SET search_path = global_temp_tests; BEGIN; @@ -637,11 +616,9 @@ DROP TABLE tmp1; ROLLBACK TO sp; INSERT INTO tmp1 VALUES (1, 'xxx'); COMMIT; -SELECT oid::regclass FROM pg_temp_class ORDER BY 1; - oid +SELECT relid::regclass FROM pg_gtt_relation_state() ORDER BY 1; + relid --------------------------------------- - pg_temp_class - pg_temp_class_oid_index pg_temp_statistic pg_temp_statistic_relid_att_inh_index pg_temp_index @@ -649,7 +626,7 @@ SELECT oid::regclass FROM pg_temp_class ORDER BY 1; tmp1_c_seq tmp1 tmp1_pkey -(9 rows) +(7 rows) SELECT * FROM tmp1; a | b | c @@ -664,7 +641,7 @@ 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 + FROM pg_class c JOIN pg_gtt_relation_state() t ON t.relid = c.oid WHERE c.oid = 'tmp2'::regclass; oid | global_relpages | global_reltuples | local_relpages | local_reltuples ------+-----------------+------------------+----------------+----------------- @@ -676,7 +653,7 @@ 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 + FROM pg_class c JOIN pg_gtt_relation_state() t ON t.relid = 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 ------------+-----------------+------------------+----------------+----------------- @@ -690,7 +667,7 @@ 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 + FROM pg_class c JOIN pg_gtt_relation_state() t ON t.relid = 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 ------------+-----------------+------------------+----------------+----------------- @@ -704,7 +681,7 @@ 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 + FROM pg_class c JOIN pg_gtt_relation_state() t ON t.relid = 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 ------------+-----------------+------------------+----------------+----------------- @@ -718,7 +695,7 @@ 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 + FROM pg_class c JOIN pg_gtt_relation_state() t ON t.relid = 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 ------------+-----------------+------------------+----------------+----------------- @@ -747,7 +724,7 @@ SELECT row_estimate('SELECT * FROM tmp2'); -- Test in-place stats update (non-transactional) TRUNCATE tmp2; -SELECT reltuples FROM pg_temp_class WHERE oid = 'tmp2'::regclass; +SELECT reltuples FROM pg_gtt_relation_state() WHERE relid = 'tmp2'::regclass; reltuples ----------- -1 @@ -762,7 +739,7 @@ SELECT row_estimate('SELECT * FROM tmp2'); BEGIN; INSERT INTO tmp2 SELECT * FROM generate_series(1, 100); ANALYZE tmp2; -SELECT reltuples FROM pg_temp_class WHERE oid = 'tmp2'::regclass; +SELECT reltuples FROM pg_gtt_relation_state() WHERE relid = 'tmp2'::regclass; reltuples ----------- 100 @@ -775,7 +752,7 @@ SELECT row_estimate('SELECT * FROM tmp2'); (1 row) ROLLBACK; -SELECT reltuples FROM pg_temp_class WHERE oid = 'tmp2'::regclass; +SELECT reltuples FROM pg_gtt_relation_state() WHERE relid = 'tmp2'::regclass; reltuples ----------- 100 @@ -792,7 +769,7 @@ BEGIN; TRUNCATE tmp2; INSERT INTO tmp2 SELECT * FROM generate_series(1, 50); ANALYZE tmp2; -SELECT reltuples FROM pg_temp_class WHERE oid = 'tmp2'::regclass; +SELECT reltuples FROM pg_gtt_relation_state() WHERE relid = 'tmp2'::regclass; reltuples ----------- 50 @@ -805,7 +782,7 @@ SELECT row_estimate('SELECT * FROM tmp2'); (1 row) ROLLBACK; -SELECT reltuples FROM pg_temp_class WHERE oid = 'tmp2'::regclass; +SELECT reltuples FROM pg_gtt_relation_state() WHERE relid = 'tmp2'::regclass; reltuples ----------- 100 @@ -831,11 +808,11 @@ SELECT oid::regclass, 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 +SELECT relid::regclass, relpages, reltuples, relallvisible, relallfrozen + FROM pg_gtt_relation_state() WHERE relid = 'tmp2'::regclass; + relid | relpages | reltuples | relallvisible | relallfrozen +-------+----------+-----------+---------------+-------------- + tmp2 | 0 | -1 | 0 | 0 (1 row) SELECT pg_restore_relation_stats( @@ -857,11 +834,11 @@ SELECT oid::regclass, 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 +SELECT relid::regclass, relpages, reltuples, relallvisible, relallfrozen + FROM pg_gtt_relation_state() WHERE relid = 'tmp2'::regclass; + relid | relpages | reltuples | relallvisible | relallfrozen +-------+----------+-----------+---------------+-------------- + tmp2 | 5 | 150 | 10 | 20 (1 row) DROP TABLE tmp2; @@ -1168,10 +1145,10 @@ SELECT tempfrozenxid, tempminmxid FROM pg_stat_activity WHERE pid = pg_backend_p CREATE GLOBAL TEMP TABLE tmp2 (a int); SELECT tempfrozenxid::text::bigint - (SELECT min(relfrozenxid::text::bigint) - FROM pg_temp_class + FROM pg_gtt_relation_state() WHERE relfrozenxid::text != '0'), tempminmxid::text::bigint - (SELECT min(relminmxid::text::bigint) - FROM pg_temp_class + FROM pg_gtt_relation_state() WHERE relminmxid::text != '0') FROM pg_stat_activity WHERE pid = pg_backend_pid(); @@ -1180,17 +1157,16 @@ SELECT tempfrozenxid::text::bigint - (SELECT min(relfrozenxid::text::bigint) 0 | 0 (1 row) -VACUUM FREEZE pg_temp_class; BEGIN; SAVEPOINT sp; DROP TABLE tmp2; ROLLBACK TO SAVEPOINT sp; COMMIT; SELECT tempfrozenxid::text::bigint - (SELECT min(relfrozenxid::text::bigint) - FROM pg_temp_class + FROM pg_gtt_relation_state() WHERE relfrozenxid::text != '0'), tempminmxid::text::bigint - (SELECT min(relminmxid::text::bigint) - FROM pg_temp_class + FROM pg_gtt_relation_state() WHERE relminmxid::text != '0') FROM pg_stat_activity WHERE pid = pg_backend_pid(); @@ -1231,6 +1207,12 @@ SELECT tempfrozenxid, tempminmxid | (1 row) +SELECT count(*) FROM pg_gtt_relation_state(); -- none left + count +------- + 0 +(1 row) + SELECT relname, pg_relation_size(oid) FROM pg_class WHERE (relname ~ 'tmp1' OR relname ~ 'tmp2' OR relname ~ 'pg_temp_') @@ -1238,7 +1220,6 @@ SELECT relname, pg_relation_size(oid) ORDER BY relname; relname | pg_relation_size ----------------------------+------------------ - pg_temp_class | 0 pg_temp_index | 0 pg_temp_statistic | 0 pg_temp_statistic_ext_data | 0 @@ -1246,7 +1227,7 @@ SELECT relname, pg_relation_size(oid) tmp2 | 0 tmp2_p1 | 0 tmp2_p2 | 0 -(8 rows) +(7 rows) SELECT * FROM tmp1; a | b | c diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out index 2b4af8452e6..4facc83bed6 100644 --- a/src/test/regress/expected/oidjoins.out +++ b/src/test/regress/expected/oidjoins.out @@ -285,8 +285,6 @@ NOTICE: checking pg_propgraph_label_property {plpellabelid} => pg_propgraph_ele NOTICE: checking pg_propgraph_property {pgppgid} => pg_class {oid} NOTICE: checking pg_propgraph_property {pgptypid} => pg_type {oid} NOTICE: checking pg_propgraph_property {pgpcollation} => pg_collation {oid} -NOTICE: checking pg_temp_class {oid} => pg_class {oid} -NOTICE: checking pg_temp_class {reltablespace} => pg_tablespace {oid} NOTICE: checking pg_temp_index {indexrelid} => pg_class {oid} NOTICE: checking pg_temp_statistic {starelid} => pg_class {oid} NOTICE: checking pg_temp_statistic {staop1} => pg_operator {oid} diff --git a/src/test/regress/sql/global_temp.sql b/src/test/regress/sql/global_temp.sql index 4d470d03f02..2fe79c332cd 100644 --- a/src/test/regress/sql/global_temp.sql +++ b/src/test/regress/sql/global_temp.sql @@ -35,10 +35,10 @@ GRANT pg_read_all_stats TO regress_global_temp_user; SET ROLE regress_global_temp_user; SELECT tempfrozenxid::text::bigint - (SELECT min(relfrozenxid::text::bigint) - FROM pg_temp_class + FROM pg_gtt_relation_state() WHERE relfrozenxid::text != '0'), tempminmxid::text::bigint - (SELECT min(relminmxid::text::bigint) - FROM pg_temp_class + FROM pg_gtt_relation_state() WHERE relminmxid::text != '0') FROM pg_stat_activity WHERE pid = pg_backend_pid(); @@ -51,9 +51,9 @@ INSERT INTO tmp1 VALUES (1, 'xxx'); SELECT * FROM tmp1; \c SET search_path = global_temp_tests; -SELECT oid::regclass FROM pg_temp_class ORDER BY 1; +SELECT relid::regclass FROM pg_gtt_relation_state() ORDER BY 1; SELECT * FROM tmp1; -SELECT oid::regclass FROM pg_temp_class ORDER BY 1; +SELECT relid::regclass FROM pg_gtt_relation_state() ORDER BY 1; -- Test pg_relation_filenode() matches global relfilenode SELECT relfilenode = pg_relation_filenode('tmp1'::regclass) AS ok @@ -79,19 +79,15 @@ REINDEX TABLE CONCURRENTLY tmp1; -- Test REINDEX -- relfilenode only changes locally SELECT c.relfilenode AS global_relfilenode, t.relfilenode AS local_relfilenode - FROM pg_class c LEFT JOIN pg_temp_class t ON t.oid = c.oid + FROM pg_class c LEFT JOIN pg_gtt_relation_state() t ON t.relid = c.oid WHERE c.relname = 'tmp1_b_idx' \gset REINDEX INDEX tmp1_b_idx; SELECT CASE WHEN c.relfilenode = :global_relfilenode THEN 'unchanged' ELSE 'changed' END AS global_relfilenode, CASE WHEN t.relfilenode = :local_relfilenode THEN 'unchange' ELSE 'changed' END AS local_relfilenode - FROM pg_class c LEFT JOIN pg_temp_class t ON t.oid = c.oid + FROM pg_class c LEFT JOIN pg_gtt_relation_state() t ON t.relid = c.oid WHERE c.relname = 'tmp1_b_idx'; DROP INDEX CONCURRENTLY tmp1_b_idx; --- REINDEX not allowed on pg_temp_class -REINDEX INDEX pg_temp_class_oid_index; -REINDEX TABLE pg_temp_class; - -- Test ON COMMIT DELETE ROWS CREATE GLOBAL TEMP TABLE tmp2 (a int) ON COMMIT DELETE ROWS; BEGIN; @@ -207,7 +203,7 @@ SELECT c.reltablespace AS global_tablespace, t.reltablespace AS local_tablespace, regexp_replace(pg_relation_filepath('tmp2'), '(\d+)', 'NNN', 'g') FROM pg_class c - LEFT JOIN pg_temp_class t ON t.oid = c.oid + LEFT JOIN pg_gtt_relation_state() t ON t.relid = c.oid WHERE c.relname = 'tmp2'; ALTER TABLE tmp2 SET TABLESPACE regress_tblspace; SELECT * FROM tmp2; @@ -215,7 +211,7 @@ SELECT s1.spcname AS global_tablespace, s2.spcname AS local_tablespace, regexp_replace(pg_relation_filepath('tmp2'), '(\d+)', 'NNN', 'g') FROM pg_class c JOIN pg_tablespace s1 ON s1.oid = c.reltablespace - LEFT JOIN pg_temp_class t ON t.oid = c.oid + LEFT JOIN pg_gtt_relation_state() t ON t.relid = c.oid JOIN pg_tablespace s2 ON s2.oid = t.reltablespace WHERE c.relname = 'tmp2'; DROP TABLE tmp2; @@ -271,58 +267,49 @@ SELECT * FROM tmp1; -- Test CLUSTER -- relfilenode only changes locally SELECT c.relfilenode AS global_relfilenode, t.relfilenode AS local_relfilenode - FROM pg_class c LEFT JOIN pg_temp_class t ON t.oid = c.oid + FROM pg_class c LEFT JOIN pg_gtt_relation_state() t ON t.relid = c.oid WHERE c.relname = 'tmp1' \gset CLUSTER tmp1 USING tmp1_pkey; SELECT CASE WHEN c.relfilenode = :global_relfilenode THEN 'unchanged' ELSE 'changed' END AS global_relfilenode, CASE WHEN t.relfilenode = :local_relfilenode THEN 'unchange' ELSE 'changed' END AS local_relfilenode - FROM pg_class c LEFT JOIN pg_temp_class t ON t.oid = c.oid + FROM pg_class c LEFT JOIN pg_gtt_relation_state() t ON t.relid = c.oid WHERE c.relname = 'tmp1'; --- CLUSTER not allowed on pg_temp_class -CLUSTER pg_temp_class; -- fail - -- Test REPACK -- relfilenode only changes locally SELECT c.relfilenode AS global_relfilenode, t.relfilenode AS local_relfilenode - FROM pg_class c LEFT JOIN pg_temp_class t ON t.oid = c.oid + FROM pg_class c LEFT JOIN pg_gtt_relation_state() t ON t.relid = c.oid WHERE c.relname = 'tmp1' \gset REPACK tmp1; SELECT CASE WHEN c.relfilenode = :global_relfilenode THEN 'unchanged' ELSE 'changed' END AS global_relfilenode, CASE WHEN t.relfilenode = :local_relfilenode THEN 'unchange' ELSE 'changed' END AS local_relfilenode - FROM pg_class c LEFT JOIN pg_temp_class t ON t.oid = c.oid + FROM pg_class c LEFT JOIN pg_gtt_relation_state() t ON t.relid = c.oid WHERE c.relname = 'tmp1'; --- REPACK not allowed on pg_temp_class -REPACK pg_temp_class; -- fail - -- Test VACUUM FULL -- relfilenode only changes locally SELECT c.relfilenode AS global_relfilenode, t.relfilenode AS local_relfilenode - FROM pg_class c LEFT JOIN pg_temp_class t ON t.oid = c.oid + FROM pg_class c LEFT JOIN pg_gtt_relation_state() t ON t.relid = c.oid WHERE c.relname = 'tmp1' \gset VACUUM FULL tmp1; SELECT CASE WHEN c.relfilenode = :global_relfilenode THEN 'unchanged' ELSE 'changed' END AS global_relfilenode, CASE WHEN t.relfilenode = :local_relfilenode THEN 'unchange' ELSE 'changed' END AS local_relfilenode - FROM pg_class c LEFT JOIN pg_temp_class t ON t.oid = c.oid + FROM pg_class c LEFT JOIN pg_gtt_relation_state() t ON t.relid = c.oid WHERE c.relname = 'tmp1'; --- VACUUM FULL not allowed on pg_temp_class -VACUUM FULL pg_temp_class; -- silently ignored - -- Test pg_relation_filenode() now matches local relfilenode SELECT relfilenode = pg_relation_filenode('tmp1'::regclass) AS ok - FROM pg_temp_class WHERE oid = 'tmp1'::regclass; + FROM pg_gtt_relation_state() WHERE relid = 'tmp1'::regclass; -- VACUUM initializes toast tables \c SET search_path = global_temp_tests; SELECT EXISTS (SELECT 1 FROM pg_class t WHERE t.oid = c.reltoastrelid), - EXISTS (SELECT 1 FROM pg_temp_class t WHERE t.oid = c.reltoastrelid) + EXISTS (SELECT 1 FROM pg_gtt_relation_state() t WHERE t.relid = c.reltoastrelid) FROM pg_class c WHERE oid = 'tmp1'::regclass; VACUUM tmp1; SELECT EXISTS (SELECT 1 FROM pg_class t WHERE t.oid = c.reltoastrelid), - EXISTS (SELECT 1 FROM pg_temp_class t WHERE t.oid = c.reltoastrelid) + EXISTS (SELECT 1 FROM pg_gtt_relation_state() t WHERE t.relid = c.reltoastrelid) FROM pg_class c WHERE oid = 'tmp1'::regclass; @@ -330,11 +317,11 @@ WHERE oid = 'tmp1'::regclass; SET search_path = global_temp_tests; VACUUM FULL tmp1; SELECT EXISTS (SELECT 1 FROM pg_class t WHERE t.oid = c.reltoastrelid), - EXISTS (SELECT 1 FROM pg_temp_class t WHERE t.oid = c.reltoastrelid) + EXISTS (SELECT 1 FROM pg_gtt_relation_state() t WHERE t.relid = c.reltoastrelid) FROM pg_class c WHERE oid = 'tmp1'::regclass; --- Test subtransaction rollback of pending pg_temp_class inserts +-- Test subtransaction rollback of pending local relation state \c SET search_path = global_temp_tests; BEGIN; @@ -344,7 +331,7 @@ DROP TABLE tmp1; ROLLBACK TO sp; INSERT INTO tmp1 VALUES (1, 'xxx'); COMMIT; -SELECT oid::regclass FROM pg_temp_class ORDER BY 1; +SELECT relid::regclass FROM pg_gtt_relation_state() ORDER BY 1; SELECT * FROM tmp1; -- Test stats updates applied by CREATE INDEX, ANALYZE, VACUUM, and REPACK @@ -354,7 +341,7 @@ 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 + FROM pg_class c JOIN pg_gtt_relation_state() t ON t.relid = c.oid WHERE c.oid = 'tmp2'::regclass; CREATE INDEX tmp2_a_idx ON tmp2(a); @@ -362,7 +349,7 @@ 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 + FROM pg_class c JOIN pg_gtt_relation_state() t ON t.relid = 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); @@ -371,7 +358,7 @@ 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 + FROM pg_class c JOIN pg_gtt_relation_state() t ON t.relid = c.oid WHERE c.oid = 'tmp2'::regclass OR c.oid = 'tmp2_a_idx'::regclass ORDER BY 1; DELETE FROM tmp2 WHERE a % 2 = 0; @@ -380,7 +367,7 @@ 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 + FROM pg_class c JOIN pg_gtt_relation_state() t ON t.relid = c.oid WHERE c.oid = 'tmp2'::regclass OR c.oid = 'tmp2_a_idx'::regclass ORDER BY 1; DELETE FROM tmp2 WHERE a % 3 = 0; @@ -389,7 +376,7 @@ 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 + FROM pg_class c JOIN pg_gtt_relation_state() t ON t.relid = c.oid WHERE c.oid = 'tmp2'::regclass OR c.oid = 'tmp2_a_idx'::regclass ORDER BY 1; -- Test stats usage @@ -410,17 +397,17 @@ SELECT row_estimate('SELECT * FROM tmp2'); -- Test in-place stats update (non-transactional) TRUNCATE tmp2; -SELECT reltuples FROM pg_temp_class WHERE oid = 'tmp2'::regclass; +SELECT reltuples FROM pg_gtt_relation_state() WHERE relid = 'tmp2'::regclass; SELECT row_estimate('SELECT * FROM tmp2'); BEGIN; INSERT INTO tmp2 SELECT * FROM generate_series(1, 100); ANALYZE tmp2; -SELECT reltuples FROM pg_temp_class WHERE oid = 'tmp2'::regclass; +SELECT reltuples FROM pg_gtt_relation_state() WHERE relid = 'tmp2'::regclass; SELECT row_estimate('SELECT * FROM tmp2'); ROLLBACK; -SELECT reltuples FROM pg_temp_class WHERE oid = 'tmp2'::regclass; +SELECT reltuples FROM pg_gtt_relation_state() WHERE relid = 'tmp2'::regclass; SELECT row_estimate('SELECT * FROM tmp2'); -- Test in-place stats update after regular update (transactional) @@ -428,19 +415,19 @@ BEGIN; TRUNCATE tmp2; INSERT INTO tmp2 SELECT * FROM generate_series(1, 50); ANALYZE tmp2; -SELECT reltuples FROM pg_temp_class WHERE oid = 'tmp2'::regclass; +SELECT reltuples FROM pg_gtt_relation_state() WHERE relid = 'tmp2'::regclass; SELECT row_estimate('SELECT * FROM tmp2'); ROLLBACK; -SELECT reltuples FROM pg_temp_class WHERE oid = 'tmp2'::regclass; +SELECT reltuples FROM pg_gtt_relation_state() WHERE relid = 'tmp2'::regclass; 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 relid::regclass, relpages, reltuples, relallvisible, relallfrozen + FROM pg_gtt_relation_state() WHERE relid = 'tmp2'::regclass; SELECT pg_restore_relation_stats( 'schemaname', 'global_temp_tests', @@ -451,8 +438,8 @@ SELECT pg_restore_relation_stats( '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; +SELECT relid::regclass, relpages, reltuples, relallvisible, relallfrozen + FROM pg_gtt_relation_state() WHERE relid = 'tmp2'::regclass; DROP TABLE tmp2; @@ -594,15 +581,14 @@ SELECT tempfrozenxid, tempminmxid FROM pg_stat_activity WHERE pid = pg_backend_p CREATE GLOBAL TEMP TABLE tmp2 (a int); SELECT tempfrozenxid::text::bigint - (SELECT min(relfrozenxid::text::bigint) - FROM pg_temp_class + FROM pg_gtt_relation_state() WHERE relfrozenxid::text != '0'), tempminmxid::text::bigint - (SELECT min(relminmxid::text::bigint) - FROM pg_temp_class + FROM pg_gtt_relation_state() WHERE relminmxid::text != '0') FROM pg_stat_activity WHERE pid = pg_backend_pid(); -VACUUM FREEZE pg_temp_class; BEGIN; SAVEPOINT sp; DROP TABLE tmp2; @@ -610,10 +596,10 @@ ROLLBACK TO SAVEPOINT sp; COMMIT; SELECT tempfrozenxid::text::bigint - (SELECT min(relfrozenxid::text::bigint) - FROM pg_temp_class + FROM pg_gtt_relation_state() WHERE relfrozenxid::text != '0'), tempminmxid::text::bigint - (SELECT min(relminmxid::text::bigint) - FROM pg_temp_class + FROM pg_gtt_relation_state() WHERE relminmxid::text != '0') FROM pg_stat_activity WHERE pid = pg_backend_pid(); @@ -635,6 +621,7 @@ DISCARD GLOBAL TEMP; SELECT tempfrozenxid, tempminmxid FROM pg_stat_activity WHERE pid = pg_backend_pid(); +SELECT count(*) FROM pg_gtt_relation_state(); -- none left SELECT relname, pg_relation_size(oid) FROM pg_class WHERE (relname ~ 'tmp1' OR relname ~ 'tmp2' OR relname ~ 'pg_temp_') diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 34a88b68bb8..e75ce6ec04a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -956,7 +956,6 @@ FormData_pg_statistic_ext_data FormData_pg_subscription FormData_pg_subscription_rel FormData_pg_tablespace -FormData_pg_temp_class FormData_pg_temp_index FormData_pg_transform FormData_pg_trigger @@ -1023,7 +1022,6 @@ Form_pg_statistic_ext_data Form_pg_subscription Form_pg_subscription_rel Form_pg_tablespace -Form_pg_temp_class Form_pg_temp_index Form_pg_transform Form_pg_trigger @@ -1198,6 +1196,8 @@ GroupingSet GroupingSetData GroupingSetKind GroupingSetsPath +GtrRelPhysState +GtrRelPhysStateHistory GtrSharedUsageEntry GtrSharedUsageKey GtrStorageEntry -- 2.54.0