Coverage Report

Created: 2025-07-03 06:49

/src/postgres/src/backend/utils/cache/relfilenumbermap.c
Line
Count
Source (jump to first uncovered line)
1
/*-------------------------------------------------------------------------
2
 *
3
 * relfilenumbermap.c
4
 *    relfilenumber to oid mapping cache.
5
 *
6
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
7
 * Portions Copyright (c) 1994, Regents of the University of California
8
 *
9
 * IDENTIFICATION
10
 *    src/backend/utils/cache/relfilenumbermap.c
11
 *
12
 *-------------------------------------------------------------------------
13
 */
14
#include "postgres.h"
15
16
#include "access/genam.h"
17
#include "access/htup_details.h"
18
#include "access/table.h"
19
#include "catalog/pg_class.h"
20
#include "catalog/pg_tablespace.h"
21
#include "miscadmin.h"
22
#include "utils/catcache.h"
23
#include "utils/fmgroids.h"
24
#include "utils/hsearch.h"
25
#include "utils/inval.h"
26
#include "utils/relfilenumbermap.h"
27
#include "utils/relmapper.h"
28
29
/* Hash table for information about each relfilenumber <-> oid pair */
30
static HTAB *RelfilenumberMapHash = NULL;
31
32
/* built first time through in InitializeRelfilenumberMap */
33
static ScanKeyData relfilenumber_skey[2];
34
35
typedef struct
36
{
37
  Oid     reltablespace;
38
  RelFileNumber relfilenumber;
39
} RelfilenumberMapKey;
40
41
typedef struct
42
{
43
  RelfilenumberMapKey key;  /* lookup key - must be first */
44
  Oid     relid;      /* pg_class.oid */
45
} RelfilenumberMapEntry;
46
47
/*
48
 * RelfilenumberMapInvalidateCallback
49
 *    Flush mapping entries when pg_class is updated in a relevant fashion.
50
 */
51
static void
52
RelfilenumberMapInvalidateCallback(Datum arg, Oid relid)
53
0
{
54
0
  HASH_SEQ_STATUS status;
55
0
  RelfilenumberMapEntry *entry;
56
57
  /* callback only gets registered after creating the hash */
58
0
  Assert(RelfilenumberMapHash != NULL);
59
60
0
  hash_seq_init(&status, RelfilenumberMapHash);
61
0
  while ((entry = (RelfilenumberMapEntry *) hash_seq_search(&status)) != NULL)
62
0
  {
63
    /*
64
     * If relid is InvalidOid, signaling a complete reset, we must remove
65
     * all entries, otherwise just remove the specific relation's entry.
66
     * Always remove negative cache entries.
67
     */
68
0
    if (relid == InvalidOid || /* complete reset */
69
0
      entry->relid == InvalidOid || /* negative cache entry */
70
0
      entry->relid == relid) /* individual flushed relation */
71
0
    {
72
0
      if (hash_search(RelfilenumberMapHash,
73
0
              &entry->key,
74
0
              HASH_REMOVE,
75
0
              NULL) == NULL)
76
0
        elog(ERROR, "hash table corrupted");
77
0
    }
78
0
  }
79
0
}
80
81
/*
82
 * InitializeRelfilenumberMap
83
 *    Initialize cache, either on first use or after a reset.
84
 */
