| /*------------------------------------------------------------------------- |
| * |
| * attoptcache.c |
| * Attribute options cache management. |
| * |
| * Attribute options are cached separately from the fixed-size portion of |
| * pg_attribute entries, which are handled by the relcache. |
| * |
| * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * IDENTIFICATION |
| * src/backend/utils/cache/attoptcache.c |
| * |
| *------------------------------------------------------------------------- |
| */ |
| #include "postgres.h" |
| |
| #include "access/reloptions.h" |
| #include "utils/attoptcache.h" |
| #include "utils/catcache.h" |
| #include "utils/hsearch.h" |
| #include "utils/inval.h" |
| #include "utils/syscache.h" |
| |
| |
| /* Hash table for information about each attribute's options */ |
| static HTAB *AttoptCacheHash = NULL; |
| |
| /* attrelid and attnum form the lookup key, and must appear first */ |
| typedef struct |
| { |
| Oid attrelid; |
| int attnum; |
| } AttoptCacheKey; |
| |
| typedef struct |
| { |
| AttoptCacheKey key; /* lookup key - must be first */ |
| AttributeOpts *opts; /* options, or NULL if none */ |
| } AttoptCacheEntry; |
| |
| |
| /* |
| * InvalidateAttoptCacheCallback |
| * Flush all cache entries when pg_attribute is updated. |
| * |
| * When pg_attribute is updated, we must flush the cache entry at least |
| * for that attribute. Currently, we just flush them all. Since attribute |
| * options are not currently used in performance-critical paths (such as |
| * query execution), this seems OK. |
| */ |
| static void |
| InvalidateAttoptCacheCallback(Datum arg, int cacheid, uint32 hashvalue) |
| { |
| HASH_SEQ_STATUS status; |
| AttoptCacheEntry *attopt; |
| |
| hash_seq_init(&status, AttoptCacheHash); |
| while ((attopt = (AttoptCacheEntry *) hash_seq_search(&status)) != NULL) |
| { |
| if (attopt->opts) |
| pfree(attopt->opts); |
| if (hash_search(AttoptCacheHash, |
| (void *) &attopt->key, |
| HASH_REMOVE, |
| NULL) == NULL) |
| elog(ERROR, "hash table corrupted"); |
| } |
| } |
| |
| /* |
| * InitializeAttoptCache |
| * Initialize the attribute options cache. |
| */ |
| static void |
| InitializeAttoptCache(void) |
| { |
| HASHCTL ctl; |
| |
| /* Initialize the hash table. */ |
| ctl.keysize = sizeof(AttoptCacheKey); |
| ctl.entrysize = sizeof(AttoptCacheEntry); |
| AttoptCacheHash = |
| hash_create("Attopt cache", 256, &ctl, |
| HASH_ELEM | HASH_BLOBS); |
| |
| /* Make sure we've initialized CacheMemoryContext. */ |
| if (!CacheMemoryContext) |
| CreateCacheMemoryContext(); |
| |
| /* Watch for invalidation events. */ |
| CacheRegisterSyscacheCallback(ATTNUM, |
| InvalidateAttoptCacheCallback, |
| (Datum) 0); |
| } |
| |
| /* |
| * get_attribute_options |
| * Fetch attribute options for a specified table OID. |
| */ |
| AttributeOpts * |
| get_attribute_options(Oid attrelid, int attnum) |
| { |
| AttoptCacheKey key; |
| AttoptCacheEntry *attopt; |
| AttributeOpts *result; |
| HeapTuple tp; |
| |
| /* Find existing cache entry, if any. */ |
| if (!AttoptCacheHash) |
| InitializeAttoptCache(); |
| memset(&key, 0, sizeof(key)); /* make sure any padding bits are unset */ |
| key.attrelid = attrelid; |
| key.attnum = attnum; |
| attopt = |
| (AttoptCacheEntry *) hash_search(AttoptCacheHash, |
| (void *) &key, |
| HASH_FIND, |
| NULL); |
| |
| /* Not found in Attopt cache. Construct new cache entry. */ |
| if (!attopt) |
| { |
| AttributeOpts *opts; |
| |
| tp = SearchSysCache2(ATTNUM, |
| ObjectIdGetDatum(attrelid), |
| Int16GetDatum(attnum)); |
| |
| /* |
| * If we don't find a valid HeapTuple, it must mean someone has |
| * managed to request attribute details for a non-existent attribute. |
| * We treat that case as if no options were specified. |
| */ |
| if (!HeapTupleIsValid(tp)) |
| opts = NULL; |
| else |
| { |
| Datum datum; |
| bool isNull; |
| |
| datum = SysCacheGetAttr(ATTNUM, |
| tp, |
| Anum_pg_attribute_attoptions, |
| &isNull); |
| if (isNull) |
| opts = NULL; |
| else |
| { |
| bytea *bytea_opts = attribute_reloptions(datum, false); |
| |
| opts = MemoryContextAlloc(CacheMemoryContext, |
| VARSIZE(bytea_opts)); |
| memcpy(opts, bytea_opts, VARSIZE(bytea_opts)); |
| } |
| ReleaseSysCache(tp); |
| } |
| |
| /* |
| * It's important to create the actual cache entry only after reading |
| * pg_attribute, since the read could cause a cache flush. |
| */ |
| attopt = (AttoptCacheEntry *) hash_search(AttoptCacheHash, |
| (void *) &key, |
| HASH_ENTER, |
| NULL); |
| attopt->opts = opts; |
| } |
| |
| /* Return results in caller's memory context. */ |
| if (attopt->opts == NULL) |
| return NULL; |
| result = palloc(VARSIZE(attopt->opts)); |
| memcpy(result, attopt->opts, VARSIZE(attopt->opts)); |
| return result; |
| } |