/src/postgres/src/backend/utils/cache/attoptcache.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * attoptcache.c |
4 | | * Attribute options cache management. |
5 | | * |
6 | | * Attribute options are cached separately from the fixed-size portion of |
7 | | * pg_attribute entries, which are handled by the relcache. |
8 | | * |
9 | | * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group |
10 | | * Portions Copyright (c) 1994, Regents of the University of California |
11 | | * |
12 | | * IDENTIFICATION |
13 | | * src/backend/utils/cache/attoptcache.c |
14 | | * |
15 | | *------------------------------------------------------------------------- |
16 | | */ |
17 | | #include "postgres.h" |
18 | | |
19 | | #include "access/reloptions.h" |
20 | | #include "utils/attoptcache.h" |
21 | | #include "utils/catcache.h" |
22 | | #include "utils/hsearch.h" |
23 | | #include "utils/inval.h" |
24 | | #include "utils/syscache.h" |
25 | | #include "varatt.h" |
26 | | |
27 | | |
28 | | /* Hash table for information about each attribute's options */ |
29 | | static HTAB *AttoptCacheHash = NULL; |
30 | | |
31 | | /* attrelid and attnum form the lookup key, and must appear first */ |
32 | | typedef struct |
33 | | { |
34 | | Oid attrelid; |
35 | | int attnum; |
36 | | } AttoptCacheKey; |
37 | | |
38 | | typedef struct |
39 | | { |
40 | | AttoptCacheKey key; /* lookup key - must be first */ |
41 | | AttributeOpts *opts; /* options, or NULL if none */ |
42 | | } AttoptCacheEntry; |
43 | | |
44 | | |
45 | | /* |
46 | | * InvalidateAttoptCacheCallback |
47 | | * Flush cache entry (or entries) when pg_attribute is updated. |
48 | | * |
49 | | * When pg_attribute is updated, we must flush the cache entry at least |
50 | | * for that attribute. |
51 | | */ |
52 | | static void |
53 | | InvalidateAttoptCacheCallback(Datum arg, int cacheid, uint32 hashvalue) |
54 | 0 | { |
55 | 0 | HASH_SEQ_STATUS status; |
56 | 0 | AttoptCacheEntry *attopt; |
57 | | |
58 | | /* |
59 | | * By convention, zero hash value is passed to the callback as a sign that |
60 | | * it's time to invalidate the whole cache. See sinval.c, inval.c and |
61 | | * InvalidateSystemCachesExtended(). |
62 | | */ |
63 | 0 | if (hashvalue == 0) |
64 | 0 | hash_seq_init(&status, AttoptCacheHash); |
65 | 0 | else |
66 | 0 | hash_seq_init_with_hash_value(&status, AttoptCacheHash, hashvalue); |
67 | |
|
68 | 0 | while ((attopt = (AttoptCacheEntry *) hash_seq_search(&status)) != NULL) |
69 | 0 | { |
70 | 0 | if (attopt->opts) |
71 | 0 | pfree(attopt->opts); |
72 | 0 | if (hash_search(AttoptCacheHash, |
73 | 0 | &attopt->key, |
74 | 0 | HASH_REMOVE, |
75 | 0 | NULL) == NULL) |
76 | 0 | elog(ERROR, "hash table corrupted"); |
77 | 0 | } |
78 | 0 | } |
79 | | |
80 | | /* |
81 | | * Hash function compatible with two-arg system cache hash function. |
82 | | */ |
83 | | static uint32 |
84 | | relatt_cache_syshash(const void *key, Size keysize) |
85 | 0 | { |
86 | 0 | const AttoptCacheKey *ckey = key; |
87 | |
|
88 | 0 | Assert(keysize == sizeof(*ckey)); |
89 | 0 | return GetSysCacheHashValue2(ATTNUM, ObjectIdGetDatum(ckey->attrelid), Int32GetDatum(ckey->attnum)); |
90 | 0 | } |
91 | | |
92 | | /* |
93 | | * InitializeAttoptCache |
94 | | * Initialize the attribute options cache. |
95 | | */ |
96 | | static void |
97 | | InitializeAttoptCache(void) |
98 | 0 | { |
99 | 0 | HASHCTL ctl; |
100 | | |
101 | | /* Initialize the hash table. */ |
102 | 0 | ctl.keysize = sizeof(AttoptCacheKey); |
103 | 0 | ctl.entrysize = sizeof(AttoptCacheEntry); |
104 | | |
105 | | /* |
106 | | * AttoptCacheEntry takes hash value from the system cache. For |
107 | | * AttoptCacheHash we use the same hash in order to speedup search by hash |
108 | | * value. This is used by hash_seq_init_with_hash_value(). |
109 | | */ |
110 | 0 | ctl.hash = relatt_cache_syshash; |
111 | |
|
112 | 0 | AttoptCacheHash = |
113 | 0 | hash_create("Attopt cache", 256, &ctl, |
114 | 0 | HASH_ELEM | HASH_FUNCTION); |
115 | | |
116 | | /* Make sure we've initialized CacheMemoryContext. */ |
117 | 0 | if (!CacheMemoryContext) |
118 | 0 | CreateCacheMemoryContext(); |
119 | | |
120 | | /* Watch for invalidation events. */ |
121 | 0 | CacheRegisterSyscacheCallback(ATTNUM, |
122 | 0 | InvalidateAttoptCacheCallback, |
123 | 0 | (Datum) 0); |
124 | 0 | } |
125 | | |
126 | | /* |
127 | | * get_attribute_options |
128 | | * Fetch attribute options for a specified table OID. |
129 | | */ |
130 | | AttributeOpts * |
131 | | get_attribute_options(Oid attrelid, int attnum) |
132 | 0 | { |
133 | 0 | AttoptCacheKey key; |
134 | 0 | AttoptCacheEntry *attopt; |
135 | 0 | AttributeOpts *result; |
136 | 0 | HeapTuple tp; |
137 | | |
138 | | /* Find existing cache entry, if any. */ |
139 | 0 | if (!AttoptCacheHash) |
140 | 0 | InitializeAttoptCache(); |
141 | 0 | memset(&key, 0, sizeof(key)); /* make sure any padding bits are unset */ |
142 | 0 | key.attrelid = attrelid; |
143 | 0 | key.attnum = attnum; |
144 | 0 | attopt = |
145 | 0 | (AttoptCacheEntry *) hash_search(AttoptCacheHash, |
146 | 0 | &key, |
147 | 0 | HASH_FIND, |
148 | 0 | NULL); |
149 | | |
150 | | /* Not found in Attopt cache. Construct new cache entry. */ |
151 | 0 | if (!attopt) |
152 | 0 | { |
153 | 0 | AttributeOpts *opts; |
154 | |
|
155 | 0 | tp = SearchSysCache2(ATTNUM, |
156 | 0 | ObjectIdGetDatum(attrelid), |
157 | 0 | Int16GetDatum(attnum)); |
158 | | |
159 | | /* |
160 | | * If we don't find a valid HeapTuple, it must mean someone has |
161 | | * managed to request attribute details for a non-existent attribute. |
162 | | * We treat that case as if no options were specified. |
163 | | */ |
164 | 0 | if (!HeapTupleIsValid(tp)) |
165 | 0 | opts = NULL; |
166 | 0 | else |
167 | 0 | { |
168 | 0 | Datum datum; |
169 | 0 | bool isNull; |
170 | |
|
171 | 0 | datum = SysCacheGetAttr(ATTNUM, |
172 | 0 | tp, |
173 | 0 | Anum_pg_attribute_attoptions, |
174 | 0 | &isNull); |
175 | 0 | if (isNull) |
176 | 0 | opts = NULL; |
177 | 0 | else |
178 | 0 | { |
179 | 0 | bytea *bytea_opts = attribute_reloptions(datum, false); |
180 | |
|
181 | 0 | opts = MemoryContextAlloc(CacheMemoryContext, |
182 | 0 | VARSIZE(bytea_opts)); |
183 | 0 | memcpy(opts, bytea_opts, VARSIZE(bytea_opts)); |
184 | 0 | } |
185 | 0 | ReleaseSysCache(tp); |
186 | 0 | } |
187 | | |
188 | | /* |
189 | | * It's important to create the actual cache entry only after reading |
190 | | * pg_attribute, since the read could cause a cache flush. |
191 | | */ |
192 | 0 | attopt = (AttoptCacheEntry *) hash_search(AttoptCacheHash, |
193 | 0 | &key, |
194 | 0 | HASH_ENTER, |
195 | 0 | NULL); |
196 | 0 | attopt->opts = opts; |
197 | 0 | } |
198 | | |
199 | | /* Return results in caller's memory context. */ |
200 | 0 | if (attopt->opts == NULL) |
201 | 0 | return NULL; |
202 | 0 | result = palloc(VARSIZE(attopt->opts)); |
203 | 0 | memcpy(result, attopt->opts, VARSIZE(attopt->opts)); |
204 | 0 | return result; |
205 | 0 | } |