From e29227b2c0a3ef1f6213799a1109280b4230c311 Mon Sep 17 00:00:00 2001 From: Haibo Yan Date: Tue, 8 Sep 2026 16:23:38 -0700 Subject: [PATCH 2/2] Remove pg_temp_index and gtcatcache in favor of backend-local state pg_temp_index exists for the same reason pg_temp_class did: a global temporary index has a shared pg_index row, but indisvalid needs a per-backend value, since each backend builds and maintains its own physical copy of the index and can independently succeed or fail to keep it in sync with its local heap contents. gtcatcache.c is a catalog-cache-with-deferred-flush layer built specifically to make pg_temp_index usable: without it, a backend's own writes to pg_temp_index would not reliably be visible to that backend's later scans without also being incorrectly visible to other backends. Unlike a plain boolean flag, indisvalid cannot simply be stored as a naked Oid->bool map. Every place that changes it -- CREATE INDEX, REINDEX, ALTER INDEX ... ATTACH PARTITION -- does so as part of DDL, and that change must roll back correctly if the enclosing transaction or subtransaction aborts. A local value with no rollback history would leave a query in the same backend able to use an index that Postgres itself considers not yet valid, if the DDL that revalidated it (or invalidated it) was later rolled back to a savepoint. This removes pg_temp_index and gtcatcache.c, and adds the local validity flag directly to the existing GtrUsageEntry (see the previous commit for why that struct is the right place for this), together with a small subtransaction-aware history mechanism: each transactional write pushes the previous value, tagged with the subtransaction that made the change, onto a singly-linked list, and that list is unwound on subtransaction abort and merged into the parent subtransaction on subtransaction commit. This is the same technique already used for the physical/statistics state added in the previous commit, and is what gtcatcache.c's own prev-chain was doing for pg_temp_index entries before this change. GetGlobalTempIndexValid()/SetGlobalTempIndexValid() replace the pg_temp_index tuple reads/writes; RelationInitIndexAccessInfo() and RelationGetIndexList() overlay this local state onto pg_index the same way the physical state is overlaid onto pg_class, so no other code needs to know the catalog is gone. SetGlobalTempIndexValid() does not itself request relcache invalidation: every caller already performs a pg_index insert or update in the same operation, which requests the necessary invalidation on its own, and an explicit request here would be unsafe for a newly created index whose pg_class row is not yet visible to catalog scans in the current command. A pg_gtt_index_isvalid() SQL function is added as a replacement for the visibility "LEFT JOIN pg_temp_index" gave; psql's \d and \di support are updated to use it, so per-session index validity display is unaffected. --- contrib/tcn/tcn.c | 1 - src/backend/access/common/relation.c | 8 +- src/backend/access/transam/xact.c | 6 +- src/backend/catalog/Makefile | 1 - src/backend/catalog/genbki.pl | 1 - src/backend/catalog/global_temp.c | 296 +++++- src/backend/catalog/index.c | 40 +- src/backend/catalog/meson.build | 1 - src/backend/catalog/pg_temp_index.c | 213 ----- src/backend/commands/indexcmds.c | 23 +- src/backend/commands/repack.c | 17 +- src/backend/commands/tablecmds.c | 21 +- src/backend/utils/cache/Makefile | 1 - src/backend/utils/cache/gtcatcache.c | 971 -------------------- src/backend/utils/cache/lsyscache.c | 2 +- src/backend/utils/cache/meson.build | 1 - src/backend/utils/cache/relcache.c | 60 +- src/bin/psql/describe.c | 6 +- src/include/catalog/Makefile | 1 - src/include/catalog/global_temp.h | 3 + src/include/catalog/meson.build | 1 - src/include/catalog/pg_proc.dat | 3 + src/include/catalog/pg_temp_index.h | 76 -- src/include/utils/gtcatcache.h | 44 - src/include/utils/relcache.h | 7 + src/test/isolation/expected/global-temp.out | 24 +- src/test/isolation/specs/global-temp.spec | 4 +- src/test/regress/expected/global_temp.out | 23 +- src/test/regress/expected/oidjoins.out | 1 - src/test/regress/sql/global_temp.sql | 12 +- src/tools/pgindent/typedefs.list | 6 +- 31 files changed, 385 insertions(+), 1489 deletions(-) delete mode 100644 src/backend/catalog/pg_temp_index.c delete mode 100644 src/backend/utils/cache/gtcatcache.c delete mode 100644 src/include/catalog/pg_temp_index.h delete mode 100644 src/include/utils/gtcatcache.h diff --git a/contrib/tcn/tcn.c b/contrib/tcn/tcn.c index 3d5c4deeb1e..b1609225de4 100644 --- a/contrib/tcn/tcn.c +++ b/contrib/tcn/tcn.c @@ -16,7 +16,6 @@ #include "postgres.h" #include "access/htup_details.h" -#include "catalog/pg_temp_index.h" #include "commands/async.h" #include "commands/trigger.h" #include "executor/spi.h" diff --git a/src/backend/access/common/relation.c b/src/backend/access/common/relation.c index 84a09217657..c9721c34abe 100644 --- a/src/backend/access/common/relation.c +++ b/src/backend/access/common/relation.c @@ -27,7 +27,6 @@ #include "pgstat.h" #include "storage/lmgr.h" #include "storage/lock.h" -#include "utils/gtcatcache.h" #include "utils/inval.h" #include "utils/syscache.h" @@ -58,14 +57,11 @@ relation_open(Oid relationId, LOCKMODE lockmode) /* * Before opening a global temporary system catalog table, process any - * invalidated global temporary relations and flush the global temporary - * catalog caches, so that the contents of the catalogs are up to date. + * invalidated global temporary relations, so that the contents of the + * catalog are up to date. */ if (IsGlobalTempCatalogTable(relationId) && !IsBootstrapProcessingMode()) - { ProcessInvalidatedGlobalTempRelations(); - GTCatCacheFlush(); - } /* The relcache does all the real work... */ r = RelationIdGetRelation(relationId); diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index a6497752c96..aa520ff6fdc 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -65,7 +65,6 @@ #include "storage/smgr.h" #include "utils/builtins.h" #include "utils/combocid.h" -#include "utils/gtcatcache.h" #include "utils/guc.h" #include "utils/inval.h" #include "utils/memutils.h" @@ -2351,12 +2350,9 @@ CommitTransaction(void) * Process any invalidated global temporary relations, dealing with any * that were dropped by other backends. This needs to be done before any * ON COMMIT handling, so that we don't try to perform ON COMMIT actions - * on deleted global temporary tables. While at it, flush the global - * temporary catalog caches, so that any new entries are written out - * before we commit. + * on deleted global temporary tables. */ ProcessInvalidatedGlobalTempRelations(); - GTCatCacheFlush(); /* * Let ON COMMIT management do its thing (must happen after closing diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index 31bb94a4b7e..0fb085fd8ee 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_index.o \ pg_type.o \ storage.o \ toasting.o diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl index 8c578dd9bba..4727fa2ce25 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_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 73a2c6d041c..b6cc14df324 100644 --- a/src/backend/catalog/global_temp.c +++ b/src/backend/catalog/global_temp.c @@ -62,7 +62,6 @@ #include "access/xlogutils.h" #include "catalog/global_temp.h" #include "catalog/indexing.h" -#include "catalog/pg_temp_index.h" #include "catalog/storage.h" #include "commands/sequence.h" #include "commands/tablecmds.h" @@ -75,7 +74,6 @@ #include "storage/shmem.h" #include "storage/subsystems.h" #include "utils/fmgroids.h" -#include "utils/gtcatcache.h" #include "utils/inval.h" #include "utils/memutils.h" #include "utils/syscache.h" @@ -128,9 +126,9 @@ static bool eoxact_storage_list_overflowed = false; * * 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. + * (sub)transaction rollback. Each node is tagged with the subtransaction + * that made the change it displaced, so that AtEOSubXact_RelPhysCleanup() + * can tell which nodes belong to an aborting subtransaction. */ typedef struct GtrRelPhysStateHistory { @@ -139,6 +137,20 @@ typedef struct GtrRelPhysStateHistory struct GtrRelPhysStateHistory *prev; } GtrRelPhysStateHistory; +/* + * GtrIndexValidHistory + * + * A saved previous value of indisvalid, kept so that a transactional + * change (see SetGlobalTempIndexValid()) can be undone by (sub)transaction + * rollback. Same technique as GtrRelPhysStateHistory above. + */ +typedef struct GtrIndexValidHistory +{ + bool indisvalid; + SubTransactionId subid; + struct GtrIndexValidHistory *prev; +} GtrIndexValidHistory; + /* * gtr_local_usage * @@ -163,6 +175,22 @@ typedef struct GtrUsageEntry GtrRelPhysState phys; SubTransactionId physsubid; GtrRelPhysStateHistory *physprev; + + /* + * Local index validity, valid only for RELKIND_INDEX and + * RELKIND_PARTITIONED_INDEX. Records whether *this backend's* physical + * copy of the index (global temporary indexes have per-backend physical + * storage) is built and in sync with this backend's local heap contents + * --- a session-local fact, independent of whether the shared index + * *definition* (pg_index.indisvalid) is healthy. Tracked the same way as + * the physical state above: indvalidsubid/indvalidprev provide + * (sub)transaction rollback for transactional changes; there is no + * non-transactional/in-place variant, since every existing caller changes + * indisvalid transactionally. + */ + bool indisvalid; + SubTransactionId indvalidsubid; + GtrIndexValidHistory *indvalidprev; } GtrUsageEntry; static HTAB *gtr_local_usage; @@ -632,6 +660,11 @@ gtr_record_usage(Oid relid, char relkind) local_entry->physsubid = InvalidSubTransactionId; local_entry->physprev = NULL; + /* No local index validity state recorded yet */ + local_entry->indisvalid = false; + local_entry->indvalidsubid = InvalidSubTransactionId; + local_entry->indvalidprev = NULL; + /* Remember the relation's relkind */ local_entry->relkind = relkind; @@ -689,10 +722,10 @@ gtr_remove_usage(Oid relid) * 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 + * AtEOXact_RelPhysCleanup()/AtEOXact_IndexValidCleanup() have 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. @@ -704,6 +737,13 @@ gtr_remove_usage(Oid relid) entry->physprev = prev->prev; pfree(prev); } + while (entry->indvalidprev != NULL) + { + GtrIndexValidHistory *prev = entry->indvalidprev; + + entry->indvalidprev = prev->prev; + pfree(prev); + } /* Remove local usage entry */ hash_search(gtr_local_usage, &relid, HASH_REMOVE, NULL); @@ -779,9 +819,6 @@ gtr_finalize_discard(void) RelationMarkInvalid(usage_entry->relid); } - /* Discard all cached pg_temp_index tuples */ - GTCatCacheDiscard(); - /* Reset tempfrozenxid and tempminmxid for this backend */ MyProc->tempfrozenxid = InvalidTransactionId; MyProc->tempminmxid = InvalidMultiXactId; @@ -792,6 +829,10 @@ static void AtEOXact_RelPhysCleanup(GtrUsageEntry *entry, bool isCommit); static void AtEOSubXact_RelPhysCleanup(GtrUsageEntry *entry, bool isCommit, SubTransactionId mySubid, SubTransactionId parentSubid); +static void AtEOXact_IndexValidCleanup(GtrUsageEntry *entry, bool isCommit); +static void AtEOSubXact_IndexValidCleanup(GtrUsageEntry *entry, bool isCommit, + SubTransactionId mySubid, + SubTransactionId parentSubid); /* * AtEOXact_UsageCleanup @@ -807,6 +848,7 @@ AtEOXact_UsageCleanup(GtrUsageEntry *entry, bool isCommit) { /* Clean up any transactional physical-state history first */ AtEOXact_RelPhysCleanup(entry, isCommit); + AtEOXact_IndexValidCleanup(entry, isCommit); /* * If the relation is no longer in use after this transaction ends, remove @@ -841,6 +883,7 @@ AtEOSubXact_UsageCleanup(GtrUsageEntry *entry, bool isCommit, { /* Clean up any transactional physical-state history first */ AtEOSubXact_RelPhysCleanup(entry, isCommit, mySubid, parentSubid); + AtEOSubXact_IndexValidCleanup(entry, isCommit, mySubid, parentSubid); /* * Did usage start in the current subtransaction? @@ -955,6 +998,89 @@ AtEOSubXact_RelPhysCleanup(GtrUsageEntry *entry, bool isCommit, } } +/* + * AtEOXact_IndexValidCleanup + * + * Clean up the local index validity 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. A no-op for a non-index relkind, since indvalidsubid is + * then always InvalidSubTransactionId. + * + * NB: this processing must be idempotent, because EOXactUsageListAdd() + * doesn't bother to prevent duplicate entries in eoxact_usage_list[]. + */ +static void +AtEOXact_IndexValidCleanup(GtrUsageEntry *entry, bool isCommit) +{ + GtrIndexValidHistory *prev = entry->indvalidprev; + + if (entry->indvalidsubid == InvalidSubTransactionId) + return; + + Assert(prev != NULL); + + if (isCommit) + { + entry->indvalidsubid = InvalidSubTransactionId; + entry->indvalidprev = NULL; + pfree(prev); + } + else + { + /* Rollback: restore the value as it was before this transaction */ + entry->indisvalid = prev->indisvalid; + entry->indvalidsubid = prev->subid; + entry->indvalidprev = prev->prev; + pfree(prev); + } +} + +/* + * AtEOSubXact_IndexValidCleanup + * + * Clean up the local index validity 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_IndexValidCleanup(GtrUsageEntry *entry, bool isCommit, + SubTransactionId mySubid, + SubTransactionId parentSubid) +{ + GtrIndexValidHistory *prev; + + if (entry->indvalidsubid != mySubid) + return; + + prev = entry->indvalidprev; + Assert(prev != NULL); + + if (isCommit) + { + /* Reparent this change to the parent subtransaction */ + entry->indvalidsubid = parentSubid; + if (prev->subid == parentSubid) + { + /* Parent already has its own checkpoint; merge into it */ + entry->indvalidprev = prev->prev; + pfree(prev); + } + } + else + { + /* Subrollback: restore the value as it was before this subxact */ + entry->indisvalid = prev->indisvalid; + entry->indvalidsubid = prev->subid; + entry->indvalidprev = prev->prev; + pfree(prev); + } +} + /* * GtrInitRelPhysState * @@ -1089,6 +1215,102 @@ SetGlobalTempRelPhysStateInPlace(Oid relid, const GtrRelPhysState *state) CacheInvalidateRelcacheByRelid(relid); } +/* + * GtrInitIndexValid + * + * Initialize the local index validity state for a global temporary index + * from the given initial value. Called when a usage record is first + * created for the index (see TrackGlobalTempRelation()), regardless of + * which backend originally created it. + * + * This initial value is not itself subject to (sub)transaction rollback + * via indvalidsubid/indvalidprev --- if the usage record's own creation is + * rolled back, the whole entry (including this initial value) disappears + * with it. + */ +static void +GtrInitIndexValid(GtrUsageEntry *entry, bool indisvalid) +{ + entry->indisvalid = indisvalid; + entry->indvalidsubid = InvalidSubTransactionId; + entry->indvalidprev = NULL; +} + +/* + * GetGlobalTempIndexValid + * + * Get this backend's local validity state for a global temporary index, + * i.e. whether this backend's own physical copy of the index is built and + * in sync with this backend's local heap contents. Returns false if the + * index has no local usage record (e.g. it's not a global temporary + * index, or this backend hasn't used it yet); in that case *indisvalid is + * left unchanged, so callers should have already initialized it from the + * shared pg_index.indisvalid. + */ +bool +GetGlobalTempIndexValid(Oid indexrelid, bool *indisvalid) +{ + GtrUsageEntry *entry; + + if (gtr_local_usage == NULL) + return false; + + entry = hash_search(gtr_local_usage, &indexrelid, HASH_FIND, NULL); + if (entry == NULL) + return false; + + *indisvalid = entry->indisvalid; + return true; +} + +/* + * SetGlobalTempIndexValid + * + * Transactionally update this backend's local validity state for a global + * temporary index already in use by this backend. The change is undone + * by (sub)transaction rollback. + * + * Note: unlike SetGlobalTempRelPhysState()/SetGlobalTempRelPhysStateInPlace(), + * this deliberately does not force a relcache invalidation for the index: + * every current caller also does its own CatalogTupleInsert/TupleUpdate on + * the shared pg_index row in the same operation, which already queues the + * standard relcache invalidation. An explicit CacheInvalidateRelcacheByRelid() + * call here would additionally be actively wrong for a brand new index + * still under construction (as in index_create()'s UpdateIndexRelation()): + * its pg_class row has not yet been made visible via CommandCounterIncrement(), + * so looking it up via syscache to build the invalidation message fails. + */ +void +SetGlobalTempIndexValid(Oid indexrelid, bool indisvalid) +{ + GtrUsageEntry *entry; + SubTransactionId mySubid = GetCurrentSubTransactionId(); + + entry = hash_search(gtr_local_usage, &indexrelid, HASH_FIND, NULL); + if (entry == NULL) + elog(ERROR, "no local state for global temporary index %u", indexrelid); + + if (entry->indvalidsubid != mySubid) + { + GtrIndexValidHistory *hist; + MemoryContext oldcontext = MemoryContextSwitchTo(TopMemoryContext); + + hist = palloc_object(GtrIndexValidHistory); + hist->indisvalid = entry->indisvalid; + hist->subid = entry->indvalidsubid; + hist->prev = entry->indvalidprev; + + entry->indvalidprev = hist; + entry->indvalidsubid = mySubid; + + MemoryContextSwitchTo(oldcontext); + + EOXactUsageListAdd(indexrelid); + } + + entry->indisvalid = indisvalid; +} + /* * GetGlobalTempMinFrozenXids * @@ -1394,7 +1616,7 @@ TrackGlobalTempRelation(Relation relation) */ GtrInitRelPhysState(entry, relation->rd_rel); - /* For an index, also insert a pg_temp_index tuple */ + /* For an index, also initialize its local validity state */ if (relation->rd_rel->relkind == RELKIND_INDEX || relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) { @@ -1407,9 +1629,9 @@ TrackGlobalTempRelation(Relation relation) * reflect whether or not the index needs to be marked invalid * locally (if our instance of the index's table is not empty). */ - InsertPgTempIndexTuple(relation->rd_id, - relation->rd_index == NULL || - relation->rd_index->indisvalid); + GtrInitIndexValid(entry, + relation->rd_index == NULL || + relation->rd_index->indisvalid); } /* @@ -1448,11 +1670,6 @@ ForgetGlobalTempRelation(Oid relid) entry->stopped_subid = GetCurrentSubTransactionId(); EOXactUsageListAdd(relid); - /* Delete its pg_temp_index tuple, if it has one */ - if (entry->relkind == RELKIND_INDEX || - entry->relkind == RELKIND_PARTITIONED_INDEX) - DeletePgTempIndexTuple(relid); - /* Update this backend's tempfrozenxid and tempminmxid */ UpdateTempFrozenXids(); } @@ -1640,13 +1857,6 @@ ProcessInvalidatedGlobalTempRelations(void) gtr_remove_usage(relid); remove_on_commit_action(relid); - /* For an index, delete its pg_temp_index tuple, if it has one */ - if (PgTempIndexTupleExists(relid)) - { - DeletePgTempIndexTuple(relid); - tuples_deleted = true; - } - /* Delete any per-column statistics from pg_temp_statistic */ ScanKeyInit(&key[0], Anum_pg_temp_statistic_starelid, @@ -1841,9 +2051,6 @@ AtEOXact_GlobalTempRelation(bool isCommit) } discard_subid = InvalidSubTransactionId; - /* Clean up global temporary catalog caches */ - AtEOXact_GTCatCache(isCommit); - /* * Finally, on commit, update tempfrozenxid and tempminmxid, if requested. * This must be done after AtEOXact_UsageCleanup() has run for every entry @@ -1942,9 +2149,6 @@ AtEOSubXact_GlobalTempRelation(bool isCommit, SubTransactionId mySubid, discard_subid = InvalidSubTransactionId; } - /* Clean up global temporary catalog caches */ - AtEOSubXact_GTCatCache(isCommit, mySubid, parentSubid); - /* Don't reset the lists; we still need more cleanup later */ } @@ -2177,3 +2381,27 @@ pg_gtt_relation_state(PG_FUNCTION_ARGS) return (Datum) 0; } + +/* + * pg_gtt_index_isvalid + * + * SQL-callable function exposing this backend's local validity state for + * a single global temporary index, i.e. whether this backend's own + * physical copy of the index is built and in sync with this backend's + * local heap contents. Returns NULL if this backend has no local state + * for the given index (e.g. it's not a global temporary index, or this + * backend hasn't used it yet), in which case callers should fall back to + * the shared pg_index.indisvalid. + */ +PG_FUNCTION_INFO_V1(pg_gtt_index_isvalid); +Datum +pg_gtt_index_isvalid(PG_FUNCTION_ARGS) +{ + Oid indexrelid = PG_GETARG_OID(0); + bool indisvalid; + + if (GetGlobalTempIndexValid(indexrelid, &indisvalid)) + PG_RETURN_BOOL(indisvalid); + + PG_RETURN_NULL(); +} diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 9c02e014206..871f8414425 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -50,7 +50,6 @@ #include "catalog/pg_opclass.h" #include "catalog/pg_operator.h" #include "catalog/pg_tablespace.h" -#include "catalog/pg_temp_index.h" #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" #include "catalog/storage.h" @@ -681,20 +680,11 @@ UpdateIndexRelation(Oid indexoid, /* * For an index on a global temporary table, TrackGlobalTempRelation() - * will have inserted a pg_temp_index tuple with indisvalid = true. If - * the index is actually not valid, fix that now. + * will have initialized its local validity state to true. If the index + * is actually not valid, fix that now. */ if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && !isvalid) - { - tuple = GetPgTempIndexTuple(indexoid); - if (!HeapTupleIsValid(tuple)) - elog(ERROR, "cache lookup failed for global temp index %u", indexoid); - - ((Form_pg_temp_index) GETSTRUCT(tuple))->indisvalid = isvalid; - - UpdatePgTempIndexTuple(indexoid, tuple); - heap_freetuple(tuple); - } + SetGlobalTempIndexValid(indexoid, isvalid); } @@ -3991,26 +3981,28 @@ reindex_index(const ReindexStmt *stmt, Oid indexId, { Relation pg_index; HeapTuple indexTuple; - HeapTuple temp_indexTuple; Form_pg_index indexForm; - Form_pg_temp_index temp_indexForm; + bool is_gtt = RELATION_IS_GLOBAL_TEMP(heapRelation); + bool local_indisvalid; bool index_bad; /* * For a global temporary index, we update indisvalid in both pg_index - * and pg_temp_index, so that the change applies to this session and - * all future sessions. + * and this backend's local validity state, so that the change applies + * to this session and all future sessions. */ pg_index = table_open(IndexRelationId, RowExclusiveLock); - indexTuple = GetPgIndexAndPgTempIndexTuples(indexId, &temp_indexTuple, - true); + indexTuple = SearchSysCacheCopy1(INDEXRELID, ObjectIdGetDatum(indexId)); if (!HeapTupleIsValid(indexTuple)) elog(ERROR, "cache lookup failed for index %u", indexId); indexForm = (Form_pg_index) GETSTRUCT(indexTuple); - temp_indexForm = (Form_pg_temp_index) GETSTRUCT_SAFE(temp_indexTuple); - index_bad = (!GetEffective_indisvalid(indexForm, temp_indexForm) || + local_indisvalid = indexForm->indisvalid; + if (is_gtt) + (void) GetGlobalTempIndexValid(indexId, &local_indisvalid); + + index_bad = (!local_indisvalid || !indexForm->indisready || !indexForm->indislive); if (index_bad || @@ -4021,13 +4013,11 @@ reindex_index(const ReindexStmt *stmt, Oid indexId, else if (index_bad) indexForm->indcheckxmin = true; indexForm->indisvalid = true; - if (temp_indexForm != NULL) - temp_indexForm->indisvalid = true; indexForm->indisready = true; indexForm->indislive = true; CatalogTupleUpdate(pg_index, &indexTuple->t_self, indexTuple); - if (HeapTupleIsValid(temp_indexTuple)) - UpdatePgTempIndexTuple(indexId, temp_indexTuple); + if (is_gtt) + SetGlobalTempIndexValid(indexId, true); /* * Invalidate the relcache for the table, so that after we commit diff --git a/src/backend/catalog/meson.build b/src/backend/catalog/meson.build index 8a8d9bcb287..7285ab2dfcf 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_index.c', 'pg_type.c', 'storage.c', 'toasting.c', diff --git a/src/backend/catalog/pg_temp_index.c b/src/backend/catalog/pg_temp_index.c deleted file mode 100644 index c623bd7e591..00000000000 --- a/src/backend/catalog/pg_temp_index.c +++ /dev/null @@ -1,213 +0,0 @@ -/*------------------------------------------------------------------------- - * - * pg_temp_index.c - * routines to support manipulation of the pg_temp_index relation - * - * The pg_temp_index system catalog table is a global temporary table that - * stores local overrides to the indisvalid field from the pg_index 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. - * - * Much of the code here mirrors similar code in pg_temp_class.c --- see - * the comments there for more detail. - * - * Copyright (c) 2026, PostgreSQL Global Development Group - * - * IDENTIFICATION - * src/backend/catalog/pg_temp_index.c - * - *------------------------------------------------------------------------- - */ -#include "postgres.h" - -#include "access/htup_details.h" -#include "catalog/pg_class.h" -#include "catalog/pg_temp_index.h" -#include "utils/gtcatcache.h" -#include "utils/lsyscache.h" -#include "utils/memutils.h" -#include "utils/syscache.h" - -/* Cached copy of the pg_temp_index tuple descriptor */ -static TupleDesc pg_temp_index_tupdesc = NULL; - -/* - * get_pg_temp_index_tupdesc - * - * Returns the tuple descriptor for pg_temp_index. - */ -static TupleDesc -get_pg_temp_index_tupdesc(void) -{ - /* Build the tuple descriptor the first time through */ - if (pg_temp_index_tupdesc == NULL) - { - MemoryContext oldcontext; - TupleDesc tupdesc; - - oldcontext = MemoryContextSwitchTo(TopMemoryContext); - - tupdesc = CreateTemplateTupleDesc(Natts_pg_temp_index); - TupleDescInitEntry(tupdesc, - (AttrNumber) Anum_pg_temp_index_indexrelid, - "indexrelid", OIDOID, -1, 0); - TupleDescInitEntry(tupdesc, - (AttrNumber) Anum_pg_temp_index_indisvalid, - "indisvalid", BOOLOID, -1, 0); - TupleDescFinalize(tupdesc); - - MemoryContextSwitchTo(oldcontext); - - /* Cache it for all future use */ - pg_temp_index_tupdesc = tupdesc; - } - return pg_temp_index_tupdesc; -} - -/* - * PgTempIndexTupleExists - * - * Test if a pg_temp_index tuple for a global temporary index exists. - */ -bool -PgTempIndexTupleExists(Oid indexrelid) -{ - return GTCatCacheTupleExists(PG_TEMP_INDEX, indexrelid); -} - -/* - * GetPgTempIndexTuple - * - * Get the pg_temp_index tuple for a global temporary index relation. - * - * Returns NULL if the tuple could not be found. Otherwise, the tuple - * returned should be freed with heap_freetuple(). - */ -HeapTuple -GetPgTempIndexTuple(Oid indexrelid) -{ - return GTCatCacheSearch(PG_TEMP_INDEX, indexrelid); -} - -/* - * InsertPgTempIndexTuple - * - * Insert a new pg_temp_index tuple for a global temporary index relation. - * - * This is called when a global temporary index relation is created or - * accessed for the first time in a session. - * - * 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 -InsertPgTempIndexTuple(Oid indexrelid, bool indisvalid) -{ - Datum values[Natts_pg_temp_index]; - bool nulls[Natts_pg_temp_index] = {0}; - - values[Anum_pg_temp_index_indexrelid - 1] = ObjectIdGetDatum(indexrelid); - values[Anum_pg_temp_index_indisvalid - 1] = BoolGetDatum(indisvalid); - - GTCatCacheTupleInsert(PG_TEMP_INDEX, indexrelid, RELKIND_INDEX, - get_pg_temp_index_tupdesc(), values, nulls); -} - -/* - * UpdatePgTempIndexTuple - * - * Update the pg_temp_index tuple for a global temporary index relation. - */ -void -UpdatePgTempIndexTuple(Oid indexrelid, HeapTuple newtuple) -{ - GTCatCacheTupleUpdate(PG_TEMP_INDEX, indexrelid, newtuple); -} - -/* - * DeletePgTempIndexTuple - * - * Delete the pg_temp_index tuple for a global temporary index relation. - */ -void -DeletePgTempIndexTuple(Oid indexrelid) -{ - GTCatCacheTupleDelete(PG_TEMP_INDEX, indexrelid); -} - -/* - * GetPgIndexAndPgTempIndexTuples - * - * Get the pg_index tuple for an index relation, and if it's a global - * temporary index relation, also get the corresponding pg_temp_index tuple, - * if present. - * - * Returns NULL if the pg_index tuple could not be found. Otherwise, the - * tuple(s) returned should be freed with heap_freetuple(). - */ -HeapTuple -GetPgIndexAndPgTempIndexTuples(Oid indexrelid, HeapTuple *temp_tuple, - bool check_temp) -{ - HeapTuple tuple; - - /* Get a copy of the pg_index tuple */ - tuple = SearchSysCacheCopy1(INDEXRELID, ObjectIdGetDatum(indexrelid)); - - if (HeapTupleIsValid(tuple) && - rel_is_global_temp(((Form_pg_index) GETSTRUCT(tuple))->indexrelid)) - { - /* Get the pg_temp_index tuple, and check it exists, if requested */ - *temp_tuple = GetPgTempIndexTuple(indexrelid); - if (check_temp && !HeapTupleIsValid(*temp_tuple)) - elog(ERROR, "cache lookup failed for global temp index %u", indexrelid); - } - else - *temp_tuple = NULL; - - return tuple; -} - -/* - * GetEffectivePgIndexTuple - * - * Get the effective pg_index tuple for an index relation. - * - * This will fetch the pg_index tuple for the relation and then, if it's a - * global temporary relation, fetch the corresponding pg_temp_index tuple and - * use the values in it to override the corresponding values in the pg_index - * tuple (currently just indisvalid). Thus, the result represents the - * effective state of the index relation in this session. - * - * For a global temporary index relation that has not yet been opened in this - * session, there will be no pg_temp_index tuple, and the pg_index tuple will - * be returned unchanged. - * - * Returns NULL if the pg_index tuple could not be found. Otherwise, the - * tuple returned should be freed with heap_freetuple(). - */ -HeapTuple -GetEffectivePgIndexTuple(Oid indexrelid) -{ - HeapTuple tuple; - HeapTuple temp_tuple; - Form_pg_index indexform; - Form_pg_temp_index temp_indexform; - - /* - * Get the pg_index and pg_temp_index tuples. If we have the latter, use - * it to update the former. - */ - tuple = GetPgIndexAndPgTempIndexTuples(indexrelid, &temp_tuple, false); - - if (HeapTupleIsValid(tuple) && HeapTupleIsValid(temp_tuple)) - { - indexform = (Form_pg_index) GETSTRUCT(tuple); - temp_indexform = (Form_pg_temp_index) GETSTRUCT(temp_tuple); - indexform->indisvalid = temp_indexform->indisvalid; - } - return tuple; -} diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 41b140a6d6d..3da846869e0 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -38,7 +38,6 @@ #include "catalog/pg_namespace.h" #include "catalog/pg_opclass.h" #include "catalog/pg_tablespace.h" -#include "catalog/pg_temp_index.h" #include "catalog/pg_type.h" #include "commands/comment.h" #include "commands/defrem.h" @@ -1598,34 +1597,26 @@ DefineIndex(ParseState *pstate, if (invalidate_parent) { Relation pg_index = table_open(IndexRelationId, RowExclusiveLock); - HeapTuple tup, - temp_tup; + HeapTuple tup; Form_pg_index form; - Form_pg_temp_index temp_form; /* * For a global temporary index, we update indisvalid in both - * pg_index and pg_temp_index, so that the change applies to - * this session and all future sessions. + * pg_index and this backend's local validity state, so that + * the change applies to this session and all future sessions. */ - tup = GetPgIndexAndPgTempIndexTuples(indexRelationId, - &temp_tup, true); + tup = SearchSysCacheCopy1(INDEXRELID, + ObjectIdGetDatum(indexRelationId)); if (!HeapTupleIsValid(tup)) elog(ERROR, "cache lookup failed for index %u", indexRelationId); form = (Form_pg_index) GETSTRUCT(tup); - temp_form = (Form_pg_temp_index) GETSTRUCT_SAFE(temp_tup); form->indisvalid = false; - if (temp_form != NULL) - temp_form->indisvalid = false; CatalogTupleUpdate(pg_index, &tup->t_self, tup); - if (HeapTupleIsValid(temp_tup)) - { - UpdatePgTempIndexTuple(indexRelationId, temp_tup); - heap_freetuple(temp_tup); - } + if (RELATION_IS_GLOBAL_TEMP(rel)) + SetGlobalTempIndexValid(indexRelationId, false); heap_freetuple(tup); table_close(pg_index, RowExclusiveLock); diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 7975cdf1313..c298cffcee0 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_index.h" #include "catalog/toasting.h" #include "commands/defrem.h" #include "commands/progress.h" @@ -816,6 +815,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal) Form_pg_index indexForm; Relation pg_index; ListCell *index; + bool is_gtt = RELATION_IS_GLOBAL_TEMP(rel); Assert(rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE); @@ -836,15 +836,16 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal) foreach(index, RelationGetIndexList(rel)) { Oid thisIndexOid = lfirst_oid(index); - HeapTuple temp_indexTuple; - Form_pg_temp_index temp_indexForm; + bool local_indisvalid; - indexTuple = GetPgIndexAndPgTempIndexTuples(thisIndexOid, - &temp_indexTuple, false); + indexTuple = SearchSysCacheCopy1(INDEXRELID, ObjectIdGetDatum(thisIndexOid)); if (!HeapTupleIsValid(indexTuple)) elog(ERROR, "cache lookup failed for index %u", thisIndexOid); indexForm = (Form_pg_index) GETSTRUCT(indexTuple); - temp_indexForm = (Form_pg_temp_index) GETSTRUCT_SAFE(temp_indexTuple); + + local_indisvalid = indexForm->indisvalid; + if (is_gtt) + (void) GetGlobalTempIndexValid(thisIndexOid, &local_indisvalid); /* * Unset the bit if set. We know it's wrong because we checked this @@ -858,7 +859,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal) else if (thisIndexOid == indexOid) { /* this was checked earlier, but let's be real sure */ - if (!GetEffective_indisvalid(indexForm, temp_indexForm)) + if (!local_indisvalid) elog(ERROR, "cannot cluster on invalid index %u", indexOid); indexForm->indisclustered = true; CatalogTupleUpdate(pg_index, &indexTuple->t_self, indexTuple); @@ -868,8 +869,6 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal) InvalidOid, is_internal); heap_freetuple(indexTuple); - if (HeapTupleIsValid(temp_indexTuple)) - heap_freetuple(temp_indexTuple); } table_close(pg_index, RowExclusiveLock); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 750ff5997c8..d209f74d90a 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_index.h" #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" #include "catalog/storage.h" @@ -22787,35 +22786,27 @@ validatePartitionedIndex(Relation partedIdx, Relation partedTbl) { Relation idxRel; HeapTuple indTup; - HeapTuple temp_indTup; Form_pg_index indexForm; - Form_pg_temp_index temp_indexForm; /* * For a global temporary index, we update indisvalid in both pg_index - * and pg_temp_index, so that the change applies to this session and - * all future sessions. + * and this backend's local validity state, so that the change applies + * to this session and all future sessions. */ idxRel = table_open(IndexRelationId, RowExclusiveLock); - indTup = GetPgIndexAndPgTempIndexTuples(RelationGetRelid(partedIdx), - &temp_indTup, true); + indTup = SearchSysCacheCopy1(INDEXRELID, + ObjectIdGetDatum(RelationGetRelid(partedIdx))); if (!HeapTupleIsValid(indTup)) elog(ERROR, "cache lookup failed for index %u", RelationGetRelid(partedIdx)); indexForm = (Form_pg_index) GETSTRUCT(indTup); - temp_indexForm = (Form_pg_temp_index) GETSTRUCT_SAFE(temp_indTup); indexForm->indisvalid = true; - if (temp_indexForm != NULL) - temp_indexForm->indisvalid = true; updated = true; CatalogTupleUpdate(idxRel, &indTup->t_self, indTup); - if (HeapTupleIsValid(temp_indTup)) - { - UpdatePgTempIndexTuple(RelationGetRelid(partedIdx), temp_indTup); - heap_freetuple(temp_indTup); - } + if (RELATION_IS_GLOBAL_TEMP(partedTbl)) + SetGlobalTempIndexValid(RelationGetRelid(partedIdx), true); table_close(idxRel, RowExclusiveLock); heap_freetuple(indTup); diff --git a/src/backend/utils/cache/Makefile b/src/backend/utils/cache/Makefile index 8cda8909e1d..77b3e1a037b 100644 --- a/src/backend/utils/cache/Makefile +++ b/src/backend/utils/cache/Makefile @@ -17,7 +17,6 @@ OBJS = \ catcache.o \ evtcache.o \ funccache.o \ - gtcatcache.o \ inval.o \ lsyscache.o \ partcache.o \ diff --git a/src/backend/utils/cache/gtcatcache.c b/src/backend/utils/cache/gtcatcache.c deleted file mode 100644 index deed83585f6..00000000000 --- a/src/backend/utils/cache/gtcatcache.c +++ /dev/null @@ -1,971 +0,0 @@ -/*------------------------------------------------------------------------- - * - * gtcatcache.c - * Global temporary catalog cache. - * - * This caches of the contents of selected global temporary catalog tables, - * holding details about all global temporary relations in use. - * - * Since global temporary relations are reset on backend exit, the contents - * of these catalogs are themselves temporary. Additionally, all data is - * local to this session, and is never invalidated by another session - * (except if another session drops a global temporary relation, which is - * handled by ProcessInvalidatedGlobalTempRelations() in global_temp.c). - * Therefore, tuples added to these caches are kept until the end of the - * session, unless explicitly deleted. - * - * In addition, the contents of these caches are regarded as the master - * copies of the data --- tuples added to the caches are not written to the - * database immediately, but instead, are only written out when necessary. - * Tuples in the database are updated from the contents of these caches, - * not the other way round. This requires all reads and writes to these - * global temporary system catalogs by backend code to go through this API. - * - * One reason for this design is that on a hot standby, or when operating - * in parallel mode, we cannot write directly to the catalog tables, but we - * may still open global temporary relations, so we must rely solely on the - * in-cache tuples. - * - * Another reason is that tuples for global temporary sequences must be - * inserted non-transactionally, but any tuple written to the database - * might be removed by rollback, so we may need to re-insert a database - * 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 pg_temp_index catalog table. - * - * Copyright (c) 2026, PostgreSQL Global Development Group - * - * IDENTIFICATION - * src/backend/utils/cache/gtcatcache.c - * - *------------------------------------------------------------------------- - */ -#include "postgres.h" - -#include "access/genam.h" -#include "access/htup_details.h" -#include "access/multixact.h" -#include "access/parallel.h" -#include "access/table.h" -#include "access/xact.h" -#include "access/xlog.h" -#include "catalog/indexing.h" -#include "catalog/pg_class.h" -#include "catalog/pg_temp_index.h" -#include "utils/fmgroids.h" -#include "utils/gtcatcache.h" -#include "utils/hsearch.h" -#include "utils/memutils.h" -#include "utils/syscache.h" - -/* - * GTCatCacheEntry - * - * A cache entry holding a single global temporary catalog table tuple. All - * cache entries are keyed by relation OID (these caches are only used for - * global temporary catalog tables whose primary key is the global temporary - * relation's OID). - * - * If a cache entry is edited in a transaction or subtransaction, a linked - * list of previous versions of the entry is built, allowing it to be - * restored on rollback or subrollback. - */ -typedef struct GTCatCacheEntry -{ - Oid relid; /* lookup key: OID the tuple is for */ - HeapTuple tuple; /* cached copy of the tuple */ - bool written; /* has tuple been written to the database? */ - bool deleted; /* has tuple been deleted? */ - SubTransactionId subid; /* subxact ID of insert/update/delete/flush */ - struct GTCatCacheEntry *prev; /* previous version, for (sub)rollback */ -} GTCatCacheEntry; - -/* - * A cache entry needs to be flushed if it has been written to the database - * and subsequently deleted, or it it has not been written to the database and - * not deleted. We don't need to worry about updates, because all updates are - * written to the database immediately. - */ -#define CACHE_ENTRY_NEEDS_FLUSH(entry) ((entry)->written == (entry)->deleted) - -/* - * GTCatCache - * - * A single global temporary catalog cache. - */ -typedef struct GTCatCache -{ - char *name; /* cache name, for debugging purposes */ - Oid catalog_relid; /* OID of underlying catalog table */ - Oid index_relid; /* OID of catalog table's OID index */ - AttrNumber key_attno; /* attno of catalog's key (OID) column */ - SysCacheIdentifier cacheid; /* catalog's syscache ID */ - HTAB *hashtable; /* hash table for catalog tuples */ - - /* - * List of cache entries that (might) need AtEOXact cleanup work. As with - * the relcache's eoxact_list[], this list intentionally has limited size, - * and we switch to a full hash table traversal if the list overflows. - */ -#define MAX_EOXACT_LIST 32 - Oid eoxact_list[MAX_EOXACT_LIST]; - int eoxact_list_len; - bool eoxact_list_overflowed; -} GTCatCache; - -#define EOXactListAdd(cache, relid) \ - do { \ - if ((cache)->eoxact_list_len < MAX_EOXACT_LIST) \ - (cache)->eoxact_list[(cache)->eoxact_list_len++] = (relid); \ - else \ - (cache)->eoxact_list_overflowed = true; \ - } while (0) - -/* Do we have any entries that need to be flushed to the database? */ -static bool have_entries_to_flush; - -/* Are we currently flushing entries (used to prevent infinite recursion) */ -static bool flushing_entries; - -/* Memory context for all cached tuples */ -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_INDEX */ - { - .name = "pg_temp_index cache", - .catalog_relid = TempIndexRelationId, - .index_relid = TempIndexRelidIndexId, - .key_attno = Anum_pg_temp_index_indexrelid, - .cacheid = TEMPINDEXRELID, - .hashtable = NULL, - .eoxact_list_len = 0, - .eoxact_list_overflowed = false, - }, -}; - -/* - * can_flush_catalogs - * - * Returns true if we can flush catalog entries to the database; false if the - * database should be considered read-only. - */ -static inline bool -can_flush_catalogs(void) -{ - /* Prevent infinite recursion */ - if (flushing_entries) - return false; - - /* - * A hot standby may open global temporary relations, creating global - * temporary catalog entries, but it can never write them out. - */ - if (RecoveryInProgress()) - return false; - - /* Similarly, while in parallel mode, the database is read-only */ - if (IsInParallelMode() || IsParallelWorker()) - return false; - - return true; -} - -/* - * initialize_cache - * - * Lazily initialize the specified global temporary catalog cache. - */ -static void -initialize_cache(GTCatCache *cache) -{ - /* Create the cache's hash table, if we haven't done so already */ - if (cache->hashtable == NULL) - { - HASHCTL ctl; - - ctl.keysize = sizeof(Oid); - ctl.entrysize = sizeof(GTCatCacheEntry); - - cache->hashtable = hash_create(cache->name, 128, &ctl, - HASH_ELEM | HASH_BLOBS); - } - - /* Create the tuple memory context, if we haven't done so already */ - if (gt_cat_cache_tupctx == NULL) - { - gt_cat_cache_tupctx = - AllocSetContextCreate(TopMemoryContext, - "Global temporary catalog cache tuples", - ALLOCSET_DEFAULT_SIZES); - } -} - -/* - * find_and_update_cache_entry - * - * Find and update the cache entry tuple for the specified relation. - */ -static GTCatCacheEntry * -find_and_update_cache_entry(GTCatCache *cache, Oid relid, HeapTuple newtuple, - bool update_in_place) -{ - SubTransactionId mySubid = GetCurrentSubTransactionId(); - GTCatCacheEntry *entry; - MemoryContext oldcontext; - - /* Find the cache entry; must exist */ - if (cache->hashtable == NULL || - (entry = hash_search(cache->hashtable, &relid, HASH_FIND, NULL)) == NULL) - elog(ERROR, "cache lookup failed for global temp relation %u", relid); - - /* Should not have been deleted */ - if (entry->deleted) - elog(ERROR, "cache entry for global temp relation %u was deleted", relid); - - Assert(HeapTupleIsValid(entry->tuple)); - - /* - * Update the cache entry, saving a copy for rollback, if necessary. - * - * An in-place update of a database tuple is non-transactional (it isn't - * affected by rollback), except if the tuple has already been updated in - * the normal way in the same transaction, in which case rollback will - * undo both the normal update and the in-place update. Since we want to - * keep our cache entry in sync with the database tuple, we must do an - * in-place update of the cache entry in the same way, by updating the - * most recent copy of the tuple, without saving a copy for rollback. - */ - oldcontext = MemoryContextSwitchTo(gt_cat_cache_tupctx); - - if (entry->subid != mySubid && !update_in_place) - { - GTCatCacheEntry *save_entry; - - save_entry = palloc_object(GTCatCacheEntry); - save_entry->relid = entry->relid; - save_entry->tuple = entry->tuple; - save_entry->written = entry->written; - save_entry->deleted = entry->deleted; - save_entry->subid = entry->subid; - save_entry->prev = entry->prev; - - entry->subid = mySubid; - entry->prev = save_entry; - - /* Flag the entry as needing eoxact cleanup */ - EOXactListAdd(cache, relid); - } - else - heap_freetuple(entry->tuple); - - entry->tuple = heap_copytuple(newtuple); - - MemoryContextSwitchTo(oldcontext); - - return entry; -} - -/* - * flush_cache_entries - * - * Flush all cache entries to their respective database catalogs, inserting - * new entries, and removing deleted entries. We needn't worry about updated - * entries, because all updates are written to the database immediately. - */ -static void -flush_cache_entries(void) -{ - SubTransactionId mySubid = GetCurrentSubTransactionId(); - Relation rel[NUM_GT_CAT_CACHES]; - CatalogIndexState indstate[NUM_GT_CAT_CACHES]; - bool db_updated = false; - - /* Prevent infinite recursion while flushing */ - Assert(!flushing_entries); - flushing_entries = true; - - /* - * Check whether we actually have any cache entries to flush. This is - * worth doing, because have_entries_to_flush may be a false positive - * after (sub)rollback, and we don't want to create global temporary - * catalog entries unless we actually need to. - */ - have_entries_to_flush = false; - - for (int cacheId = 0; cacheId < NUM_GT_CAT_CACHES; cacheId++) - { - GTCatCache *cache = >_cat_cache[cacheId]; - - if (cache->hashtable != NULL) - { - HASH_SEQ_STATUS status; - GTCatCacheEntry *entry; - - hash_seq_init(&status, cache->hashtable); - while ((entry = hash_seq_search(&status)) != NULL) - { - if (CACHE_ENTRY_NEEDS_FLUSH(entry)) - { - have_entries_to_flush = true; - hash_seq_term(&status); - break; - } - } - if (have_entries_to_flush) - break; - } - } - - if (!have_entries_to_flush) - { - flushing_entries = false; - return; - } - - /* - * Open the catalog tables and their indexes for all the caches. We do - * this before anything else, because doing so might lead to additional - * cache entries being inserted, which we would like to write out too. - */ - for (int cacheId = 0; cacheId < NUM_GT_CAT_CACHES; cacheId++) - { - GTCatCache *cache = >_cat_cache[cacheId]; - - rel[cacheId] = table_open(cache->catalog_relid, RowExclusiveLock); - indstate[cacheId] = CatalogOpenIndexes(rel[cacheId]); - } - - /* - * For each cache, write out all entries not already written, and delete - * any database tuples for entries written and marked as deleted. - */ - for (int cacheId = 0; cacheId < NUM_GT_CAT_CACHES; cacheId++) - { - GTCatCache *cache = >_cat_cache[cacheId]; - - if (cache->hashtable != NULL) - { - HASH_SEQ_STATUS status; - GTCatCacheEntry *entry; - - hash_seq_init(&status, cache->hashtable); - while ((entry = hash_seq_search(&status)) != NULL) - { - /* Ignore entries that don't need flushing */ - if (!CACHE_ENTRY_NEEDS_FLUSH(entry)) - continue; - - /* Delete or insert the tuple, as necessary */ - if (entry->deleted) - { - HeapTuple tuple; - - tuple = SearchSysCache1(cache->cacheid, - ObjectIdGetDatum(entry->relid)); - if (HeapTupleIsValid(tuple)) - { - CatalogTupleDelete(rel[cacheId], &tuple->t_self); - ReleaseSysCache(tuple); - } - } - else - CatalogTupleInsertWithInfo(rel[cacheId], entry->tuple, - indstate[cacheId]); - - /* - * Update the entry's written status, saving a copy for - * rollback, if necessary. - */ - if (entry->subid != mySubid) - { - MemoryContext oldcontext; - GTCatCacheEntry *save_entry; - - oldcontext = MemoryContextSwitchTo(gt_cat_cache_tupctx); - - save_entry = palloc_object(GTCatCacheEntry); - save_entry->relid = entry->relid; - save_entry->tuple = heap_copytuple(entry->tuple); - save_entry->written = entry->written; - save_entry->deleted = entry->deleted; - save_entry->subid = entry->subid; - save_entry->prev = entry->prev; - - entry->subid = mySubid; - entry->prev = save_entry; - - /* Flag the entry as needing eoxact cleanup */ - EOXactListAdd(cache, entry->relid); - - MemoryContextSwitchTo(oldcontext); - } - - entry->written = !entry->deleted; - db_updated = true; - } - } - } - - /* If we made any changes, make them visible */ - if (db_updated) - CommandCounterIncrement(); - - /* Tidy up */ - for (int cacheId = 0; cacheId < NUM_GT_CAT_CACHES; cacheId++) - { - CatalogCloseIndexes(indstate[cacheId]); - table_close(rel[cacheId], RowExclusiveLock); - } - have_entries_to_flush = false; - flushing_entries = false; -} - -/* - * AtEOXact_GTCatCacheEntryCleanup - * - * Clean up a single cache entry at main-transaction commit or abort. - * - * NB: this processing must be idempotent, because EOXactListAdd() doesn't - * bother to prevent duplicate entries in eoxact_list[]. - */ -static void -AtEOXact_GTCatCacheEntryCleanup(GTCatCache *cache, GTCatCacheEntry *entry, - bool isCommit) -{ - /* - * Was the entry inserted, updated, deleted, or flushed in this - * transaction? - * - * On commit, reset the subid, marking it as no longer belonging to a - * transaction, and discard any previous copy of the entry that was saved - * in case of rollback. If the tuple has been deleted in both the cache - * and the database, the cache entry is no longer needed, and is removed. - * - * On rollback of an update, delete, or flush, restore the saved copy - * reflecting the state of the entry prior to the transaction. - * - * Otherwise (rollback of an insert), the tuple no longer exists, and has - * been removed from the database, so remove the cache entry. - */ - if (entry->subid != InvalidSubTransactionId) - { - GTCatCacheEntry *prev = entry->prev; - - /* - * If there's a saved copy, it should be the version that existed - * prior to this transaction. - * - * Note: the saved copy might be marked as deleted, and have no tuple - * (the change made in this transaction might have been to flush that - * delete to the database). - */ - Assert(prev == NULL || - (prev->relid == entry->relid && - prev->subid == InvalidSubTransactionId && - prev->prev == NULL)); - - if (isCommit) - { - /* Commit of an insert, update, delete, or flush */ - entry->subid = InvalidSubTransactionId; - entry->prev = NULL; - if (prev != NULL) - heap_freetuple(prev->tuple); - if (entry->deleted && !entry->written) - hash_search(cache->hashtable, &entry->relid, HASH_REMOVE, NULL); - } - else if (prev != NULL) - { - /* Rollback of an update, delete, or flush */ - if (HeapTupleIsValid(entry->tuple)) - heap_freetuple(entry->tuple); - entry->tuple = prev->tuple; - entry->written = prev->written; - entry->deleted = prev->deleted; - entry->subid = prev->subid; - entry->prev = NULL; - if (CACHE_ENTRY_NEEDS_FLUSH(entry)) - have_entries_to_flush = true; - } - else - { - /* Rollback of an insert */ - if (HeapTupleIsValid(entry->tuple)) - heap_freetuple(entry->tuple); - if (prev != NULL) - heap_freetuple(prev->tuple); - hash_search(cache->hashtable, &entry->relid, HASH_REMOVE, NULL); - } - - /* Free previous saved copy */ - if (prev) - pfree(prev); - } -} - -/* - * AtEOSubXact_GTCatCacheEntryCleanup - * - * Clean up a single cache entry at subtransaction commit or abort. - * - * NB: this processing must be idempotent, because EOXactListAdd() doesn't - * bother to prevent duplicate entries in eoxact_list[]. - */ -static void -AtEOSubXact_GTCatCacheEntryCleanup(GTCatCache *cache, GTCatCacheEntry *entry, - bool isCommit, SubTransactionId mySubid, - SubTransactionId parentSubid) -{ - /* - * Was the entry inserted, updated, deleted, or flushed in the current - * subtransaction? - * - * On subcommit, mark it as inserted, updated, deleted, or flushed in the - * parent, instead, and discard any previous copy of the entry that was - * saved in case of subrollback, if it was for the parent subtransaction. - * - * On subrollback of an update, delete, or flush, restore the saved copy - * from the parent subtransaction (or possibly a lower level). - * - * Otherwise (subrollback of an insert), just remove the cache entry. - */ - if (entry->subid == mySubid) - { - GTCatCacheEntry *prev = entry->prev; - - /* - * If there's a saved copy, it should be a version from the parent - * subtransaction, or a lower level. - * - * Note: the saved copy might be marked as deleted, and have no tuple - * (the change made in this subtransaction might have been to flush - * that delete to the database). - */ - Assert(prev == NULL || - (prev->relid == entry->relid && prev->subid <= parentSubid)); - - if (isCommit) - { - /* Subcommit of an insert, update, delete, or flush */ - entry->subid = parentSubid; - if (prev != NULL && prev->subid == parentSubid) - { - if (HeapTupleIsValid(prev->tuple)) - heap_freetuple(prev->tuple); - entry->prev = prev->prev; - pfree(prev); - } - } - else if (prev != NULL) - { - /* Subrollback of an update, delete, or flush */ - if (HeapTupleIsValid(entry->tuple)) - heap_freetuple(entry->tuple); - entry->tuple = prev->tuple; - entry->written = prev->written; - entry->deleted = prev->deleted; - entry->subid = prev->subid; - entry->prev = prev->prev; - pfree(prev); - if (CACHE_ENTRY_NEEDS_FLUSH(entry)) - have_entries_to_flush = true; - } - else - { - /* Subrollback of an insert */ - if (HeapTupleIsValid(entry->tuple)) - heap_freetuple(entry->tuple); - hash_search(cache->hashtable, &entry->relid, HASH_REMOVE, NULL); - } - } -} - -/* - * GTCatCacheTupleExists - * - * Test if a catalog tuple for the specified relation exists. - */ -bool -GTCatCacheTupleExists(GTCatCacheIdentifier cacheId, Oid relid) -{ - GTCatCache *cache = >_cat_cache[cacheId]; - GTCatCacheEntry *entry; - - if (cache->hashtable == NULL) - return false; - - entry = hash_search(cache->hashtable, &relid, HASH_FIND, NULL); - - return entry != NULL && !entry->deleted; -} - -/* - * GTCatCacheSearch - * - * Search for the catalog tuple for the specified relation. Returns NULL if - * not found. Otherwise the tuple should be freed with heap_freetuple(). - */ -HeapTuple -GTCatCacheSearch(GTCatCacheIdentifier cacheId, Oid relid) -{ - GTCatCache *cache = >_cat_cache[cacheId]; - GTCatCacheEntry *entry; - - if (cache->hashtable == NULL) - return NULL; - - entry = hash_search(cache->hashtable, &relid, HASH_FIND, NULL); - if (entry == NULL || entry->deleted) - return NULL; - - return heap_copytuple(entry->tuple); -} - -/* - * GTCatCacheTupleInsert - * - * Insert a new catalog tuple, constructed from the specified values, for the - * specified relation. - * - * Note: The new tuple is not written to the database until GTCatCacheFlush() - * is called. - */ -void -GTCatCacheTupleInsert(GTCatCacheIdentifier cacheId, - Oid relid, char relkind, TupleDesc tupdesc, - const Datum *values, const bool *nulls) -{ - GTCatCache *cache = >_cat_cache[cacheId]; - GTCatCacheEntry *entry; - bool found; - MemoryContext oldcontext; - - initialize_cache(cache); - - /* Insert a new cache entry for the tuple */ - entry = hash_search(cache->hashtable, &relid, HASH_ENTER, &found); - if (found && !entry->deleted) - /* Should never try to re-insert a tuple for the same relid */ - elog(ERROR, "tuple for global temporary relation %u already exists", relid); - - /* Fill in entry; copy tuple to long-term tuple memory context */ - oldcontext = MemoryContextSwitchTo(gt_cat_cache_tupctx); - - entry->tuple = heap_form_tuple(tupdesc, values, nulls); - entry->written = false; - entry->deleted = false; - entry->prev = NULL; - - MemoryContextSwitchTo(oldcontext); - - /* - * For a sequence, the tuple is inserted non-transactionally, and isn't - * deleted on (sub)rollback. Otherwise, for any other relkind, mark the - * entry as created in the current subtransaction, and flag it for eoxact - * cleanup. - */ - if (relkind == RELKIND_SEQUENCE) - entry->subid = InvalidSubTransactionId; - else - { - entry->subid = GetCurrentSubTransactionId(); - EOXactListAdd(cache, relid); - } - - /* Ensure that it is written out when requested */ - have_entries_to_flush = true; -} - -/* - * GTCatCacheTupleUpdate - * - * Update a catalog tuple for the specified relation. - * - * Note: This updates both the cache entry, and the tuple in the database - * (inserting it, if it hasn't already been written out). This should not be - * called while the database is read-only. - */ -void -GTCatCacheTupleUpdate(GTCatCacheIdentifier cacheId, Oid relid, - HeapTuple newtuple) -{ - GTCatCache *cache = >_cat_cache[cacheId]; - GTCatCacheEntry *entry; - Relation rel; - - /* Find and update the cache entry for this relation */ - entry = find_and_update_cache_entry(cache, relid, newtuple, false); - - /* Update the tuple in the database to match */ - rel = table_open(cache->catalog_relid, RowExclusiveLock); - - if (entry->written) - { - HeapTuple oldtuple; - - oldtuple = SearchSysCache1(cache->cacheid, ObjectIdGetDatum(relid)); - if (!HeapTupleIsValid(oldtuple)) - elog(ERROR, "cache lookup failed for global temp relation %u", relid); - - CatalogTupleUpdate(rel, &oldtuple->t_self, newtuple); - - ReleaseSysCache(oldtuple); - } - else - { - CatalogTupleInsert(rel, newtuple); - entry->written = true; - } - - table_close(rel, RowExclusiveLock); -} - -/* - * GTCatCacheTupleUpdateInPlace - * - * Do an in-place update of a catalog tuple for the specified relation. - * - * Note: This updates both the cache entry, and the tuple in the database - * (inserting it, if it hasn't already been written out). This should not be - * called while the database is read-only. - */ -void -GTCatCacheTupleUpdateInPlace(GTCatCacheIdentifier cacheId, Oid relid, - HeapTuple newtuple) -{ - GTCatCache *cache = >_cat_cache[cacheId]; - GTCatCacheEntry *entry; - Relation rel; - - /* Find and update in place the cache entry for this relation */ - entry = find_and_update_cache_entry(cache, relid, newtuple, true); - - /* Do an in-place update of the tuple in the database */ - rel = table_open(cache->catalog_relid, RowExclusiveLock); - - if (entry->written) - { - ScanKeyData key[1]; - HeapTuple oldtuple; - void *inplace_state; - - ScanKeyInit(&key[0], - cache->key_attno, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(relid)); - - systable_inplace_update_begin(rel, cache->index_relid, true, NULL, - 1, key, &oldtuple, &inplace_state); - if (!HeapTupleIsValid(oldtuple)) - elog(ERROR, "cache lookup failed for global temp relation %u", relid); - - ItemPointerCopy(&oldtuple->t_self, &newtuple->t_self); - systable_inplace_update_finish(inplace_state, newtuple); - - heap_freetuple(oldtuple); - } - else - { - CatalogTupleInsert(rel, newtuple); - entry->written = true; - } - - table_close(rel, RowExclusiveLock); -} - -/* - * GTCatCacheTupleDelete - * - * Delete the catalog tuple for the specified relation. - * - * Note: If the database is currently read-only, the tuple will only be - * marked as deleted in the cache; it won't actually be deleted from the - * database until GTCatCacheFlush() is called. - */ -void -GTCatCacheTupleDelete(GTCatCacheIdentifier cacheId, Oid relid) -{ - GTCatCache *cache = >_cat_cache[cacheId]; - GTCatCacheEntry *entry; - - /* - * Find and update the cache entry for this relation, setting its tuple to - * NULL, and marking it as deleted. - */ - entry = find_and_update_cache_entry(cache, relid, NULL, false); - entry->deleted = true; - - /* - * If it was written to the database, delete the tuple there too, unless - * the database is currently read-only. - */ - if (entry->written && can_flush_catalogs()) - { - Relation rel; - - rel = table_open(cache->catalog_relid, RowExclusiveLock); - - /* Re-check entry->written, in case an intervening flush deleted it */ - if (entry->written) - { - HeapTuple oldtuple; - - oldtuple = SearchSysCache1(cache->cacheid, ObjectIdGetDatum(relid)); - if (!HeapTupleIsValid(oldtuple)) - elog(ERROR, "cache lookup failed for global temp relation %u", relid); - - CatalogTupleDelete(rel, &oldtuple->t_self); - ReleaseSysCache(oldtuple); - entry->written = false; - } - - table_close(rel, RowExclusiveLock); - } -} - -/* - * GTCatCacheFlush - * - * Write out any new cache entries to the database, so that the database is - * in sync with the contents of the cache (unless the database is currently - * read-only). - */ -void -GTCatCacheFlush(void) -{ - if (have_entries_to_flush && can_flush_catalogs()) - flush_cache_entries(); -} - -/* - * AtEOXact_GTCatCache - * - * Clean up global temporary catalog caches at main-transaction commit or - * abort. - */ -void -AtEOXact_GTCatCache(bool isCommit) -{ - /* Clean up each cache */ - for (int cacheId = 0; cacheId < NUM_GT_CAT_CACHES; cacheId++) - { - GTCatCache *cache = >_cat_cache[cacheId]; - GTCatCacheEntry *entry; - - /* - * Unless the eoxact_list[] overflowed, we only need to examine the - * entries listed in it. Otherwise fall back on a hash_seq_search - * scan --- see similar code in AtEOXact_RelationCache(). - */ - if (cache->eoxact_list_overflowed) - { - HASH_SEQ_STATUS status; - - hash_seq_init(&status, cache->hashtable); - while ((entry = hash_seq_search(&status)) != NULL) - { - AtEOXact_GTCatCacheEntryCleanup(cache, entry, isCommit); - } - } - else - { - for (int i = 0; i < cache->eoxact_list_len; i++) - { - entry = hash_search(cache->hashtable, &cache->eoxact_list[i], - HASH_FIND, NULL); - if (entry) - AtEOXact_GTCatCacheEntryCleanup(cache, entry, isCommit); - } - } - - /* Now we're out of the transaction and can clear eoxact_list */ - cache->eoxact_list_len = 0; - cache->eoxact_list_overflowed = false; - } - flushing_entries = false; -} - -/* - * AtEOSubXact_GTCatCache - * - * Clean up global temporary catalog caches at sub-transaction commit or - * abort. - */ -void -AtEOSubXact_GTCatCache(bool isCommit, SubTransactionId mySubid, - SubTransactionId parentSubid) -{ - /* Clean up each cache */ - for (int cacheId = 0; cacheId < NUM_GT_CAT_CACHES; cacheId++) - { - GTCatCache *cache = >_cat_cache[cacheId]; - GTCatCacheEntry *entry; - - /* - * Unless the eoxact_list[] overflowed, we only need to examine the - * entries listed in it. Otherwise fall back on a hash_seq_search - * scan. Same logic as in AtEOXact_GTCatCache(). - */ - if (cache->eoxact_list_overflowed) - { - HASH_SEQ_STATUS status; - - hash_seq_init(&status, cache->hashtable); - while ((entry = hash_seq_search(&status)) != NULL) - { - AtEOSubXact_GTCatCacheEntryCleanup(cache, entry, isCommit, - mySubid, parentSubid); - } - } - else - { - for (int i = 0; i < cache->eoxact_list_len; i++) - { - entry = hash_search(cache->hashtable, &cache->eoxact_list[i], - HASH_FIND, NULL); - if (entry) - AtEOSubXact_GTCatCacheEntryCleanup(cache, entry, - isCommit, mySubid, - parentSubid); - } - } - - /* Don't reset eoxact_list; we still need more cleanup later */ - } -} - -/* - * GTCatCacheDiscard - * - * Discard all global temporary catalog cache entries. - */ -void -GTCatCacheDiscard(void) -{ - /* Blow away the hash tables */ - for (int cacheId = 0; cacheId < NUM_GT_CAT_CACHES; cacheId++) - { - GTCatCache *cache = >_cat_cache[cacheId]; - - if (cache->hashtable != NULL) - { - hash_destroy(cache->hashtable); - cache->hashtable = NULL; - } - cache->eoxact_list_len = 0; - cache->eoxact_list_overflowed = false; - } - - /* Delete the tuple memory context */ - if (gt_cat_cache_tupctx != NULL) - { - MemoryContextDelete(gt_cat_cache_tupctx); - gt_cat_cache_tupctx = NULL; - } - - /* No entries to flush */ - have_entries_to_flush = false; -} diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 6402df39005..119a8e4375d 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -41,7 +41,6 @@ #include "catalog/pg_range.h" #include "catalog/pg_statistic.h" #include "catalog/pg_subscription.h" -#include "catalog/pg_temp_index.h" #include "catalog/pg_temp_statistic.h" #include "catalog/pg_transform.h" #include "catalog/pg_type.h" @@ -53,6 +52,7 @@ #include "utils/datum.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" +#include "utils/relcache.h" #include "utils/syscache.h" #include "utils/typcache.h" diff --git a/src/backend/utils/cache/meson.build b/src/backend/utils/cache/meson.build index d5e1a361d7f..a4435e0c3c6 100644 --- a/src/backend/utils/cache/meson.build +++ b/src/backend/utils/cache/meson.build @@ -5,7 +5,6 @@ backend_sources += files( 'catcache.c', 'evtcache.c', 'funccache.c', - 'gtcatcache.c', 'inval.c', 'lsyscache.c', 'partcache.c', diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 49198b1c74c..a29edbcbc77 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_index.h" #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" #include "catalog/schemapg.h" @@ -493,6 +492,37 @@ GetEffectivePgClassTuple(Oid relid) return tuple; } +/* + * GetEffectivePgIndexTuple + * + * Get the effective pg_index tuple for an index, without going through + * the relcache: fetches the pg_index tuple for the index and then, if + * it's a global temporary index that this backend has used, overrides + * its indisvalid field with this backend's local validity state. + * + * For a global temporary index that has not yet been used in this + * session, there is no local state, and the pg_index tuple is returned + * unchanged. + * + * Returns NULL if the pg_index tuple could not be found. Otherwise, + * the tuple returned should be freed with heap_freetuple(). + */ +HeapTuple +GetEffectivePgIndexTuple(Oid indexrelid) +{ + HeapTuple tuple; + bool indisvalid; + + tuple = SearchSysCacheCopy1(INDEXRELID, ObjectIdGetDatum(indexrelid)); + if (!HeapTupleIsValid(tuple)) + return NULL; + + if (GetGlobalTempIndexValid(indexrelid, &indisvalid)) + ((Form_pg_index) GETSTRUCT(tuple))->indisvalid = indisvalid; + + return tuple; +} + /* * AllocateRelationDesc * @@ -1560,20 +1590,15 @@ RelationInitIndexAccessInfo(Relation relation) ReleaseSysCache(tuple); /* - * For global temporary indexes, update indisvalid from pg_temp_index. - * This won't work for system catalog indexes, because pg_temp_index may - * not be loaded at this point, but they shouldn't be invalid anyway. + * For global temporary indexes, override indisvalid with this backend's + * local validity state, if it has any. */ - if (RELATION_IS_GLOBAL_TEMP(relation) && !IsCatalogRelation(relation)) + if (RELATION_IS_GLOBAL_TEMP(relation)) { - tuple = GetPgTempIndexTuple(RelationGetRelid(relation)); - if (HeapTupleIsValid(tuple)) - { - Form_pg_temp_index temp_form = (Form_pg_temp_index) GETSTRUCT(tuple); + bool indisvalid; - relation->rd_index->indisvalid = temp_form->indisvalid; - heap_freetuple(tuple); - } + if (GetGlobalTempIndexValid(RelationGetRelid(relation), &indisvalid)) + relation->rd_index->indisvalid = indisvalid; } /* @@ -5092,7 +5117,6 @@ RelationGetIndexList(Relation relation) while (HeapTupleIsValid(htup = systable_getnext(indscan))) { Form_pg_index index = (Form_pg_index) GETSTRUCT(htup); - HeapTuple temp_htup; bool indisvalid; /* @@ -5122,15 +5146,7 @@ RelationGetIndexList(Relation relation) */ indisvalid = index->indisvalid; if (RELATION_IS_GLOBAL_TEMP(relation)) - { - temp_htup = GetPgTempIndexTuple(index->indexrelid); - - if (HeapTupleIsValid(temp_htup)) - { - indisvalid = ((Form_pg_temp_index) GETSTRUCT(temp_htup))->indisvalid; - heap_freetuple(temp_htup); - } - } + (void) GetGlobalTempIndexValid(index->indexrelid, &indisvalid); /* * Remember primary key index, if any. For regular tables we do this diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 4e24e3ed971..c7a7c117396 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -2357,7 +2357,7 @@ describeOneTableDetails(const char *schemaname, if (pset.sversion >= 200000) appendPQExpBufferStr(&buf, - "COALESCE(ti.indisvalid, i.indisvalid),\n"); + "COALESCE(pg_catalog.pg_gtt_index_isvalid(i.indexrelid), i.indisvalid),\n"); else appendPQExpBufferStr(&buf, "i.indisvalid,\n"); @@ -2390,7 +2390,6 @@ describeOneTableDetails(const char *schemaname, appendPQExpBuffer(&buf, " a.amname, c2.relname, " "pg_catalog.pg_get_expr(i.indpred, i.indrelid, true)\n" "FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_am a, pg_catalog.pg_index i\n" - " LEFT JOIN pg_catalog.pg_temp_index ti ON ti.indexrelid = i.indexrelid\n" "WHERE i.indexrelid = c.oid AND c.oid = '%s' AND c.relam = a.oid\n" "AND i.indrelid = c2.oid;", oid); @@ -2492,7 +2491,7 @@ describeOneTableDetails(const char *schemaname, "i.indisclustered, "); if (pset.sversion >= 200000) appendPQExpBufferStr(&buf, - "COALESCE(ti.indisvalid, i.indisvalid), "); + "COALESCE(pg_catalog.pg_gtt_index_isvalid(i.indexrelid), i.indisvalid), "); else appendPQExpBufferStr(&buf, "i.indisvalid, "); appendPQExpBufferStr(&buf, @@ -2512,7 +2511,6 @@ describeOneTableDetails(const char *schemaname, CppAsString2(CONSTRAINT_PRIMARY) "," CppAsString2(CONSTRAINT_UNIQUE) "," CppAsString2(CONSTRAINT_EXCLUSION) "))\n" - " LEFT JOIN pg_catalog.pg_temp_index ti ON ti.indexrelid = i.indexrelid\n" "WHERE c.oid = '%s' AND c.oid = i.indrelid AND i.indexrelid = c2.oid\n" "ORDER BY i.indisprimary DESC, c2.relname;", oid); diff --git a/src/include/catalog/Makefile b/src/include/catalog/Makefile index 6175767ef17..8da3f9fe848 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_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 dec787db607..908bffdd264 100644 --- a/src/include/catalog/global_temp.h +++ b/src/include/catalog/global_temp.h @@ -63,4 +63,7 @@ extern void SetGlobalTempRelPhysStateInPlace(Oid relid, extern void GetGlobalTempMinFrozenXids(TransactionId *min_relfrozenxid, MultiXactId *min_relminmxid); +extern bool GetGlobalTempIndexValid(Oid indexrelid, bool *indisvalid); +extern void SetGlobalTempIndexValid(Oid indexrelid, bool indisvalid); + #endif /* GLOBAL_TEMP_H */ diff --git a/src/include/catalog/meson.build b/src/include/catalog/meson.build index d0a7772996c..b2da98b590c 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_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 d1270e2d00a..5db33e4db67 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5714,6 +5714,9 @@ 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 => '8111', descr => 'session-local validity state for a global temporary index in use by this backend', + proname => 'pg_gtt_index_isvalid', provolatile => 's', prorettype => 'bool', + proargtypes => 'oid', prosrc => 'pg_gtt_index_isvalid' }, { 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_index.h b/src/include/catalog/pg_temp_index.h deleted file mode 100644 index 313536ccea3..00000000000 --- a/src/include/catalog/pg_temp_index.h +++ /dev/null @@ -1,76 +0,0 @@ -/*------------------------------------------------------------------------- - * - * pg_temp_index.h - * definition of the "temporary index" system catalog (pg_temp_index) - * - * This is a global temporary system catalog table storing session-specific - * information about temporary indexes. Currently, it is only used for global - * temporary indexes. The attributes (currently just indisvalid) are a subset - * of those from pg_index, and their values take precedence over the values - * from pg_index. - * - * Portions Copyright (c) 2026, PostgreSQL Global Development Group - * - * src/include/catalog/pg_index.h - * - * NOTES - * The Catalog.pm module reads this file and derives schema - * information. - * - *------------------------------------------------------------------------- - */ -#ifndef PG_TEMP_INDEX_H -#define PG_TEMP_INDEX_H - -#include "access/htup.h" -#include "catalog/genbki.h" -#include "catalog/pg_index.h" -#include "catalog/pg_temp_index_d.h" /* IWYU pragma: export */ - -/* ---------------- - * pg_temp_index definition. cpp turns this into - * typedef struct FormData_pg_temp_index. - * ---------------- - */ -BEGIN_CATALOG_STRUCT - -CATALOG(pg_temp_index,8092,TempIndexRelationId) BKI_TEMP_RELATION -{ - Oid indexrelid BKI_LOOKUP(pg_class); /* OID of the index */ - bool indisvalid; /* is this index valid for use by queries? */ -} FormData_pg_temp_index; - -END_CATALOG_STRUCT - -/* ---------------- - * Form_pg_temp_index corresponds to a pointer to a tuple with - * the format of pg_temp_index relation. - * ---------------- - */ -typedef FormData_pg_temp_index *Form_pg_temp_index; - -DECLARE_UNIQUE_INDEX_PKEY(pg_temp_index_indexrelid_index, 8093, TempIndexRelidIndexId, pg_temp_index, btree(indexrelid oid_ops)); - -MAKE_SYSCACHE(TEMPINDEXRELID, pg_temp_index_indexrelid_index, 64); - -/* - * Get the effective value of indisvalid from pg_index and pg_temp_index tuple - * data. The value from pg_temp_index (if present) takes precedence. - */ -static inline bool -GetEffective_indisvalid(Form_pg_index f, Form_pg_temp_index tf) -{ - return tf != NULL ? tf->indisvalid : f->indisvalid; -} - -extern bool PgTempIndexTupleExists(Oid relid); -extern HeapTuple GetPgTempIndexTuple(Oid indexrelid); -extern void InsertPgTempIndexTuple(Oid indexrelid, bool indisvalid); -extern void UpdatePgTempIndexTuple(Oid indexrelid, HeapTuple newtuple); -extern void DeletePgTempIndexTuple(Oid indexrelid); -extern HeapTuple GetPgIndexAndPgTempIndexTuples(Oid indexrelid, - HeapTuple *temp_tuple, - bool check_temp); -extern HeapTuple GetEffectivePgIndexTuple(Oid indexrelid); - -#endif /* PG_TEMP_INDEX_H */ diff --git a/src/include/utils/gtcatcache.h b/src/include/utils/gtcatcache.h deleted file mode 100644 index c0921dc60e7..00000000000 --- a/src/include/utils/gtcatcache.h +++ /dev/null @@ -1,44 +0,0 @@ -/*------------------------------------------------------------------------- - * - * gtcatcache.h - * Global temporary catalog cache. - * - * Copyright (c) 2026, PostgreSQL Global Development Group - * - * src/include/utils/gtcatcache.h - * - *------------------------------------------------------------------------- - */ -#ifndef GTCATCACHE_H -#define GTCATCACHE_H - -#include "access/htup.h" -#include "access/tupdesc.h" - -/* - * Identifier of global temporary catalog cache. - */ -typedef enum GTCatCacheIdentifier -{ - PG_TEMP_INDEX, -} GTCatCacheIdentifier; - -#define NUM_GT_CAT_CACHES ((int) PG_TEMP_INDEX + 1) - -extern bool GTCatCacheTupleExists(GTCatCacheIdentifier cacheId, Oid relid); -extern HeapTuple GTCatCacheSearch(GTCatCacheIdentifier cacheId, Oid relid); -extern void GTCatCacheTupleInsert(GTCatCacheIdentifier cacheId, Oid relid, - char relkind, TupleDesc tupdesc, - const Datum *values, const bool *nulls); -extern void GTCatCacheTupleUpdate(GTCatCacheIdentifier cacheId, Oid relid, - HeapTuple newtuple); -extern void GTCatCacheTupleUpdateInPlace(GTCatCacheIdentifier cacheId, - Oid relid, HeapTuple newtuple); -extern void GTCatCacheTupleDelete(GTCatCacheIdentifier cacheId, Oid relid); -extern void GTCatCacheFlush(void); -extern void AtEOXact_GTCatCache(bool isCommit); -extern void AtEOSubXact_GTCatCache(bool isCommit, SubTransactionId mySubid, - SubTransactionId parentSubid); -extern void GTCatCacheDiscard(void); - -#endif /* GTCATCACHE_H */ diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h index 8411f0ecadd..de675e27255 100644 --- a/src/include/utils/relcache.h +++ b/src/include/utils/relcache.h @@ -139,6 +139,13 @@ extern void RelationAssumeNewRelfilelocator(Relation relation); */ extern HeapTuple GetEffectivePgClassTuple(Oid relid); +/* + * Get the effective pg_index tuple for an index, taking into account + * backend-local global temporary index validity state, without opening the + * index. See the comment in relcache.c for details. + */ +extern HeapTuple GetEffectivePgIndexTuple(Oid indexrelid); + /* * 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 8f518a4c7e3..91a504f2422 100644 --- a/src/test/isolation/expected/global-temp.out +++ b/src/test/isolation/expected/global-temp.out @@ -270,28 +270,28 @@ key|pg_typeof|val (1 row) step idx_valid1: - SELECT indisvalid FROM pg_temp_index WHERE indexrelid = 'tmp2_un'::regclass; + SELECT pg_gtt_index_isvalid('tmp2_un'::regclass); -indisvalid ----------- -t +pg_gtt_index_isvalid +-------------------- +t (1 row) step idx_valid2: - SELECT indisvalid FROM pg_temp_index WHERE indexrelid = 'tmp2_un'::regclass; + SELECT pg_gtt_index_isvalid('tmp2_un'::regclass); -indisvalid ----------- -f +pg_gtt_index_isvalid +-------------------- +f (1 row) step uniq_reidx2: REINDEX INDEX tmp2_un; step idx_valid2: - SELECT indisvalid FROM pg_temp_index WHERE indexrelid = 'tmp2_un'::regclass; + SELECT pg_gtt_index_isvalid('tmp2_un'::regclass); -indisvalid ----------- -t +pg_gtt_index_isvalid +-------------------- +t (1 row) step drop1: DROP TABLE tmp2; diff --git a/src/test/isolation/specs/global-temp.spec b/src/test/isolation/specs/global-temp.spec index 529a099af7f..8611ce7c891 100644 --- a/src/test/isolation/specs/global-temp.spec +++ b/src/test/isolation/specs/global-temp.spec @@ -48,7 +48,7 @@ step r1 { ROLLBACK; } step sp1 { SAVEPOINT sp; } step rsp1 { ROLLBACK TO SAVEPOINT sp; } step idx_valid1 { - SELECT indisvalid FROM pg_temp_index WHERE indexrelid = 'tmp2_un'::regclass; + SELECT pg_gtt_index_isvalid('tmp2_un'::regclass); } step drop1 { DROP TABLE tmp2; } step c1 { COMMIT; } @@ -91,7 +91,7 @@ step ins2_2 { INSERT INTO tmp2 VALUES (1, 's2'); } step sel2_2 { SELECT * FROM tmp2; } step seltype2 { SELECT key, pg_typeof(key), val FROM tmp2; } step idx_valid2 { - SELECT indisvalid FROM pg_temp_index WHERE indexrelid = 'tmp2_un'::regclass; + SELECT pg_gtt_index_isvalid('tmp2_un'::regclass); } step uniq_reidx2 { REINDEX INDEX tmp2_un; } step drop2 { DROP TABLE tmp2; } diff --git a/src/test/regress/expected/global_temp.out b/src/test/regress/expected/global_temp.out index f2946633eea..32a684e63a5 100644 --- a/src/test/regress/expected/global_temp.out +++ b/src/test/regress/expected/global_temp.out @@ -102,11 +102,9 @@ SELECT relid::regclass FROM pg_gtt_relation_state() ORDER BY 1; --------------------------------------- pg_temp_statistic pg_temp_statistic_relid_att_inh_index - pg_temp_index - pg_temp_index_indexrelid_index tmp1 tmp1_pkey -(6 rows) +(4 rows) -- Test pg_relation_filenode() matches global relfilenode SELECT relfilenode = pg_relation_filenode('tmp1'::regclass) AS ok @@ -250,9 +248,9 @@ DROP TABLE tmp2, perm; CREATE GLOBAL TEMP TABLE tmp2 (a int) PARTITION BY LIST (a); CREATE GLOBAL TEMP TABLE tmp2_p1 PARTITION OF tmp2 FOR VALUES IN (1); CREATE INDEX tmp2_a_idx ON tmp2 (a); -SELECT c.relname, i.indisvalid AS global_valid, t.indisvalid AS local_valid +SELECT c.relname, i.indisvalid AS global_valid, + pg_gtt_index_isvalid(i.indexrelid) AS local_valid FROM pg_class c JOIN pg_index i ON i.indexrelid = c.oid - LEFT JOIN pg_temp_index t ON t.indexrelid = i.indexrelid WHERE c.relname ~ 'tmp2(.*)_a_idx' ORDER BY c.relname; relname | global_valid | local_valid @@ -281,9 +279,9 @@ Number of partitions: 1 (Use \d+ to list them.) DROP INDEX tmp2_a_idx; CREATE INDEX tmp2_a_idx ON ONLY tmp2 (a); -SELECT c.relname, i.indisvalid AS global_valid, t.indisvalid AS local_valid +SELECT c.relname, i.indisvalid AS global_valid, + pg_gtt_index_isvalid(i.indexrelid) AS local_valid FROM pg_class c JOIN pg_index i ON i.indexrelid = c.oid - LEFT JOIN pg_temp_index t ON t.indexrelid = i.indexrelid WHERE c.relname ~ 'tmp2(.*)_a_idx' ORDER BY c.relname; relname | global_valid | local_valid @@ -311,9 +309,9 @@ Number of partitions: 0 CREATE INDEX tmp2_p1_a_idx ON tmp2_p1 (a); ALTER INDEX tmp2_a_idx ATTACH PARTITION tmp2_p1_a_idx; -SELECT c.relname, i.indisvalid AS global_valid, t.indisvalid AS local_valid +SELECT c.relname, i.indisvalid AS global_valid, + pg_gtt_index_isvalid(i.indexrelid) AS local_valid FROM pg_class c JOIN pg_index i ON i.indexrelid = c.oid - LEFT JOIN pg_temp_index t ON t.indexrelid = i.indexrelid WHERE c.relname ~ 'tmp2(.*)_a_idx' ORDER BY c.relname; relname | global_valid | local_valid @@ -621,12 +619,10 @@ SELECT relid::regclass FROM pg_gtt_relation_state() ORDER BY 1; --------------------------------------- pg_temp_statistic pg_temp_statistic_relid_att_inh_index - pg_temp_index - pg_temp_index_indexrelid_index tmp1_c_seq tmp1 tmp1_pkey -(7 rows) +(5 rows) SELECT * FROM tmp1; a | b | c @@ -1220,14 +1216,13 @@ SELECT relname, pg_relation_size(oid) ORDER BY relname; relname | pg_relation_size ----------------------------+------------------ - pg_temp_index | 0 pg_temp_statistic | 0 pg_temp_statistic_ext_data | 0 tmp1 | 0 tmp2 | 0 tmp2_p1 | 0 tmp2_p2 | 0 -(7 rows) +(6 rows) SELECT * FROM tmp1; a | b | c diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out index 4facc83bed6..8f5049c1595 100644 --- a/src/test/regress/expected/oidjoins.out +++ b/src/test/regress/expected/oidjoins.out @@ -285,7 +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_index {indexrelid} => pg_class {oid} NOTICE: checking pg_temp_statistic {starelid} => pg_class {oid} NOTICE: checking pg_temp_statistic {staop1} => pg_operator {oid} NOTICE: checking pg_temp_statistic {staop2} => pg_operator {oid} diff --git a/src/test/regress/sql/global_temp.sql b/src/test/regress/sql/global_temp.sql index 2fe79c332cd..bafa33220e3 100644 --- a/src/test/regress/sql/global_temp.sql +++ b/src/test/regress/sql/global_temp.sql @@ -139,9 +139,9 @@ DROP TABLE tmp2, perm; CREATE GLOBAL TEMP TABLE tmp2 (a int) PARTITION BY LIST (a); CREATE GLOBAL TEMP TABLE tmp2_p1 PARTITION OF tmp2 FOR VALUES IN (1); CREATE INDEX tmp2_a_idx ON tmp2 (a); -SELECT c.relname, i.indisvalid AS global_valid, t.indisvalid AS local_valid +SELECT c.relname, i.indisvalid AS global_valid, + pg_gtt_index_isvalid(i.indexrelid) AS local_valid FROM pg_class c JOIN pg_index i ON i.indexrelid = c.oid - LEFT JOIN pg_temp_index t ON t.indexrelid = i.indexrelid WHERE c.relname ~ 'tmp2(.*)_a_idx' ORDER BY c.relname; \d tmp2 @@ -149,9 +149,9 @@ SELECT c.relname, i.indisvalid AS global_valid, t.indisvalid AS local_valid DROP INDEX tmp2_a_idx; CREATE INDEX tmp2_a_idx ON ONLY tmp2 (a); -SELECT c.relname, i.indisvalid AS global_valid, t.indisvalid AS local_valid +SELECT c.relname, i.indisvalid AS global_valid, + pg_gtt_index_isvalid(i.indexrelid) AS local_valid FROM pg_class c JOIN pg_index i ON i.indexrelid = c.oid - LEFT JOIN pg_temp_index t ON t.indexrelid = i.indexrelid WHERE c.relname ~ 'tmp2(.*)_a_idx' ORDER BY c.relname; \d tmp2 @@ -159,9 +159,9 @@ SELECT c.relname, i.indisvalid AS global_valid, t.indisvalid AS local_valid CREATE INDEX tmp2_p1_a_idx ON tmp2_p1 (a); ALTER INDEX tmp2_a_idx ATTACH PARTITION tmp2_p1_a_idx; -SELECT c.relname, i.indisvalid AS global_valid, t.indisvalid AS local_valid +SELECT c.relname, i.indisvalid AS global_valid, + pg_gtt_index_isvalid(i.indexrelid) AS local_valid FROM pg_class c JOIN pg_index i ON i.indexrelid = c.oid - LEFT JOIN pg_temp_index t ON t.indexrelid = i.indexrelid WHERE c.relname ~ 'tmp2(.*)_a_idx' ORDER BY c.relname; \d tmp2 diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index e75ce6ec04a..459b1c85c3e 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_index FormData_pg_transform FormData_pg_trigger FormData_pg_ts_config @@ -1022,7 +1021,6 @@ Form_pg_statistic_ext_data Form_pg_subscription Form_pg_subscription_rel Form_pg_tablespace -Form_pg_temp_index Form_pg_transform Form_pg_trigger Form_pg_ts_config @@ -1088,9 +1086,6 @@ GISTTYPE GIST_SPLITVEC GMReaderTupleBuffer GROUP -GTCatCache -GTCatCacheEntry -GTCatCacheIdentifier GUCHashEntry GV Gather @@ -1196,6 +1191,7 @@ GroupingSet GroupingSetData GroupingSetKind GroupingSetsPath +GtrIndexValidHistory GtrRelPhysState GtrRelPhysStateHistory GtrSharedUsageEntry -- 2.54.0