Coverage Report

Created: 2025-09-27 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}