85
static void
86
InitializeRelfilenumberMap(void)
87
0
{
88
0
  HASHCTL   ctl;
89
0
  int     i;
90
91
  /* Make sure we've initialized CacheMemoryContext. */
92
0
  if (CacheMemoryContext == NULL)
93
0
    CreateCacheMemoryContext();
94
95
  /* build skey */
96
0
  MemSet(&relfilenumber_skey, 0, sizeof(relfilenumber_skey));
97
98
0
  for (i = 0; i < 2; i++)
99
0
  {
100
0
    fmgr_info_cxt(F_OIDEQ,
101
0
            &relfilenumber_skey[i].sk_func,
102
0
            CacheMemoryContext);
103
0
    relfilenumber_skey[i].sk_strategy = BTEqualStrategyNumber;
104
0
    relfilenumber_skey[i].sk_subtype = InvalidOid;
105
0
    relfilenumber_skey[i].sk_collation = InvalidOid;
106
0
  }
107
108
0
  relfilenumber_skey[0].sk_attno = Anum_pg_class_reltablespace;
109
0
  relfilenumber_skey[1].sk_attno = Anum_pg_class_relfilenode;
110
111
  /*
112
   * Only create the RelfilenumberMapHash now, so we don't end up partially
113
   * initialized when fmgr_info_cxt() above ERRORs out with an out of memory
114
   * error.
115
   */
116
0
  ctl.keysize = sizeof(RelfilenumberMapKey);
117
0
  ctl.entrysize = sizeof(RelfilenumberMapEntry);
118
0
  ctl.hcxt = CacheMemoryContext;
119
120
0
  RelfilenumberMapHash =
121
0
    hash_create("RelfilenumberMap cache", 64, &ctl,
122
0
          HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
123
124
  /* Watch for invalidation events. */
125
0
  CacheRegisterRelcacheCallback(RelfilenumberMapInvalidateCallback,
126
0
                  (Datum) 0);
127
0
}
128
129
/*
130
 * Map a relation's (tablespace, relfilenumber) to a relation's oid and cache
131
 * the result.
132
 *
133
 * Returns InvalidOid if no relation matching the criteria could be found.
134
 */
135
Oid
136
RelidByRelfilenumber(Oid reltablespace, RelFileNumber relfilenumber)
137
0
{
138
0
  RelfilenumberMapKey key;
139
0
  RelfilenumberMapEntry *entry;
140
0
  bool    found;
141
0
  SysScanDesc scandesc;
142
0
  Relation  relation;
143
0
  HeapTuple ntp;
144
0
  Oid     relid;
145
146
0
  if (RelfilenumberMapHash == NULL)
147
0
    InitializeRelfilenumberMap();
148
149
  /* pg_class will show 0 when the value is actually MyDatabaseTableSpace */
150
0
  if (reltablespace == MyDatabaseTableSpace)
151
0
    reltablespace = 0;
152
153
0
  MemSet(&key, 0, sizeof(key));
154
0
  key.reltablespace = reltablespace;
155
0
  key.relfilenumber = relfilenumber;
156
157
  /*
158
   * Check cache and return entry if one is found. Even if no target
159
   * relation can be found later on we store the negative match and return a
160
   * InvalidOid from cache. That's not really necessary for performance
161
   * since querying invalid values isn't supposed to be a frequent thing,
162
   * but it's basically free.
163
   */
164
0
  entry = hash_search(RelfilenumberMapHash, &key, HASH_FIND, &found);
165
166
0
  if (found)
167
0
    return entry->relid;
168
169
  /* ok, no previous cache entry, do it the hard way */
170
171
  /* initialize empty/negative cache entry before doing the actual lookups */
172
0
  relid = InvalidOid;
173
174
0
  if (reltablespace == GLOBALTABLESPACE_OID)
175
0
  {
176
    /*
177
     * Ok, shared table, check relmapper.
178
     */
179
0
    relid = RelationMapFilenumberToOid(relfilenumber, true);
180
0
  }
181
0
  else
182
0
  {
183
0
    ScanKeyData skey[2];
184
185
    /*
186
     * Not a shared table, could either be a plain relation or a
187
     * non-shared, nailed one, like e.g. pg_class.
188
     */
189
190
    /* check for plain relations by looking in pg_class */
191
0
    relation = table_open(RelationRelationId, AccessShareLock);
192
193
    /* copy scankey to local copy and set scan arguments */
194
0
    memcpy(skey, relfilenumber_skey, sizeof(skey));
195
0
    skey[0].sk_argument = ObjectIdGetDatum(reltablespace);
196
0
    skey[1].sk_argument = ObjectIdGetDatum(relfilenumber);
197
198
0
    scandesc = systable_beginscan(relation,
199
0
                    ClassTblspcRelfilenodeIndexId,
200
0
                    true,
201
0
                    NULL,
202
0
                    2,
203
0
                    skey);
204
205
0
    found = false;
206
207
0
    while (HeapTupleIsValid(ntp = systable_getnext(scandesc)))
208
0
    {
209
0
      Form_pg_class classform = (Form_pg_class) GETSTRUCT(ntp);
210
211
0
      if (found)
212
0
        elog(ERROR,
213
0
           "unexpected duplicate for tablespace %u, relfilenumber %u",
214
0
           reltablespace, relfilenumber);
215
0
      found = true;
216
217
0
      Assert(classform->reltablespace == reltablespace);
218
0
      Assert(classform->relfilenode == relfilenumber);
219
0
      relid = classform->oid;
220
0
    }
221
222
0
    systable_endscan(scandesc);
223
0
    table_close(relation, AccessShareLock);
224
225
    /* check for tables that are mapped but not shared */
226
0
    if (!found)
227
0
      relid = RelationMapFilenumberToOid(relfilenumber, false);
228
0
  }
229
230
  /*
231
   * Only enter entry into cache now, our opening of pg_class could have
232
   * caused cache invalidations to be executed which would have deleted a
233
   * new entry if we had entered it above.
234
   */
235
0
  entry = hash_search(RelfilenumberMapHash, &key, HASH_ENTER, &found);
236
0
  if (found)
237
0
    elog(ERROR, "corrupted hashtable");
238
0
  entry->relid = relid;
239
240
0
  return relid;
241
0
}