From 081e6b3ee74151c27a87d23f4d2367745426679c Mon Sep 17 00:00:00 2001 From: David Geier Date: Thu, 3 Sep 2026 09:46:30 +0200 Subject: [PATCH v5 4/7] Overlay relation-kind-specific relcache fields Table-like relations and indexes use disjoint sets of cached metadata. Put those fields in an anonymous union so each relcache entry pays only for the larger of both sets, instead of the sum of both sets. sizeof(RelationData): 368 bytes --- src/backend/access/spgist/spgutils.c | 2 +- src/backend/commands/repack.c | 2 +- src/backend/commands/tablecmds.c | 12 +- src/backend/storage/lmgr/predicate.c | 6 +- src/backend/utils/cache/relcache.c | 455 ++++++++++++++------------- src/include/utils/rel.h | 8 + src/include/utils/rel_internal.h | 245 ++++++++------- 7 files changed, 390 insertions(+), 340 deletions(-) diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c index f2ee333f60d..a33dcfe8ecf 100644 --- a/src/backend/access/spgist/spgutils.c +++ b/src/backend/access/spgist/spgutils.c @@ -126,7 +126,7 @@ GetIndexInputType(Relation index, AttrNumber indexcol) List *indexprs; ListCell *indexpr_item; - Assert(index->rd_index != NULL); + Assert(RelationIsIndex(index)); Assert(indexcol > 0 && indexcol <= index->rd_index->indnkeyatts); opcintype = index->rd_opcintype[indexcol - 1]; if (!IsPolymorphicType(opcintype)) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 596c1abaf78..6f14de97a2f 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -790,7 +790,7 @@ check_index_is_clusterable(Relation OldHeap, Oid indexOid, LOCKMODE lockmode) /* * Check that index is in fact an index on the given relation */ - if (OldIndex->rd_index == NULL || + if (!RelationIsIndex(OldIndex) || OldIndex->rd_index->indrelid != RelationGetRelid(OldHeap)) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 0274d892f2e..0170c41fd00 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4371,9 +4371,7 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo * let's not be totally oblivious to it. We can process an index as * not-an-index, but not the other way around. */ - Assert(!is_index || - is_index == (targetrelation->rd_rel->relkind == RELKIND_INDEX || - targetrelation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)); + Assert(!is_index || is_index == RelationIsIndex(targetrelation)); /* * Update pg_class tuple with new relname. (Scribbling on reltup is OK @@ -4400,8 +4398,7 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo /* * Also rename the associated constraint, if any. */ - if (targetrelation->rd_rel->relkind == RELKIND_INDEX || - targetrelation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) + if (RelationIsIndex(targetrelation)) { Oid constraintId = get_index_constraint(myrelid); @@ -9072,8 +9069,7 @@ ATExecSetStatistics(Relation rel, const char *colName, int16 colNum, Node *newVa errmsg("cannot alter statistics on virtual generated column \"%s\"", colName))); - if (rel->rd_rel->relkind == RELKIND_INDEX || - rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) + if (RelationIsIndex(rel)) { if (attnum > rel->rd_index->indnkeyatts) ereport(ERROR, @@ -19194,7 +19190,7 @@ ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode indexRel = index_open(indexOid, ShareLock); /* Check that the index is on the relation we're altering. */ - if (indexRel->rd_index == NULL || + if (!RelationIsIndex(indexRel) || indexRel->rd_index->indrelid != RelationGetRelid(rel)) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c index 0ae85b7d5b4..5c539306e77 100644 --- a/src/backend/storage/lmgr/predicate.c +++ b/src/backend/storage/lmgr/predicate.c @@ -2558,7 +2558,7 @@ PredicateLockTID(Relation relation, const ItemPointerData *tid, Snapshot snapsho /* * Return if this xact wrote it. */ - if (relation->rd_index == NULL) + if (!RelationIsIndex(relation)) { /* If we wrote it; we already have a write lock. */ if (TransactionIdIsCurrentTransactionId(tuple_xid)) @@ -2890,7 +2890,7 @@ DropAllPredicateLocksFromTable(Relation relation, bool transfer) dbId = relation->rd_locator.dbOid; relId = relation->rd_id; - if (relation->rd_index == NULL) + if (!RelationIsIndex(relation)) { isIndex = false; heapId = relId; @@ -4371,7 +4371,7 @@ CheckTableForSerializableConflictIn(Relation relation) */ MyXactDidWrite = true; - Assert(relation->rd_index == NULL); /* not an index relation */ + Assert(!RelationIsIndex(relation)); dbId = relation->rd_locator.dbOid; heapId = relation->rd_id; diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 293725b1d19..0429b6de8ac 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -93,7 +93,7 @@ #include "utils/snapmgr.h" #include "utils/syscache.h" -#define RELCACHE_INIT_FILEMAGIC 0x573266 /* version ID value */ +#define RELCACHE_INIT_FILEMAGIC 0x573267 /* version ID value */ /* * Whether to bother checking if relation cache memory needs to be freed @@ -1214,71 +1214,10 @@ retry: */ RelationBuildTupleDesc(relation); - /* foreign key data is not loaded till asked for */ - relation->rd_fkeylist = NIL; - relation->rd_fkeyvalid = false; - - /* TOAST type data is not loaded till asked for */ - relation->rd_toastchunkidtype = InvalidOid; - - /* partitioning data is not loaded till asked for */ - relation->rd_partkey = NULL; - relation->rd_partkeycxt = NULL; - relation->rd_partdesc = NULL; - relation->rd_partdesc_nodetached = NULL; - relation->rd_partdesc_nodetached_xmin = InvalidTransactionId; - relation->rd_pdcxt = NULL; - relation->rd_pddcxt = NULL; relation->rd_partcheck = NIL; relation->rd_partcheckvalid = false; relation->rd_partcheckcxt = NULL; - /* - * initialize access method information - */ - if (relation->rd_rel->relkind == RELKIND_INDEX || - relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) - RelationInitIndexAccessInfo(relation); - else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || - relation->rd_rel->relkind == RELKIND_SEQUENCE) - RelationInitTableAccessMethod(relation); - else if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - { - /* - * Do nothing: access methods are a setting that partitions can - * inherit. - */ - } - else - Assert(relation->rd_rel->relam == InvalidOid); - - /* extract reloptions if any */ - RelationParseRelOptions(relation, pg_class_tuple); - - /* - * Fetch rules and triggers that affect this relation. - * - * Note that RelationBuildRuleLock() relies on this being done after - * extracting the relation's reloptions. - */ - if (relation->rd_rel->relhasrules) - RelationBuildRuleLock(relation); - else - { - relation->rd_rules = NULL; - relation->rd_rulescxt = NULL; - } - - if (relation->rd_rel->relhastriggers) - RelationBuildTriggers(relation); - else - relation->trigdesc = NULL; - - if (relation->rd_rel->relrowsecurity) - RelationBuildRowSecurity(relation); - else - relation->rd_rsdesc = NULL; - /* * initialize the relation lock manager information */ @@ -1292,6 +1231,75 @@ retry: /* make sure relation is marked as having no open file yet */ relation->rd_smgr = NULL; + if (RelationIsIndex(relation)) + { + RelationInitIndexAccessInfo(relation); + + /* extract reloptions if any */ + RelationParseRelOptions(relation, pg_class_tuple); + } + else + { + /* partitioning data is not loaded till asked for */ + relation->rd_partkey = NULL; + relation->rd_partkeycxt = NULL; + relation->rd_partdesc = NULL; + relation->rd_partdesc_nodetached = NULL; + relation->rd_partdesc_nodetached_xmin = InvalidTransactionId; + relation->rd_pdcxt = NULL; + relation->rd_pddcxt = NULL; + + /* + * initialize access method information + */ + if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || + relation->rd_rel->relkind == RELKIND_SEQUENCE) + RelationInitTableAccessMethod(relation); + else if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + /* + * Do nothing: access methods are a setting that partitions can + * inherit. + */ + } + else + Assert(relation->rd_rel->relam == InvalidOid); + + /* extract reloptions if any */ + RelationParseRelOptions(relation, pg_class_tuple); + + /* foreign key data is not loaded till asked for */ + relation->rd_fkeylist = NIL; + relation->rd_fkeyvalid = false; + + /* TOAST type data is not loaded till asked for */ + relation->rd_toastchunkidtype = InvalidOid; + + /* + * Fetch rules and triggers that affect this relation. + * + * Note that RelationBuildRuleLock() relies on this being done after + * extracting the relation's reloptions. + */ + if (relation->rd_rel->relhasrules) + RelationBuildRuleLock(relation); + else + { + relation->rd_rules = NULL; + relation->rd_rulescxt = NULL; + } + + if (relation->rd_rel->relhastriggers) + RelationBuildTriggers(relation); + else + relation->trigdesc = NULL; + + if (relation->rd_rel->relrowsecurity) + RelationBuildRowSecurity(relation); + else + relation->rd_rsdesc = NULL; + } + /* * now we can free the memory allocated for pg_class_tuple */ @@ -2240,7 +2248,7 @@ RelationCloseCleanup(Relation relation) * stale partition descriptors it has. This is unlikely, so check to see * if there are child contexts before expending a call to mcxt.c. */ - if (RelationHasReferenceCountZero(relation)) + if (RelationHasReferenceCountZero(relation) && !RelationIsIndex(relation)) { if (relation->rd_pdcxt != NULL && relation->rd_pdcxt->firstchild != NULL) @@ -2287,9 +2295,7 @@ RelationReloadIndexInfo(Relation relation) Form_pg_class relp; /* Should be called only for invalidated, live indexes */ - Assert((relation->rd_rel->relkind == RELKIND_INDEX || - relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) && - !relation->rd_isvalid && + Assert(RelationIsIndex(relation) && !relation->rd_isvalid && relation->rd_droppedSubid == InvalidSubTransactionId); /* @@ -2445,6 +2451,8 @@ RelationReloadNailed(Relation relation) static void RelationDestroyRelation(Relation relation, bool remember_tupdesc) { + const bool is_index = relation->rd_rel && RelationIsIndex(relation); + Assert(RelationHasReferenceCountZero(relation)); /* @@ -2480,39 +2488,45 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc) else FreeTupleDesc(relation->rd_att); } - FreeTriggerDesc(relation->trigdesc); - list_free_deep(relation->rd_fkeylist); - list_free(relation->rd_indexlist); - list_free(relation->rd_statlist); - bms_free(relation->rd_keyattr); - bms_free(relation->rd_pkattr); - bms_free(relation->rd_idattr); - bms_free(relation->rd_hotblockingattr); - bms_free(relation->rd_summarizedattr); - if (relation->rd_pubdesc) - pfree(relation->rd_pubdesc); if (relation->rd_options) pfree(relation->rd_options); - if (relation->rd_indextuple) - pfree(relation->rd_indextuple); if (relation->rd_amcache) pfree(relation->rd_amcache); - if (relation->rd_fdwroutine) - pfree(relation->rd_fdwroutine); - if (relation->rd_indexcxt) - MemoryContextDelete(relation->rd_indexcxt); - if (relation->rd_rulescxt) - MemoryContextDelete(relation->rd_rulescxt); - if (relation->rd_rsdesc) - MemoryContextDelete(relation->rd_rsdesc->rscxt); - if (relation->rd_partkeycxt) - MemoryContextDelete(relation->rd_partkeycxt); - if (relation->rd_pdcxt) - MemoryContextDelete(relation->rd_pdcxt); - if (relation->rd_pddcxt) - MemoryContextDelete(relation->rd_pddcxt); if (relation->rd_partcheckcxt) MemoryContextDelete(relation->rd_partcheckcxt); + if (!is_index) + { + list_free(relation->rd_indexlist); + list_free(relation->rd_statlist); + FreeTriggerDesc(relation->trigdesc); + list_free_deep(relation->rd_fkeylist); + bms_free(relation->rd_keyattr); + bms_free(relation->rd_pkattr); + bms_free(relation->rd_idattr); + bms_free(relation->rd_hotblockingattr); + bms_free(relation->rd_summarizedattr); + if (relation->rd_pubdesc) + pfree(relation->rd_pubdesc); + if (relation->rd_fdwroutine) + pfree(relation->rd_fdwroutine); + if (relation->rd_rulescxt) + MemoryContextDelete(relation->rd_rulescxt); + if (relation->rd_rsdesc) + MemoryContextDelete(relation->rd_rsdesc->rscxt); + if (relation->rd_partkeycxt) + MemoryContextDelete(relation->rd_partkeycxt); + if (relation->rd_pdcxt) + MemoryContextDelete(relation->rd_pdcxt); + if (relation->rd_pddcxt) + MemoryContextDelete(relation->rd_pddcxt); + } + else + { + if (relation->rd_indextuple) + pfree(relation->rd_indextuple); + if (relation->rd_indexcxt) + MemoryContextDelete(relation->rd_indexcxt); + } pfree(relation); } @@ -2610,9 +2624,7 @@ RelationRebuildRelation(Relation relation) * pg_index tuple only after that, and then relies on * CommandCounterIncrement to load the pg_index contents. */ - if ((relation->rd_rel->relkind == RELKIND_INDEX || - relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) && - relation->rd_indexcxt != NULL) + if (RelationIsIndex(relation) && relation->rd_indexcxt != NULL) { RelationReloadIndexInfo(relation); return; @@ -2657,10 +2669,11 @@ RelationRebuildRelation(Relation relation) */ Relation newrel; Oid save_relid = RelationGetRelid(relation); + bool is_index = RelationIsIndex(relation); bool keep_tupdesc; - bool keep_rules; - bool keep_policies; - bool keep_partkey; + bool keep_rules = false; + bool keep_policies = false; + bool keep_partkey = false; /* Build temporary entry, but don't link it into hashtable */ newrel = RelationBuildDesc(save_relid, false); @@ -2704,10 +2717,14 @@ RelationRebuildRelation(Relation relation) Assert(relation->rd_rel->relkind == newrel->rd_rel->relkind); keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att); - keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules); - keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc); - /* partkey is immutable once set up, so we can always keep it */ - keep_partkey = (relation->rd_partkey != NULL); + + if (!is_index) + { + keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules); + keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc); + /* partkey is immutable once set up, so we can always keep it */ + keep_partkey = (relation->rd_partkey != NULL); + } /* * Perform swapping of the relcache entry contents. Within this @@ -2750,71 +2767,78 @@ RelationRebuildRelation(Relation relation) SWAPFIELD(Form_pg_class, rd_rel); /* ... but actually, we don't have to update newrel->rd_rel */ memcpy(relation->rd_rel, newrel->rd_rel, CLASS_TUPLE_SIZE); - /* preserve old tupledesc, rules, policies if no logical change */ + /* preserve old tupledesc if there was no logical change */ if (keep_tupdesc) SWAPFIELD(TupleDesc, rd_att); - if (keep_rules) - { - SWAPFIELD(RuleLock *, rd_rules); - SWAPFIELD(MemoryContext, rd_rulescxt); - } - if (keep_policies) - SWAPFIELD(RowSecurityDesc *, rd_rsdesc); - /* toast OID override must be preserved */ - SWAPFIELD(Oid, rd_toastoid); /* pgstat_info / enabled must be preserved */ SWAPFIELD(struct PgStat_RelationStatus *, pgstat_info); SWAPFIELD(bool, pgstat_enabled); - /* preserve old partition key if we have one */ - if (keep_partkey) - { - SWAPFIELD(PartitionKey, rd_partkey); - SWAPFIELD(MemoryContext, rd_partkeycxt); - } - if (newrel->rd_pdcxt != NULL || newrel->rd_pddcxt != NULL) + + if (!is_index) { - /* - * We are rebuilding a partitioned relation with a non-zero - * reference count, so we must keep the old partition descriptor - * around, in case there's a PartitionDirectory with a pointer to - * it. This means we can't free the old rd_pdcxt yet. (This is - * necessary because RelationGetPartitionDesc hands out direct - * pointers to the relcache's data structure, unlike our usual - * practice which is to hand out copies. We'd have the same - * problem with rd_partkey, except that we always preserve that - * once created.) - * - * To ensure that it's not leaked completely, re-attach it to the - * new reldesc, or make it a child of the new reldesc's rd_pdcxt - * in the unlikely event that there is one already. (Compare hack - * in RelationBuildPartitionDesc.) RelationClose will clean up - * any such contexts once the reference count reaches zero. - * - * In the case where the reference count is zero, this code is not - * reached, which should be OK because in that case there should - * be no PartitionDirectory with a pointer to the old entry. - * - * Note that newrel and relation have already been swapped, so the - * "old" partition descriptor is actually the one hanging off of - * newrel. - */ - relation->rd_partdesc = NULL; /* ensure rd_partdesc is invalid */ - relation->rd_partdesc_nodetached = NULL; - relation->rd_partdesc_nodetached_xmin = InvalidTransactionId; - if (relation->rd_pdcxt != NULL) /* probably never happens */ - MemoryContextSetParent(newrel->rd_pdcxt, relation->rd_pdcxt); - else - relation->rd_pdcxt = newrel->rd_pdcxt; - if (relation->rd_pddcxt != NULL) - MemoryContextSetParent(newrel->rd_pddcxt, relation->rd_pddcxt); - else - relation->rd_pddcxt = newrel->rd_pddcxt; - /* drop newrel's pointers so we don't destroy it below */ - newrel->rd_partdesc = NULL; - newrel->rd_partdesc_nodetached = NULL; - newrel->rd_partdesc_nodetached_xmin = InvalidTransactionId; - newrel->rd_pdcxt = NULL; - newrel->rd_pddcxt = NULL; + /* preserve old rules and policies if there was no logical change */ + if (keep_rules) + { + SWAPFIELD(RuleLock *, rd_rules); + SWAPFIELD(MemoryContext, rd_rulescxt); + } + if (keep_policies) + SWAPFIELD(RowSecurityDesc *, rd_rsdesc); + + /* toast OID override must be preserved */ + SWAPFIELD(Oid, rd_toastoid); + + /* preserve old partition key if we have one */ + if (keep_partkey) + { + SWAPFIELD(PartitionKey, rd_partkey); + SWAPFIELD(MemoryContext, rd_partkeycxt); + } + if (newrel->rd_pdcxt != NULL || newrel->rd_pddcxt != NULL) + { + /* + * We are rebuilding a partitioned relation with a non-zero + * reference count, so we must keep the old partition descriptor + * around, in case there's a PartitionDirectory with a pointer to + * it. This means we can't free the old rd_pdcxt yet. (This is + * necessary because RelationGetPartitionDesc hands out direct + * pointers to the relcache's data structure, unlike our usual + * practice which is to hand out copies. We'd have the same + * problem with rd_partkey, except that we always preserve that + * once created.) + * + * To ensure that it's not leaked completely, re-attach it to the + * new reldesc, or make it a child of the new reldesc's rd_pdcxt + * in the unlikely event that there is one already. (Compare hack + * in RelationBuildPartitionDesc.) RelationClose will clean up + * any such contexts once the reference count reaches zero. + * + * In the case where the reference count is zero, this code is not + * reached, which should be OK because in that case there should + * be no PartitionDirectory with a pointer to the old entry. + * + * Note that newrel and relation have already been swapped, so the + * "old" partition descriptor is actually the one hanging off of + * newrel. + */ + relation->rd_partdesc = NULL; /* ensure rd_partdesc is invalid */ + relation->rd_partdesc_nodetached = NULL; + relation->rd_partdesc_nodetached_xmin = InvalidTransactionId; + if (relation->rd_pdcxt != NULL) /* probably never happens */ + MemoryContextSetParent(newrel->rd_pdcxt, relation->rd_pdcxt); + else + relation->rd_pdcxt = newrel->rd_pdcxt; + if (relation->rd_pddcxt != NULL) + MemoryContextSetParent(newrel->rd_pddcxt, relation->rd_pddcxt); + else + relation->rd_pddcxt = newrel->rd_pddcxt; + /* drop newrel's pointers so we don't destroy it below */ + newrel->rd_partdesc = NULL; + newrel->rd_partdesc_nodetached = NULL; + newrel->rd_partdesc_nodetached_xmin = InvalidTransactionId; + newrel->rd_pdcxt = NULL; + newrel->rd_pddcxt = NULL; + } } #undef SWAPFIELD @@ -4364,8 +4388,8 @@ RelationCacheInitializePhase3(void) } /* Reload tableam data if needed */ - if (relation->rd_tableam == NULL && - (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || relation->rd_rel->relkind == RELKIND_SEQUENCE)) + if ((RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || relation->rd_rel->relkind == RELKIND_SEQUENCE) && + relation->rd_tableam == NULL) { RelationInitTableAccessMethod(relation); Assert(relation->rd_tableam != NULL); @@ -5163,6 +5187,9 @@ RelationGetIndexExpressions(Relation relation) char *exprsString; MemoryContext oldcxt; + if (!RelationIsIndex(relation)) + return NIL; + /* Quick exit if we already computed the result. */ if (relation->rd_indexprs) return copyObject(relation->rd_indexprs); @@ -5223,6 +5250,9 @@ RelationGetDummyIndexExpressions(Relation relation) List *rawExprs; ListCell *lc; + if (!RelationIsIndex(relation)) + return NIL; + /* Quick exit if there is nothing to do. */ if (relation->rd_indextuple == NULL || heap_attisnull(relation->rd_indextuple, Anum_pg_index_indexprs, NULL)) @@ -5276,6 +5306,9 @@ RelationGetIndexPredicate(Relation relation) char *predString; MemoryContext oldcxt; + if (!RelationIsIndex(relation)) + return NIL; + /* Quick exit if we already computed the result. */ if (relation->rd_indpred) return copyObject(relation->rd_indpred); @@ -6521,18 +6554,6 @@ load_relcache_init_file(bool shared) /* Load table AM data */ if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind) || rel->rd_rel->relkind == RELKIND_SEQUENCE) RelationInitTableAccessMethod(rel); - - Assert(rel->rd_index == NULL); - Assert(rel->rd_indextuple == NULL); - Assert(rel->rd_indexcxt == NULL); - Assert(rel->rd_indam == NULL); - Assert(rel->rd_opfamily == NULL); - Assert(rel->rd_opcintype == NULL); - Assert(rel->rd_support == NULL); - Assert(rel->rd_supportinfo == NULL); - Assert(rel->rd_indoption == NULL); - Assert(rel->rd_indcollation == NULL); - Assert(rel->rd_opcoptions == NULL); } /* @@ -6542,55 +6563,69 @@ load_relcache_init_file(bool shared) * be a big performance hit since few system catalogs have such. Ditto * for RLS policy data, partition info, index expressions, predicates, * exclusion info, and FDW info. + * + * These fields are overlaid in a union with the index-only fields + * that were just set up above (see RelationData in rel_internal.h), + * so this must be skipped for index relations to avoid clobbering + * that data. */ - rel->rd_rules = NULL; - rel->rd_rulescxt = NULL; - rel->trigdesc = NULL; - rel->rd_rsdesc = NULL; - rel->rd_partkey = NULL; - rel->rd_partkeycxt = NULL; - rel->rd_partdesc = NULL; - rel->rd_partdesc_nodetached = NULL; - rel->rd_partdesc_nodetached_xmin = InvalidTransactionId; - rel->rd_pdcxt = NULL; - rel->rd_pddcxt = NULL; - rel->rd_partcheck = NIL; - rel->rd_partcheckvalid = false; - rel->rd_partcheckcxt = NULL; - rel->rd_indexprs = NIL; - rel->rd_indpred = NIL; - rel->rd_exclops = NULL; - rel->rd_exclprocs = NULL; - rel->rd_exclstrats = NULL; - rel->rd_fdwroutine = NULL; + if (!RelationIsIndex(rel)) + { + rel->rd_rules = NULL; + rel->rd_rulescxt = NULL; + rel->trigdesc = NULL; + rel->rd_rsdesc = NULL; + rel->rd_partkey = NULL; + rel->rd_partkeycxt = NULL; + rel->rd_partdesc = NULL; + rel->rd_partdesc_nodetached = NULL; + rel->rd_partdesc_nodetached_xmin = InvalidTransactionId; + rel->rd_pdcxt = NULL; + rel->rd_pddcxt = NULL; + rel->rd_keyattr = NULL; + rel->rd_pkattr = NULL; + rel->rd_idattr = NULL; + rel->rd_hotblockingattr = NULL; + rel->rd_summarizedattr = NULL; + rel->rd_pubdesc = NULL; + rel->rd_fdwroutine = NULL; + rel->rd_fkeyvalid = false; + rel->rd_fkeylist = NIL; + rel->rd_indexvalid = false; + rel->rd_indexlist = NIL; + rel->rd_pkindex = InvalidOid; + rel->rd_replidindex = InvalidOid; + rel->rd_attrsvalid = false; + rel->rd_statvalid = false; + rel->rd_statlist = NIL; + rel->rd_toastchunkidtype = InvalidOid; + } + else + { + /* These lazily built fields are not stored in the init file. */ + rel->rd_indexprs = NIL; + rel->rd_indpred = NIL; + rel->rd_exclops = NULL; + rel->rd_exclprocs = NULL; + rel->rd_exclstrats = NULL; + } /* * Reset transient-state fields in the relcache entry */ rel->rd_smgr = NULL; + rel->rd_partcheck = NIL; + rel->rd_partcheckvalid = false; + rel->rd_partcheckcxt = NULL; if (rel->rd_isnailed) rel->rd_refcnt = 1; else rel->rd_refcnt = 0; - rel->rd_indexvalid = false; - rel->rd_indexlist = NIL; - rel->rd_pkindex = InvalidOid; - rel->rd_replidindex = InvalidOid; - rel->rd_attrsvalid = false; - rel->rd_keyattr = NULL; - rel->rd_pkattr = NULL; - rel->rd_idattr = NULL; - rel->rd_pubdesc = NULL; - rel->rd_statvalid = false; - rel->rd_statlist = NIL; - rel->rd_fkeyvalid = false; - rel->rd_fkeylist = NIL; rel->rd_createSubid = InvalidSubTransactionId; rel->rd_newRelfilelocatorSubid = InvalidSubTransactionId; rel->rd_firstRelfilelocatorSubid = InvalidSubTransactionId; rel->rd_droppedSubid = InvalidSubTransactionId; rel->rd_amcache = NULL; - rel->rd_toastchunkidtype = InvalidOid; rel->pgstat_info = NULL; /* diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index fc7b5f6d800..d1a8408932f 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -308,6 +308,14 @@ typedef struct ViewOptions */ #define RelationGetRelid(relation) ((relation)->rd_id) +/* + * RelationIsIndex + * True for both, ordinary and partitioned index relations. + */ +#define RelationIsIndex(relation) \ + ((relation)->rd_rel->relkind == RELKIND_INDEX || \ + (relation)->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) + /* * RelationGetNumberOfAttributes * Returns the total number of attributes in a relation. diff --git a/src/include/utils/rel_internal.h b/src/include/utils/rel_internal.h index adee58a6c5a..f2ec9196f3c 100644 --- a/src/include/utils/rel_internal.h +++ b/src/include/utils/rel_internal.h @@ -40,6 +40,134 @@ typedef LockInfoData *LockInfo; typedef struct RelationData { + union + { + /* fields used only for a non-index (table-like) relation */ + struct + { + RuleLock *rd_rules; /* rewrite rules */ + MemoryContext rd_rulescxt; /* private memory cxt for rd_rules, if any */ + TriggerDesc *trigdesc; /* Trigger info, or NULL if rel has none */ + /* use "struct" here to avoid needing to include rowsecurity.h: */ + struct RowSecurityDesc *rd_rsdesc; /* row security policies, or NULL */ + + /* data managed by RelationGetFKeyList: */ + List *rd_fkeylist; /* list of ForeignKeyCacheInfo (see below) */ + bool rd_fkeyvalid; /* true if rd_fkeylist has been computed */ + + /* data managed by RelationGetPartitionKey: */ + PartitionKey rd_partkey; /* partition key, or NULL */ + MemoryContext rd_partkeycxt; /* private context for rd_partkey, if any */ + + /* data managed by RelationGetPartitionDesc: */ + PartitionDesc rd_partdesc; /* partition descriptor, or NULL */ + MemoryContext rd_pdcxt; /* private context for rd_partdesc, if any */ + + /* Same as above, for partdescs that omit detached partitions */ + PartitionDesc rd_partdesc_nodetached; /* partdesc w/o detached parts */ + MemoryContext rd_pddcxt; /* for rd_partdesc_nodetached, if any */ + + /* + * pg_inherits.xmin of the partition that was excluded in + * rd_partdesc_nodetached. This informs a future user of that partdesc: + * if this value is not in progress for the active snapshot, then the + * partdesc can be used, otherwise they have to build a new one. (This + * matches what find_inheritance_children_extended would do). + */ + TransactionId rd_partdesc_nodetached_xmin; + + /* data managed by RelationGetIndexList: */ + List *rd_indexlist; /* list of OIDs of indexes on relation */ + Oid rd_pkindex; /* OID of (deferrable?) primary key, if any */ + bool rd_ispkdeferrable; /* is rd_pkindex a deferrable PK? */ + bool rd_indexvalid; /* is rd_indexlist valid? (also rd_pkindex and + * rd_replidindex) */ + Oid rd_replidindex; /* OID of replica identity index, if any */ + + /* data managed by RelationGetIndexAttrBitmap: */ + bool rd_attrsvalid; /* are bitmaps of attrs valid? (see + * RelationGetIndexAttrBitmap) */ + Bitmapset *rd_keyattr; /* cols that can be ref'd by foreign keys */ + Bitmapset *rd_pkattr; /* cols included in primary key */ + Bitmapset *rd_idattr; /* included in replica identity index */ + Bitmapset *rd_hotblockingattr; /* cols blocking HOT update */ + Bitmapset *rd_summarizedattr; /* cols indexed by summarizing indexes */ + + /* data managed by RelationGetStatExtList: */ + List *rd_statlist; /* list of OIDs of extended stats */ + bool rd_statvalid; /* is rd_statlist valid? */ + + /* + * Table access method. + */ + const struct TableAmRoutine *rd_tableam; + + /* + * Hack for CLUSTER, rewriting ALTER TABLE, etc: when writing a new + * version of a table, we need to make any toast pointers inserted into it + * have the existing toast table's OID, not the OID of the transient toast + * table. If rd_toastoid isn't InvalidOid, it is the OID to place in + * toast pointers inserted into this rel. (Note it's set on the new + * version of the main heap, not the toast table itself.) This also + * causes toast_save_datum() to try to preserve toast value OIDs. + */ + Oid rd_toastoid; /* Real TOAST table's OID, or InvalidOid */ + + /* + * Type OID of the "chunk_id" column of this relation's TOAST table, i.e. + * OIDOID or OID8OID. + * + * This data is filled on demand by RelationGetToastChunkIdType(), not at + * relcache build time, so as to save in syscache lookups. + */ + Oid rd_toastchunkidtype; + + PublicationDesc *rd_pubdesc; /* publication descriptor, or NULL */ + + /* + * foreign-table support + * + * rd_fdwroutine must point to a single memory chunk palloc'd in + * CacheMemoryContext. It will be freed and reset to NULL on a + * relcache reset. + */ + /* use "struct" here to avoid needing to include fdwapi.h: */ + struct FdwRoutine *rd_fdwroutine; /* cached function pointers, or NULL */ + }; + + /* fields used only for an index relation */ + struct + { + Form_pg_index rd_index; /* pg_index tuple describing this index */ + /* use "struct" here to avoid needing to include htup.h: */ + struct HeapTupleData *rd_indextuple; /* all of pg_index tuple */ + + /* + * index access support info (used only for an index relation) + * + * Note: only default support procs for each opclass are cached, namely + * those with lefttype and righttype equal to the opclass's opcintype. The + * arrays are indexed by support function number, which is a sufficient + * identifier given that restriction. + */ + MemoryContext rd_indexcxt; /* private memory cxt for this stuff */ + /* use "struct" here to avoid needing to include amapi.h: */ + const struct IndexAmRoutine *rd_indam; /* index AM's API struct */ + Oid *rd_opfamily; /* OIDs of op families for each index col */ + Oid *rd_opcintype; /* OIDs of opclass declared input data types */ + RegProcedure *rd_support; /* OIDs of support procedures */ + struct FmgrInfo *rd_supportinfo; /* lookup info for support procedures */ + int16 *rd_indoption; /* per-column AM-specific flags */ + List *rd_indexprs; /* index expression trees, if any */ + List *rd_indpred; /* index predicate tree, if any */ + Oid *rd_exclops; /* OIDs of exclusion operators, if any */ + Oid *rd_exclprocs; /* OIDs of exclusion ops' procs, if any */ + uint16 *rd_exclstrats; /* exclusion ops' strategy numbers, if any */ + Oid *rd_indcollation; /* OIDs of index collations */ + bytea **rd_opcoptions; /* parsed opclass-specific options */ + }; + }; + RelFileLocator rd_locator; /* relation physical identifier */ SMgrRelation rd_smgr; /* cached file handle, or NULL */ int rd_refcnt; /* reference count */ @@ -47,9 +175,6 @@ typedef struct RelationData bool rd_islocaltemp; /* rel is a temp rel of this session */ bool rd_isnailed; /* rel is nailed in cache */ bool rd_isvalid; /* relcache entry is valid */ - bool rd_indexvalid; /* is rd_indexlist valid? (also rd_pkindex and - * rd_replidindex) */ - bool rd_statvalid; /* is rd_statlist valid? */ /*---------- * rd_createSubid is the ID of the highest subtransaction the rel has @@ -98,61 +223,12 @@ typedef struct RelationData TupleDesc rd_att; /* tuple descriptor */ Oid rd_id; /* relation's object id */ LockInfoData rd_lockInfo; /* lock mgr's info for locking relation */ - RuleLock *rd_rules; /* rewrite rules */ - MemoryContext rd_rulescxt; /* private memory cxt for rd_rules, if any */ - TriggerDesc *trigdesc; /* Trigger info, or NULL if rel has none */ - /* use "struct" here to avoid needing to include rowsecurity.h: */ - struct RowSecurityDesc *rd_rsdesc; /* row security policies, or NULL */ - - /* data managed by RelationGetFKeyList: */ - List *rd_fkeylist; /* list of ForeignKeyCacheInfo (see below) */ - bool rd_fkeyvalid; /* true if list has been computed */ - - /* data managed by RelationGetPartitionKey: */ - PartitionKey rd_partkey; /* partition key, or NULL */ - MemoryContext rd_partkeycxt; /* private context for rd_partkey, if any */ - - /* data managed by RelationGetPartitionDesc: */ - PartitionDesc rd_partdesc; /* partition descriptor, or NULL */ - MemoryContext rd_pdcxt; /* private context for rd_partdesc, if any */ - - /* Same as above, for partdescs that omit detached partitions */ - PartitionDesc rd_partdesc_nodetached; /* partdesc w/o detached parts */ - MemoryContext rd_pddcxt; /* for rd_partdesc_nodetached, if any */ - - /* - * pg_inherits.xmin of the partition that was excluded in - * rd_partdesc_nodetached. This informs a future user of that partdesc: - * if this value is not in progress for the active snapshot, then the - * partdesc can be used, otherwise they have to build a new one. (This - * matches what find_inheritance_children_extended would do). - */ - TransactionId rd_partdesc_nodetached_xmin; /* data managed by RelationGetPartitionQual: */ List *rd_partcheck; /* partition CHECK quals */ bool rd_partcheckvalid; /* true if list has been computed */ MemoryContext rd_partcheckcxt; /* private cxt for rd_partcheck, if any */ - /* data managed by RelationGetIndexList: */ - List *rd_indexlist; /* list of OIDs of indexes on relation */ - Oid rd_pkindex; /* OID of (deferrable?) primary key, if any */ - bool rd_ispkdeferrable; /* is rd_pkindex a deferrable PK? */ - Oid rd_replidindex; /* OID of replica identity index, if any */ - - /* data managed by RelationGetStatExtList: */ - List *rd_statlist; /* list of OIDs of extended stats */ - - /* data managed by RelationGetIndexAttrBitmap: */ - bool rd_attrsvalid; /* are bitmaps of attrs valid? */ - Bitmapset *rd_keyattr; /* cols that can be ref'd by foreign keys */ - Bitmapset *rd_pkattr; /* cols included in primary key */ - Bitmapset *rd_idattr; /* included in replica identity index */ - Bitmapset *rd_hotblockingattr; /* cols blocking HOT update */ - Bitmapset *rd_summarizedattr; /* cols indexed by summarizing indexes */ - - PublicationDesc *rd_pubdesc; /* publication descriptor, or NULL */ - /* * rd_options is set whenever rd_rel is loaded into the relcache entry. * Note that you can NOT look into rd_rel for this data. NULL means "use @@ -169,40 +245,6 @@ typedef struct RelationData */ Oid rd_amhandler; /* OID of index AM's handler function */ - /* - * Table access method. - */ - const struct TableAmRoutine *rd_tableam; - - /* These are non-NULL only for an index relation: */ - Form_pg_index rd_index; /* pg_index tuple describing this index */ - /* use "struct" here to avoid needing to include htup.h: */ - struct HeapTupleData *rd_indextuple; /* all of pg_index tuple */ - - /* - * index access support info (used only for an index relation) - * - * Note: only default support procs for each opclass are cached, namely - * those with lefttype and righttype equal to the opclass's opcintype. The - * arrays are indexed by support function number, which is a sufficient - * identifier given that restriction. - */ - MemoryContext rd_indexcxt; /* private memory cxt for this stuff */ - /* use "struct" here to avoid needing to include amapi.h: */ - const struct IndexAmRoutine *rd_indam; /* index AM's API struct */ - Oid *rd_opfamily; /* OIDs of op families for each index col */ - Oid *rd_opcintype; /* OIDs of opclass declared input data types */ - RegProcedure *rd_support; /* OIDs of support procedures */ - struct FmgrInfo *rd_supportinfo; /* lookup info for support procedures */ - int16 *rd_indoption; /* per-column AM-specific flags */ - List *rd_indexprs; /* index expression trees, if any */ - List *rd_indpred; /* index predicate tree, if any */ - Oid *rd_exclops; /* OIDs of exclusion operators, if any */ - Oid *rd_exclprocs; /* OIDs of exclusion ops' procs, if any */ - uint16 *rd_exclstrats; /* exclusion ops' strategy numbers, if any */ - Oid *rd_indcollation; /* OIDs of index collations */ - bytea **rd_opcoptions; /* parsed opclass-specific options */ - /* * rd_amcache is available for index and table AMs to cache private data * about the relation. This must be just a cache since it may get reset @@ -214,37 +256,6 @@ typedef struct RelationData */ void *rd_amcache; /* available for use by index/table AM */ - /* - * foreign-table support - * - * rd_fdwroutine must point to a single memory chunk palloc'd in - * CacheMemoryContext. It will be freed and reset to NULL on a relcache - * reset. - */ - - /* use "struct" here to avoid needing to include fdwapi.h: */ - struct FdwRoutine *rd_fdwroutine; /* cached function pointers, or NULL */ - - /* - * Hack for CLUSTER, rewriting ALTER TABLE, etc: when writing a new - * version of a table, we need to make any toast pointers inserted into it - * have the existing toast table's OID, not the OID of the transient toast - * table. If rd_toastoid isn't InvalidOid, it is the OID to place in - * toast pointers inserted into this rel. (Note it's set on the new - * version of the main heap, not the toast table itself.) This also - * causes toast_save_datum() to try to preserve toast value OIDs. - */ - Oid rd_toastoid; /* Real TOAST table's OID, or InvalidOid */ - - /* - * Type OID of the "chunk_id" column of this relation's TOAST table, i.e. - * OIDOID or OID8OID. - * - * This data is filled on demand by RelationGetToastChunkIdType(), not at - * relcache build time, so as to save in syscache lookups. - */ - Oid rd_toastchunkidtype; - bool pgstat_enabled; /* should relation stats be counted */ /* use "struct" here to avoid needing to include pgstat.h: */ struct PgStat_RelationStatus *pgstat_info; /* statistics collection area */ -- 2.55.0