From 4c0c58d28ef7a11947b58900252c11f30165277d Mon Sep 17 00:00:00 2001 From: Matthias van de Meent Date: Wed, 25 Feb 2026 02:16:16 +0100 Subject: [PATCH v1 3/5] Deduplicate some index attributes in the relcache Many indexes have the same opclasses; so this deduplicates all fields that are directly dependent on the selected opclasses. This can save a significant amount of memory when partitioned indexes are involved, as those always have the same definition. I have a few ideas on how to implement this for other fields as well, but that's not needed in this patch. This does come at a cost of a slight increase in the amount of work required to load index relations into catalog caches; but I'd consider that a worthy trade-off. --- src/backend/access/index/indexam.c | 23 +- src/backend/utils/cache/relcache.c | 872 ++++++++++++++++++++++------- src/backend/utils/fmgr/fmgr.c | 96 +++- src/include/c.h | 5 + src/include/fmgr.h | 7 +- src/include/nodes/pathnodes.h | 2 +- src/include/utils/rel.h | 2 +- src/include/utils/relcache.h | 2 +- 8 files changed, 804 insertions(+), 205 deletions(-) diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c index 0bd0dd9a2f3..e3e6db75557 100644 --- a/src/backend/access/index/indexam.c +++ b/src/backend/access/index/indexam.c @@ -909,6 +909,7 @@ index_getprocinfo(Relation irel, { const RegProcedure *loc = irel->rd_support; RegProcedure procId; + Node *opcoptions; Assert(loc != NULL); @@ -924,17 +925,25 @@ index_getprocinfo(Relation irel, elog(ERROR, "missing support function %d for attribute %d of index \"%s\"", procnum, attnum, RelationGetRelationName(irel)); + /* + * irel->rd_supportinfo[].fn_expr is pre-populated during cache + * initialization; though only after optsproc has been used. + */ + opcoptions = locinfo->fn_expr; fmgr_info_cxt(procId, locinfo, irel->rd_indexcxt); if (procnum != optsproc) { - /* Initialize locinfo->fn_expr with opclass options Const */ - bytea **attoptions = RelationGetIndexAttOptions(irel, false); - MemoryContext oldcxt = MemoryContextSwitchTo(irel->rd_indexcxt); - - set_fn_opclass_options(locinfo, attoptions[attnum - 1]); - - MemoryContextSwitchTo(oldcxt); + /* + * Even without attoptions, opcoptions here would not be NULL, + * but instead would have ((Const *) opcoptions)->constisnull. + */ + if (opcoptions == NULL) + elog(PANIC, "Invalid opcoptions for valid Relation: NULL instead of Const"); + if (nodeTag(opcoptions) != T_Const) + elog(PANIC, "Unexpected node tag for opcoptions: %d, expected %d", + nodeTag(opcoptions), T_Const); + locinfo->fn_expr = opcoptions; } } diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 9767d78782e..0d6badb9f01 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -68,6 +68,7 @@ #include "commands/policy.h" #include "commands/publicationcmds.h" #include "commands/trigger.h" +#include "common/hashfn.h" #include "common/int.h" #include "miscadmin.h" #include "nodes/makefuncs.h" @@ -93,7 +94,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 @@ -137,6 +138,28 @@ typedef struct relidcacheent static HTAB *RelationIdCache; +typedef struct RelShapeCacheKey +{ + AttrNumber nkeyatts; + uint16 nsupport; + /* opclass-specific data */ + const Oid *opclass; + const bytea *const *opcoptions; /* Treated as an array of NULL + * elements when NULL */ +} RelShapeCacheKey; + +typedef struct RelShapeCacheEnt +{ + RelShapeCacheKey key; + int rsc_refcount; /* refcount */ + const Oid *rsc_opfamily; /* opfamilies of key.opclass */ + const Oid *rsc_opcintype; + const RegProcedure *rsc_support; /* support functions */ + FmgrInfo *rsc_supportinfo; /* can be updated */ +} RelShapeCacheEnt; + +static HTAB *RelShapeCache; + /* * This flag is false until we have prepared the critical relcache entries * that are needed to do indexscans on the tables read by relcache building. @@ -294,6 +317,7 @@ static void AtEOXact_cleanup(Relation relation, bool isCommit); static void AtEOSubXact_cleanup(Relation relation, bool isCommit, SubTransactionId mySubid, SubTransactionId parentSubid); static bool load_relcache_init_file(bool shared); +static bool load_relcache_init_index(Relation relation, FILE *fp); static void write_relcache_init_file(bool shared); static void write_item(const void *data, Size len, FILE *fp); @@ -315,13 +339,21 @@ static void CheckNNConstraintFetch(Relation relation); static int CheckConstraintCmp(const void *a, const void *b); static void InitIndexAmRoutine(Relation relation); static void IndexSupportInitialize(oidvector *indclass, AttrNumber nKeyAtts, StrategyNumber maxSupportNumber, - MemoryContext indexcxt, const Oid **rd_opfamily, const Oid **rd_opcintype, - const RegProcedure **rd_support, FmgrInfo **rd_supportinfo); + const Oid **rd_opfamily, const Oid **rd_opcintype, + const RegProcedure **rd_support, FmgrInfo **rd_supportinfo, + const bytea *const **rd_opcoptions); +static void IndexSupportDeregister(oidvector *indclass, AttrNumber nKeyAtts, + StrategyNumber maxSupportNumber, + const bytea * const*rd_opcoptions); +static bytea **IndexRelationGetAttOptions(Relation relation); static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid, StrategyNumber numSupport); static void RelationCacheInitFileRemoveInDir(const char *tblspcpath); static void unlink_initfile(const char *initfilename, int elevel); +static uint32 HashRelShapeKey(const void *key, Size keysize); +static int CmpRelShapeKey(const void *key1, const void *key2, Size keysize); +static void *CopyRelShapeKey(void *dest, const void *src, Size keysize); /* * ScanPgRelation @@ -1448,6 +1480,8 @@ RelationInitIndexAccessInfo(Relation relation) int indnatts; int indnkeyatts; uint16 amsupport; + const bytea *const *opcoptions; + bool hasopcoptions; /* * Make a copy of the pg_index entry for the index. Since pg_index @@ -1542,14 +1576,56 @@ RelationInitIndexAccessInfo(Relation relation) /* * Fill the support procedure OID array, as well as the info about - * opfamilies and opclass input types. (aminfo and supportinfo are left - * as zeroes, and are filled on-the-fly when used) + * opfamilies and opclass input types. (opcoptions and supportinfo + * are left as zeroes; opcoptions is filled down below, supportinfo + * mostly only on access) */ - IndexSupportInitialize(indclass, indnkeyatts, amsupport, indexcxt, + IndexSupportInitialize(indclass, indnkeyatts, amsupport, &relation->rd_opfamily, &relation->rd_opcintype, &relation->rd_support, - &relation->rd_supportinfo); + &relation->rd_supportinfo, + &relation->rd_opcoptions); + + opcoptions = (const bytea *const *) IndexRelationGetAttOptions(relation); + + for (int i = 0; i < indnkeyatts; i++) + { + if (opcoptions[i] == NULL) + continue; + hasopcoptions = true; + } + + if (hasopcoptions) + { + IndexSupportDeregister(indclass, indnkeyatts, amsupport, + relation->rd_opcoptions); + + relation->rd_opcoptions = opcoptions; + + /* + * This fills in all fields, again; now whilst taking the new opcoptions + * into account. + */ + IndexSupportInitialize(indclass, indnkeyatts, amsupport, + &relation->rd_opfamily, + &relation->rd_opcintype, + &relation->rd_support, + &relation->rd_supportinfo, + &relation->rd_opcoptions); + } + + /* + * Opcoptions was either copied into the cache context, or wasn't used; + * in either case it's now unused and we should free it. + */ + Assert(opcoptions != relation->rd_opcoptions); + for (int i = 0; i < indnkeyatts; i++) + { + if (opcoptions[i] != NULL) + pfree(unconstify(bytea *, opcoptions[i])); + } + pfree(unconstify_array(const bytea *, opcoptions)); /* * Similarly extract indoption and copy it to the cache entry @@ -1562,8 +1638,6 @@ RelationInitIndexAccessInfo(Relation relation) indoption = (int2vector *) DatumGetPointer(indoptionDatum); memcpy(relation->rd_indoption, indoption->values, indnkeyatts * sizeof(int16)); - (void) RelationGetIndexAttOptions(relation, false); - /* * expressions, predicate, exclusion caches will be filled later */ @@ -1591,58 +1665,170 @@ RelationInitIndexAccessInfo(Relation relation) static void IndexSupportInitialize(oidvector *indclass, AttrNumber nKeyAtts, StrategyNumber maxSupportNumber, - MemoryContext indexcxt, const Oid **rd_opfamily, const Oid **rd_opcintype, const RegProcedure **rd_support, - FmgrInfo **rd_supportinfo) + FmgrInfo **rd_supportinfo, + const bytea *const **rd_opcoptions) { - Oid *opFamily; - Oid *opcInType; - RegProcedure *indexSupport; + RelShapeCacheKey key = {0}; + RelShapeCacheEnt *entry; + bool found; - if (maxSupportNumber > 0) - { - int nprocs = maxSupportNumber * nKeyAtts; - rd_supportinfo[0] = (FmgrInfo *) - MemoryContextAllocZero(indexcxt, nprocs * sizeof(FmgrInfo)); - indexSupport = (RegProcedure *) - MemoryContextAllocZero(indexcxt, nprocs * sizeof(RegProcedure)); - } - else - { - *rd_supportinfo = NULL; - indexSupport = NULL; - } + key.nkeyatts = nKeyAtts; + key.nsupport = maxSupportNumber; + key.opclass = indclass->values; + key.opcoptions = *rd_opcoptions; + + entry = hash_search(RelShapeCache, &key, HASH_ENTER, &found); - opFamily = (Oid *) - MemoryContextAllocZero(indexcxt, nKeyAtts * sizeof(Oid)); - opcInType = (Oid *) - MemoryContextAllocZero(indexcxt, nKeyAtts * sizeof(Oid)); + Assert(entry != NULL); - for (int attIndex = 0; attIndex < nKeyAtts; attIndex++) + if (!found) { - OpClassCacheEnt *opcentry; + Oid *opFamily; + Oid *opcInType; + RegProcedure *support; + FmgrInfo *supportinfo; + + if (maxSupportNumber > 0) + { + supportinfo = (FmgrInfo *) + MemoryContextAllocZero(CacheMemoryContext, + maxSupportNumber * nKeyAtts * sizeof(FmgrInfo)); + support = (RegProcedure *) + MemoryContextAllocZero(CacheMemoryContext, + maxSupportNumber * nKeyAtts * sizeof(RegProcedure)); + } + else + { + supportinfo = NULL; + support = NULL; + } + + opFamily = (Oid *) + MemoryContextAllocZero(CacheMemoryContext, + nKeyAtts * sizeof(Oid)); + opcInType = (Oid *) + MemoryContextAllocZero(CacheMemoryContext, + nKeyAtts * sizeof(Oid)); + + for (int attIndex = 0; attIndex < nKeyAtts; attIndex++) + { + OpClassCacheEnt *opcentry; - if (!OidIsValid(indclass->values[attIndex])) - elog(ERROR, "bogus pg_index tuple"); + if (!OidIsValid(indclass->values[attIndex])) + elog(ERROR, "bogus pg_index tuple"); - /* look up the info for this opclass, using a cache */ - opcentry = LookupOpclassInfo(indclass->values[attIndex], - maxSupportNumber); + /* look up the info for this opclass, using a cache */ + opcentry = LookupOpclassInfo(indclass->values[attIndex], + maxSupportNumber); + + /* copy cached data into relcache entry */ + opFamily[attIndex] = opcentry->opcfamily; + opcInType[attIndex] = opcentry->opcintype; + + if (maxSupportNumber > 0) + { + /* copy support proc IDs into the support proc array */ + memcpy(&support[attIndex * maxSupportNumber], + opcentry->supportProcs, + maxSupportNumber * sizeof(RegProcedure)); + } + } - /* copy cached data into relcache entry */ - opFamily[attIndex] = opcentry->opcfamily; - opcInType[attIndex] = opcentry->opcintype; if (maxSupportNumber > 0) - memcpy(&indexSupport[attIndex * maxSupportNumber], - opcentry->supportProcs, - maxSupportNumber * sizeof(RegProcedure)); + { + MemoryContext cxt = MemoryContextSwitchTo(CacheMemoryContext); + set_fn_opclass_options_bulk(supportinfo, + entry->key.opcoptions, + maxSupportNumber, + nKeyAtts); + MemoryContextSwitchTo(cxt); + } + + entry->rsc_opfamily = opFamily; + entry->rsc_opcintype = opcInType; + entry->rsc_support = support; + entry->rsc_supportinfo = supportinfo; + entry->rsc_refcount = 1; + + Assert(maxSupportNumber == 0 || entry->rsc_supportinfo); + Assert(maxSupportNumber == 0 || entry->rsc_support); + Assert(maxSupportNumber == 0 || entry->key.opcoptions); } - rd_support[0] = indexSupport; - rd_opcintype[0] = opcInType; - rd_opfamily[0] = opFamily; + entry->rsc_refcount++; + + *rd_opcoptions = entry->key.opcoptions; + + *rd_opfamily = entry->rsc_opfamily; + *rd_opcintype = entry->rsc_opcintype; + *rd_support = entry->rsc_support; + *rd_supportinfo = entry->rsc_supportinfo; +} + +static void +IndexSupportDeregister(oidvector *indclass, AttrNumber nKeyAtts, + StrategyNumber maxSupportNumber, + const bytea *const *rd_opcoptions) +{ + RelShapeCacheKey key = {0}; + RelShapeCacheEnt *entry; + bool found; + + key.nkeyatts = nKeyAtts; + key.nsupport = maxSupportNumber; + key.opclass = indclass->values; + key.opcoptions = rd_opcoptions; + + entry = hash_search(RelShapeCache, &key, HASH_FIND, &found); + + Assert(found); + + entry->rsc_refcount--; + + if (entry->rsc_refcount > 1) + return; + + Assert(entry->rsc_refcount == 1); + /* + * Apart from key.opclass[], which is taken from an oidvector, all key + * fields are stored in RelationData and are expected to be passed + * directly by the caller. + */ + Assert(entry->key.opcoptions == key.opcoptions); + Assert(entry->key.opclass != key.opclass); + + /* + * Store the opclass reference, so we can use it after removing the hash + * entry. + */ + key.opclass = entry->key.opclass; + + /* free the cached elements */ + pfree(unconstify(Oid *, entry->rsc_opfamily)); + pfree(unconstify(Oid *, entry->rsc_opcintype)); + pfree(unconstify(RegProcedure *, entry->rsc_support)); + + clear_fn_opclass_options_bulk(entry->rsc_supportinfo, + entry->key.nkeyatts * entry->key.nsupport, + entry->key.nkeyatts); + + pfree(entry->rsc_supportinfo); + + /* Remove the hash entry */ + entry = hash_search(RelShapeCache, &key, HASH_REMOVE, &found); + Assert(found); + + /* free the key's allocated elements */ + pfree(unconstify(Oid *, key.opclass)); + for (int i = 0; i < key.nkeyatts; i++) + { + if (key.opcoptions[i] != NULL) + pfree(unconstify(bytea *, key.opcoptions[i])); + } + pfree(unconstify_array(const bytea *, key.opcoptions)); } /* @@ -2461,6 +2647,28 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc) /* break mutual link with stats entry */ pgstat_unlink_relation(relation); + if (relation->rd_isvalid && relation->rd_indam) + { + Datum indclassDatum; + bool isnull; + oidvector *indclass; + + Assert(relation->rd_indextuple != NULL); + + indclassDatum = fastgetattr(relation->rd_indextuple, + Anum_pg_index_indclass, + GetPgIndexDescriptor(), + &isnull); + Assert(!isnull); + + indclass = (oidvector *) DatumGetPointer(indclassDatum); + + IndexSupportDeregister(indclass, + IndexRelationGetNumberOfKeyAttributes(relation), + relation->rd_indam->amsupport, + relation->rd_opcoptions); + } + /* * Free all the subsidiary data structures of the relcache entry, then the * entry itself. @@ -4030,6 +4238,21 @@ RelationCacheInitialize(void) RelationIdCache = hash_create("Relcache by OID", INITRELCACHESIZE, &ctl, HASH_ELEM | HASH_BLOBS); + /* + * create hashtable that indexes and caches relation shapes + */ + ctl.keysize = sizeof(RelShapeCacheKey); + ctl.entrysize = sizeof(RelShapeCacheEnt); + ctl.keycopy = CopyRelShapeKey; + ctl.hash = HashRelShapeKey; + ctl.match = CmpRelShapeKey; + ctl.hcxt = CacheMemoryContext; + + RelShapeCache = hash_create("Relation shape cache", INITRELCACHESIZE, + &ctl, HASH_ELEM | + HASH_FUNCTION | HASH_COMPARE | HASH_KEYCOPY | + HASH_CONTEXT); + /* * reserve enough in_progress_list slots for many cases */ @@ -4401,6 +4624,192 @@ RelationCacheInitializePhase3(void) } } +static uint32 +HashRelShapeKey(const void *key, Size keysize) +{ + const RelShapeCacheKey *rskey = key; + uint32 hdr_hash, opc_hash, opcopt_hash; + + hdr_hash = hash_combine(hash_uint32(rskey->nkeyatts), + hash_uint32(rskey->nsupport)); + opc_hash = hash_any((void *) rskey->opclass, + sizeof(Oid) * rskey->nkeyatts); + + /* + * Opcoptions hashing is special; as it may be NULL in search keys. + * Additionally, the array (if present) can contain nulls. We must still + * hash these NULLs, because positionality matters: An index with + * (opts, NULL) must hash differently from one with (NULL, opts) or even + * (opts). If we ignored NULLs in the array, they'd be guaranteed to have + * an equal hash value, causing hash conflicts with performance issues + * as a result. + */ + opcopt_hash = 0; + if (rskey->opcoptions == NULL) + { + /* + * Only present for search operations, but regardless, we'll have to + * combine the hashes of every (missing) element of the NULL array. + */ + for (int i = 0; i < rskey->nkeyatts; i++) + opcopt_hash = hash_combine(opcopt_hash, 0); + } + else + { + for (int i = 0; i < rskey->nkeyatts; i++) + { + const bytea *opt = rskey->opcoptions[i]; + uint32 hash; + + if (opt == NULL) + hash = 0; + else + { + hash = hash_any((const unsigned char *) VARDATA(opt), + (int) VARSIZE(opt)); + } + + opcopt_hash = hash_combine(opcopt_hash, hash); + } + } + + return hash_combine(opcopt_hash, hash_combine(hdr_hash, opc_hash)); +} + +static int +CmpRelShapeKey(const void *key1, const void *key2, Size keysize) +{ + const RelShapeCacheKey *rskey1 = key1; + const RelShapeCacheKey *rskey2 = key2; + + if (rskey1->nkeyatts != rskey2->nkeyatts || + rskey1->nsupport != rskey2->nsupport) + return 1; + + if (rskey1->opclass != rskey2->opclass && + memcmp(rskey1->opclass, rskey2->opclass, + sizeof(Oid) * rskey1->nkeyatts) != 0) + return 1; + + /* both NULL, or point to the same memory */ + if (rskey1->opcoptions == rskey2->opcoptions) + return 0; + + /* + * One of the two arrays is null; they only match when the allocated + * array's elements are all NULL too + */ + if (rskey1->opcoptions == NULL || rskey2->opcoptions == NULL) + { + const bytea *const *opts; + + if (rskey1->opcoptions != NULL) + { + Assert(rskey2->opcoptions == NULL); + opts = rskey1->opcoptions; + } + else + { + Assert(rskey2->opcoptions != NULL); + opts = rskey2->opcoptions; + } + + for (int i = 0; i < rskey1->nkeyatts; i++) + { + if (opts[i] != NULL) + return 1; + } + + return 0; + } + + /* + * Compare the options elements; now that we've established that + * both arrays are non-null. + */ + for (int i = 0; i < rskey1->nkeyatts; i++) + { + const bytea *a = rskey1->opcoptions[i]; + const bytea *b = rskey2->opcoptions[i]; + + /* both elements are NULL, or point to the same value */ + if (a == b) + continue; + + /* not the same: only one of the elements is NULL */ + if (a == NULL || b == NULL) + return 1; + + /* different; if the sizes are different */ + if (VARSIZE(a) != VARSIZE(b)) + return 1; + + /* different; if the data in the options is different */ + if (memcmp(VARDATA(a), VARDATA(b), VARSIZE(a)) != 0) + return 1; + } + + /* no differences detected */ + return 0; +} + +static void * +CopyRelShapeKey(void *dest, const void *src, Size keysize) +{ + RelShapeCacheKey *destrskey = dest; + const RelShapeCacheKey *srcrskey = src; + Oid *opclass; + const bytea **opcoptions; + + destrskey->nkeyatts = srcrskey->nkeyatts; + destrskey->nsupport = srcrskey->nsupport; + + opclass = MemoryContextAlloc(CacheMemoryContext, + sizeof(Oid) * srcrskey->nkeyatts); + opcoptions = MemoryContextAlloc(CacheMemoryContext, + sizeof(bytea *) * srcrskey->nkeyatts); + + memcpy(opclass, srcrskey->opclass, sizeof(Oid) * srcrskey->nkeyatts); + + if (srcrskey->opcoptions != NULL) + { + for (int i = 0; i < srcrskey->nkeyatts; i++) + { + const bytea *srcopt = srcrskey->opcoptions[i]; + Size srcsize; + + if (srcopt != NULL) + { + bytea *newopt; + + srcsize = VARSIZE(srcopt); + newopt = MemoryContextAlloc(CacheMemoryContext, srcsize + VARHDRSZ); + SET_VARSIZE(newopt, srcsize); + memcpy(VARDATA(newopt), VARDATA(srcopt), srcsize); + opcoptions[i] = newopt; + } + else + { + opcoptions[i] = NULL; + } + } + } + else + { + for (int i = 0; i < srcrskey->nkeyatts; i++) + opcoptions[i] = NULL; + } + + destrskey->opclass = opclass; + destrskey->opcoptions = opcoptions; + + Assert(HashRelShapeKey(src, keysize) == HashRelShapeKey(dest, keysize)); + Assert(CmpRelShapeKey(src, dest, keysize) == 0); + + return destrskey; +} + + /* * Load one critical system index into the relcache * @@ -6006,13 +6415,13 @@ RelationBuildPublicationDesc(Relation relation, PublicationDesc *pubdesc) } static bytea ** -CopyIndexAttOptions(bytea **srcopts, int natts) +CopyIndexAttOptions(const bytea *const *srcopts, int natts) { bytea **opts = palloc_array(bytea *, natts); for (int i = 0; i < natts; i++) { - bytea *opt = srcopts[i]; + const bytea *opt = srcopts[i]; opts[i] = !opt ? NULL : (bytea *) DatumGetPointer(datumCopy(PointerGetDatum(opt), false, -1)); @@ -6021,29 +6430,19 @@ CopyIndexAttOptions(bytea **srcopts, int natts) return opts; } -/* - * RelationGetIndexAttOptions - * get AM/opclass-specific options for an index parsed into a binary form - */ -bytea ** -RelationGetIndexAttOptions(Relation relation, bool copy) +static bytea ** +IndexRelationGetAttOptions(Relation relation) { - MemoryContext oldcxt; - bytea **opts = relation->rd_opcoptions; - Oid relid = RelationGetRelid(relation); - int natts = IndexRelationGetNumberOfKeyAttributes(relation); - int i; - - /* Try to copy cached options. */ - if (opts) - return copy ? CopyIndexAttOptions(opts, natts) : opts; + bytea **opts; + int natts = IndexRelationGetNumberOfKeyAttributes(relation); + Oid relid = relation->rd_id; /* Get and parse opclass options. */ opts = palloc0_array(bytea *, natts); - for (i = 0; i < natts; i++) + if (criticalRelcachesBuilt && relid != AttributeRelidNumIndexId) { - if (criticalRelcachesBuilt && relid != AttributeRelidNumIndexId) + for (int i = 0; i < natts; i++) { Datum attoptions = get_attoptions(relid, i + 1); @@ -6054,9 +6453,32 @@ RelationGetIndexAttOptions(Relation relation, bool copy) } } + return opts; +} + +/* + * RelationGetIndexAttOptions + * get AM/opclass-specific options for an index parsed into a binary form + */ +const bytea *const * +RelationGetIndexAttOptions(Relation relation, bool copy) +{ + MemoryContext oldcxt; + const bytea *const *opts = relation->rd_opcoptions; + int natts = IndexRelationGetNumberOfKeyAttributes(relation); + int i; + + /* Try to copy cached options. */ + if (opts) + return copy ? (const bytea *const *) CopyIndexAttOptions(opts, natts) : opts; + + /* Get and parse opclass options. */ + opts = (const bytea *const *) IndexRelationGetAttOptions(relation); + /* Copy parsed options to the cache. */ oldcxt = MemoryContextSwitchTo(relation->rd_indexcxt); - relation->rd_opcoptions = CopyIndexAttOptions(opts, natts); + relation->rd_opcoptions = (const bytea *const *) + CopyIndexAttOptions(opts, natts); MemoryContextSwitchTo(oldcxt); if (copy) @@ -6065,10 +6487,10 @@ RelationGetIndexAttOptions(Relation relation, bool copy) for (i = 0; i < natts; i++) { if (opts[i]) - pfree(opts[i]); + pfree(unconstify(bytea *, opts[i])); } - pfree(opts); + pfree(unconstify_array(const bytea *, opts)); return relation->rd_opcoptions; } @@ -6346,119 +6768,12 @@ load_relcache_init_file(bool shared) */ if (rel->rd_rel->relkind == RELKIND_INDEX) { - MemoryContext indexcxt; - Oid *opfamily; - Oid *opcintype; - RegProcedure *support; - int nsupport; - int16 *indoption; - Oid *indcollation; - /* Count nailed indexes to ensure we have 'em all */ if (rel->rd_isnailed) nailed_indexes++; - /* read the pg_index tuple */ - if (fread(&len, 1, sizeof(len), fp) != sizeof(len)) - goto read_failed; - - rel->rd_indextuple = (HeapTuple) palloc(len); - if (fread(rel->rd_indextuple, 1, len, fp) != len) - goto read_failed; - - /* Fix up internal pointers in the tuple -- see heap_copytuple */ - rel->rd_indextuple->t_data = (HeapTupleHeader) ((char *) rel->rd_indextuple + HEAPTUPLESIZE); - rel->rd_index = (Form_pg_index) GETSTRUCT(rel->rd_indextuple); - - /* - * prepare index info context --- parameters should match - * RelationInitIndexAccessInfo - */ - indexcxt = AllocSetContextCreate(CacheMemoryContext, - "index info", - ALLOCSET_SMALL_SIZES); - rel->rd_indexcxt = indexcxt; - MemoryContextCopyAndSetIdentifier(indexcxt, - RelationGetRelationName(rel)); - - /* - * Now we can fetch the index AM's API struct. (We can't store - * that in the init file, since it contains function pointers that - * might vary across server executions. Fortunately, it should be - * safe to call the amhandler even while bootstrapping indexes.) - */ - InitIndexAmRoutine(rel); - - /* read the vector of opfamily OIDs */ - if (fread(&len, 1, sizeof(len), fp) != sizeof(len)) - goto read_failed; - - opfamily = (Oid *) MemoryContextAlloc(indexcxt, len); - if (fread(opfamily, 1, len, fp) != len) - goto read_failed; - - rel->rd_opfamily = opfamily; - - /* read the vector of opcintype OIDs */ - if (fread(&len, 1, sizeof(len), fp) != sizeof(len)) - goto read_failed; - - opcintype = (Oid *) MemoryContextAlloc(indexcxt, len); - if (fread(opcintype, 1, len, fp) != len) - goto read_failed; - - rel->rd_opcintype = opcintype; - - /* read the vector of support procedure OIDs */ - if (fread(&len, 1, sizeof(len), fp) != sizeof(len)) - goto read_failed; - support = (RegProcedure *) MemoryContextAlloc(indexcxt, len); - if (fread(support, 1, len, fp) != len) + if (!load_relcache_init_index(rel, fp)) goto read_failed; - - rel->rd_support = support; - - /* read the vector of collation OIDs */ - if (fread(&len, 1, sizeof(len), fp) != sizeof(len)) - goto read_failed; - - indcollation = (Oid *) MemoryContextAlloc(indexcxt, len); - if (fread(indcollation, 1, len, fp) != len) - goto read_failed; - - rel->rd_indcollation = indcollation; - - /* read the vector of indoption values */ - if (fread(&len, 1, sizeof(len), fp) != sizeof(len)) - goto read_failed; - - indoption = (int16 *) MemoryContextAlloc(indexcxt, len); - if (fread(indoption, 1, len, fp) != len) - goto read_failed; - - rel->rd_indoption = indoption; - - /* read the vector of opcoptions values */ - rel->rd_opcoptions = (bytea **) - MemoryContextAllocZero(indexcxt, sizeof(*rel->rd_opcoptions) * relform->relnatts); - - for (i = 0; i < relform->relnatts; i++) - { - if (fread(&len, 1, sizeof(len), fp) != sizeof(len)) - goto read_failed; - - if (len > 0) - { - rel->rd_opcoptions[i] = (bytea *) MemoryContextAlloc(indexcxt, len); - if (fread(rel->rd_opcoptions[i], 1, len, fp) != len) - goto read_failed; - } - } - - /* set up zeroed fmgr-info vector */ - nsupport = relform->relnatts * rel->rd_indam->amsupport; - rel->rd_supportinfo = (FmgrInfo *) - MemoryContextAllocZero(indexcxt, nsupport * sizeof(FmgrInfo)); } else { @@ -6619,6 +6934,171 @@ read_failed: return false; } +static bool +load_relcache_init_index(Relation rel, FILE *fp) +{ + MemoryContext indexcxt; + RelShapeCacheKey key; + const bytea **opcoptions; + RelShapeCacheEnt *entry; + Oid *opfamily; + Oid *opcintype; + RegProcedure *support; + int16 *indoption; + Oid *indcollation; + Oid indclass[INDEX_MAX_KEYS]; + Size len; + Datum indclassDatum; + bool isnull, found; + + /* read the pg_index tuple */ + if (fread(&len, 1, sizeof(len), fp) != sizeof(len)) + return false; + + Assert(len >= HEAPTUPLESIZE + SizeofHeapTupleHeader + sizeof(FormData_pg_index)); + + rel->rd_indextuple = (HeapTuple) palloc(len); + Assert(len != 0); + + if (fread(rel->rd_indextuple, 1, len, fp) != len) + return false; + Assert(rel->rd_indextuple->t_len >= 0); + + /* Fix up internal pointers in the tuple -- see heap_copytuple */ + rel->rd_indextuple->t_data = (HeapTupleHeader) (((char *) rel->rd_indextuple) + HEAPTUPLESIZE); + rel->rd_index = (Form_pg_index) GETSTRUCT(rel->rd_indextuple); + + /* + * prepare index info context --- parameters should match + * RelationInitIndexAccessInfo + */ + indexcxt = AllocSetContextCreate(CacheMemoryContext, + "index info", + ALLOCSET_SMALL_SIZES); + rel->rd_indexcxt = indexcxt; + MemoryContextCopyAndSetIdentifier(indexcxt, + RelationGetRelationName(rel)); + + /* + * Now we can fetch the index AM's API struct. (We can't store + * that in the init file, since it contains function pointers that + * might vary across server executions. Fortunately, it should be + * safe to call the amhandler even while bootstrapping indexes.) + */ + InitIndexAmRoutine(rel); + + key.nkeyatts = IndexRelationGetNumberOfKeyAttributes(rel); + key.nsupport = rel->rd_indam->amsupport; + + indclassDatum = fastgetattr(rel->rd_indextuple, + Anum_pg_index_indclass, + GetPgIndexDescriptor(), + &isnull); + Assert(!isnull); + memcpy(&indclass[0], + ((oidvector *) DatumGetPointer(indclassDatum))->values, + sizeof(Oid) * key.nkeyatts); + key.opclass = indclass; + + /* read the vector of opcoptions values */ + opcoptions = (const bytea **) + MemoryContextAlloc(CacheMemoryContext, sizeof(const bytea *) * key.nkeyatts); + + /* read the values */ + for (int i = 0; i < key.nkeyatts; i++) + { + if (fread(&len, 1, sizeof(len), fp) != sizeof(len)) + return false; + + if (len > 0) + { + bytea *read = (bytea *) + MemoryContextAlloc(CacheMemoryContext, len); + + Assert(len >= 4); + + if (fread(read, 1, len, fp) != len) + return false; + + opcoptions[i] = read; + } + else + opcoptions[i] = NULL; + } + + Assert(opcoptions); + Assert(key.nkeyatts > 0 && key.nkeyatts <= INDEX_MAX_KEYS); + Assert(key.nsupport); + + key.opcoptions = opcoptions; + entry = hash_search(RelShapeCache, &key, HASH_ENTER, &found); + +#define READ_ITEM(type, name, neachkey, context) \ + do { \ + if (fread(&len, 1, sizeof(len), fp) != sizeof(len)) \ + return false; \ + \ + if (len != sizeof(type) * (neachkey) * key.nkeyatts) \ + return false; \ + \ + name = (type *) \ + MemoryContextAlloc(context, len); \ + \ + if (fread(name, 1, len, fp) != len) \ + return false; \ + } while (0) + + READ_ITEM(Oid, opfamily, 1, CacheMemoryContext); + READ_ITEM(Oid, opcintype, 1, CacheMemoryContext); + READ_ITEM(RegProcedure, support, key.nsupport, CacheMemoryContext); + READ_ITEM(Oid, indcollation, 1, indexcxt); + READ_ITEM(int16, indoption, 1, indexcxt); + + if (!found) + { + MemoryContext cxt; + entry->rsc_refcount = 1; + entry->rsc_opfamily = opfamily; + entry->rsc_opcintype = opcintype; + entry->rsc_support = support; + entry->rsc_supportinfo = (FmgrInfo *) + MemoryContextAllocZero(CacheMemoryContext, + sizeof(FmgrInfo) * key.nkeyatts * key.nsupport); + + Assert(entry->key.opcoptions); + Assert(entry->key.opclass); + Assert(entry->rsc_supportinfo || entry->key.nsupport == 0); + Assert(entry->rsc_support || entry->key.nsupport == 0); + + cxt = MemoryContextSwitchTo(CacheMemoryContext); + + set_fn_opclass_options_bulk(entry->rsc_supportinfo, + entry->key.opcoptions, + key.nsupport, + key.nkeyatts); + + MemoryContextSwitchTo(cxt); + } + else + { + pfree(opfamily); + pfree(opcintype); + pfree(support); + } + + entry->rsc_refcount++; + + rel->rd_opfamily = entry->rsc_opfamily; + rel->rd_opcintype = entry->rsc_opcintype; + rel->rd_support = entry->rsc_support; + rel->rd_indcollation = indcollation; + rel->rd_indoption = indoption; + rel->rd_opcoptions = entry->key.opcoptions; + rel->rd_supportinfo = entry->rsc_supportinfo; + + return true; +} + /* * Write out a new initialization file with the current contents * of the relcache (either shared rels or local rels, as indicated). @@ -6743,46 +7223,54 @@ write_relcache_init_file(bool shared) */ if (rel->rd_rel->relkind == RELKIND_INDEX) { + int nkeyatts = IndexRelationGetNumberOfKeyAttributes(rel); + bool isnull; + Datum indclassDatum = + fastgetattr(rel->rd_indextuple, + Anum_pg_index_indclass, + GetPgIndexDescriptor(), + &isnull); + Assert(!isnull); + Assert(((oidvector *) DatumGetPointer(indclassDatum))->values[0] != InvalidOid); + /* write the pg_index tuple */ /* we assume this was created by heap_copytuple! */ write_item(rel->rd_indextuple, HEAPTUPLESIZE + rel->rd_indextuple->t_len, fp); + /* write the vector of opcoptions values */ + for (i = 0; i < nkeyatts; i++) + { + const bytea *opt = rel->rd_opcoptions[i]; + + write_item(opt, opt ? VARSIZE(opt) : 0, fp); + } + /* write the vector of opfamily OIDs */ write_item(rel->rd_opfamily, - relform->relnatts * sizeof(Oid), + nkeyatts * sizeof(Oid), fp); /* write the vector of opcintype OIDs */ write_item(rel->rd_opcintype, - relform->relnatts * sizeof(Oid), + nkeyatts * sizeof(Oid), fp); /* write the vector of support procedure OIDs */ write_item(rel->rd_support, - relform->relnatts * (rel->rd_indam->amsupport * sizeof(RegProcedure)), + nkeyatts * (rel->rd_indam->amsupport * sizeof(RegProcedure)), fp); /* write the vector of collation OIDs */ write_item(rel->rd_indcollation, - relform->relnatts * sizeof(Oid), + nkeyatts * sizeof(Oid), fp); /* write the vector of indoption values */ write_item(rel->rd_indoption, - relform->relnatts * sizeof(int16), + nkeyatts * sizeof(int16), fp); - - Assert(rel->rd_opcoptions); - - /* write the vector of opcoptions values */ - for (i = 0; i < relform->relnatts; i++) - { - bytea *opt = rel->rd_opcoptions[i]; - - write_item(opt, opt ? VARSIZE(opt) : 0, fp); - } } } diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c index b2fd6dbb73d..07adecc6e87 100644 --- a/src/backend/utils/fmgr/fmgr.c +++ b/src/backend/utils/fmgr/fmgr.c @@ -2027,19 +2027,111 @@ get_fn_expr_variadic(FmgrInfo *flinfo) } /* - * Set options to FmgrInfo of opclass support function. + * Set options of an uncached FmgrInfo of opclass support function. * * Opclass support functions are called outside of expressions. Thanks to that * we can use fn_expr to store opclass options as bytea constant. + * + * The relcache code will populate a relation's rd_supportinfo through + * set_fn_opclass_options_bulk(). This function is therefore mostly useful + * for non-default (e.g. cross-type) support procedures. */ void -set_fn_opclass_options(FmgrInfo *flinfo, bytea *options) +set_fn_opclass_options(FmgrInfo *flinfo, const bytea *options) { + Assert(flinfo->fn_expr == NULL); flinfo->fn_expr = (Node *) makeConst(BYTEAOID, -1, InvalidOid, -1, PointerGetDatum(options), options == NULL, false); } +/* used only in set/clear_fn_opclass_options_bulk */ +const static Const OPCOPTION_NULL_CONST = { + .xpr = { .type = T_Const }, + .consttype = BYTEAOID, + .consttypmod = -1, + .constcollid = InvalidOid, + .constlen = -1, + .constvalue = (Datum) (uintptr_t) NULL, + .constisnull = true, + .constbyval = false, + .location = -1 +}; + +/* + * Set options to FmgrInfos of opclass support functions. + * + * Opclass support functions are called outside of expressions, and so don't + * use the fn_expr field. As such, we use the field to store the index' + * opclass options inside a bytea-typed Const node. + * + * Note: For NULL opcopts, a sentinel OPCOPTION_NULL_CONST is used, all other + * options get their own Const allocation in the caller's memory context. + */ +void +set_fn_opclass_options_bulk(FmgrInfo *flinfo, const bytea *const *opcopts, int nsupport, int nkeycols) +{ + if (nsupport == 0) + return; + + Assert(flinfo != NULL); + Assert(opcopts != NULL); + + for (int attno = 0; attno < nkeycols; attno++) + { + Node *option; + const bytea *opcoption = opcopts[attno]; + + /* Avoid allocating memory in the common case of no opcoptions */ + if (opcoption == NULL) + option = (Node *) &OPCOPTION_NULL_CONST; + else + { + option = (Node *) makeConst(BYTEAOID, -1, InvalidOid, -1, + PointerGetDatum(opcoption), + false, false); + } + Assert(option != NULL); + + /* + * Put the Const container in the relevant support structs. + * + * We push the same allocation into all support functions of the same + * attribute, so we can avoid what would be duplicate allocations, and + * minimize duplicated allocations. + */ + for (int support = 0; support < nsupport; support++) + { + Assert(flinfo->fn_expr == NULL); + + flinfo->fn_expr = option; + flinfo++; + } + } +} + +/* + * Clear the opcopts from FmgrInfos of opclass support functions. + * +* Opclass support functions are called outside of expressions, and so don't + * use the fn_expr field. As such, we use the field to store the index' + * opclass options inside a bytea-typed Const node. + */ +void +clear_fn_opclass_options_bulk(FmgrInfo *flinfo, int ninfos, int nkeyatts) +{ + int stride = ninfos / nkeyatts; + Assert(ninfos % nkeyatts == 0); + + for (int i = 0; i < ninfos; i += stride) + { + /* avoid trying to free the sentinel for NULL opcinfos */ + if (i % stride == 0 && flinfo[i].fn_expr != (Node *) &OPCOPTION_NULL_CONST) + pfree(flinfo[i].fn_expr); + flinfo[i].fn_expr = NULL; + } +} + /* * Check if options are defined for opclass support function. */ diff --git a/src/include/c.h b/src/include/c.h index 20cfbac54e7..c49806bf447 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -1373,6 +1373,11 @@ typedef struct PGAlignedXLogBlock PGAlignedXLogBlock; #define unconstify(underlying_type, expr) \ (StaticAssertVariableIsOfTypeMacro(expr, const underlying_type), \ (underlying_type) (expr)) +#define unconstify_array(underlying_type, expr) \ + (StaticAssertExpr(__builtin_types_compatible_p(__typeof(expr), underlying_type const*), \ + "wrong cast"), \ + (underlying_type*) (expr)) + #define unvolatize(underlying_type, expr) \ (StaticAssertVariableIsOfTypeMacro(expr, volatile underlying_type), \ (underlying_type) (expr)) diff --git a/src/include/fmgr.h b/src/include/fmgr.h index 04b7914095f..a7679daca28 100644 --- a/src/include/fmgr.h +++ b/src/include/fmgr.h @@ -779,7 +779,12 @@ extern bool get_call_expr_arg_stable(Node *expr, int argnum); extern bool get_fn_expr_variadic(FmgrInfo *flinfo); extern bytea *get_fn_opclass_options(FmgrInfo *flinfo); extern bool has_fn_opclass_options(FmgrInfo *flinfo); -extern void set_fn_opclass_options(FmgrInfo *flinfo, bytea *options); +extern void set_fn_opclass_options(FmgrInfo *flinfo, const bytea *options); +extern void set_fn_opclass_options_bulk(FmgrInfo *flinfo, + const bytea * const*opcopts, + int ninfos, int nkeycols); +extern void clear_fn_opclass_options_bulk(FmgrInfo *flinfo, int ninfos, + int nkeyatts); extern bool CheckFunctionValidatorAccess(Oid validatorOid, Oid functionOid); /* diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index c48e656ce80..37a8eb0043e 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -1392,7 +1392,7 @@ typedef struct IndexOptInfo /* do NULLs come first in the sort order? or NULL if partitioned index */ bool *nulls_first pg_node_attr(array_size(nkeycolumns)); /* opclass-specific options for columns */ - bytea **opclassoptions pg_node_attr(read_write_ignore); + const bytea *const *opclassoptions pg_node_attr(read_write_ignore); /* which index cols can be returned in an index-only scan? */ bool *canreturn pg_node_attr(array_size(ncolumns)); /* OID of the access method (in pg_am) */ diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index 78e052bf34b..574e1ca9d2a 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -215,7 +215,7 @@ typedef struct RelationData 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 */ + const bytea *const *rd_opcoptions; /* parsed opclass-specific options */ /* * rd_amcache is available for index and table AMs to cache private data diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h index 89c27aa1529..b1af1a2ec3f 100644 --- a/src/include/utils/relcache.h +++ b/src/include/utils/relcache.h @@ -60,7 +60,7 @@ extern Oid RelationGetReplicaIndex(Relation relation); extern List *RelationGetIndexExpressions(Relation relation); extern List *RelationGetDummyIndexExpressions(Relation relation); extern List *RelationGetIndexPredicate(Relation relation); -extern bytea **RelationGetIndexAttOptions(Relation relation, bool copy); +extern const bytea *const *RelationGetIndexAttOptions(Relation relation, bool copy); /* * Which set of columns to return by RelationGetIndexAttrBitmap. -- 2.50.1 (Apple Git-155)