Coverage Report

Created: 2025-07-03 06:49

/src/postgres/src/backend/utils/cache/partcache.c
Line
Count
Source (jump to first uncovered line)
1
/*-------------------------------------------------------------------------
2
 *
3
 * partcache.c
4
 *    Support routines for manipulating partition information cached in
5
 *    relcache
6
 *
7
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
8
 * Portions Copyright (c) 1994, Regents of the University of California
9
 *
10
 * IDENTIFICATION
11
 *      src/backend/utils/cache/partcache.c
12
 *
13
 *-------------------------------------------------------------------------
14
*/
15
#include "postgres.h"
16
17
#include "access/hash.h"
18
#include "access/htup_details.h"
19
#include "access/nbtree.h"
20
#include "access/relation.h"
21
#include "catalog/partition.h"
22
#include "catalog/pg_opclass.h"
23
#include "catalog/pg_partitioned_table.h"
24
#include "miscadmin.h"
25
#include "nodes/makefuncs.h"
26
#include "nodes/nodeFuncs.h"
27
#include "optimizer/optimizer.h"
28
#include "partitioning/partbounds.h"
29
#include "utils/builtins.h"
30
#include "utils/lsyscache.h"
31
#include "utils/memutils.h"
32
#include "utils/partcache.h"
33
#include "utils/rel.h"
34
#include "utils/syscache.h"
35
36
37
static void RelationBuildPartitionKey(Relation relation);
38
static List *generate_partition_qual(Relation rel);
39
40
/*
41
 * RelationGetPartitionKey -- get partition key, if relation is partitioned
42
 *
43
 * Note: partition keys are not allowed to change after the partitioned rel
44
 * is created.  RelationClearRelation knows this and preserves rd_partkey
45
 * across relcache rebuilds, as long as the relation is open.  Therefore,
46
 * even though we hand back a direct pointer into the relcache entry, it's
47
 * safe for callers to continue to use that pointer as long as they hold
48
 * the relation open.
49
 */
50
PartitionKey
51
RelationGetPartitionKey(Relation rel)
52
0
{
53
0
  if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
54
0
    return NULL;
55
56
0
  if (unlikely(rel->rd_partkey == NULL))
57
0
    RelationBuildPartitionKey(rel);
58
59
0
  return rel->rd_partkey;
60
0
}
61
62
/*
63
 * RelationBuildPartitionKey
64
 *    Build partition key data of relation, and attach to relcache
65
 *
66
 * Partitioning key data is a complex structure; to avoid complicated logic to
67
 * free individual elements whenever the relcache entry is flushed, we give it
68
 * its own memory context, a child of CacheMemoryContext, which can easily be
69
 * deleted on its own.  To avoid leaking memory in that context in case of an
70
 * error partway through this function, the context is initially created as a
71
 * child of CurTransactionContext and only re-parented to CacheMemoryContext
72
 * at the end, when no further errors are possible.  Also, we don't make this
73
 * context the current context except in very brief code sections, out of fear
74
 * that some of our callees allocate memory on their own which would be leaked
75
 * permanently.
76
 */
77
static void
78
RelationBuildPartitionKey(Relation relation)
79
0
{
80
0
  Form_pg_partitioned_table form;
81
0
  HeapTuple tuple;
82
0
  bool    isnull;
83
0
  int     i;
84
0
  PartitionKey key;
85
0
  AttrNumber *attrs;
86
0
  oidvector  *opclass;
87
0
  oidvector  *collation;
88
0
  ListCell   *partexprs_item;
89
0
  Datum   datum;
90
0
  MemoryContext partkeycxt,
91
0
        oldcxt;
92
0
  int16   procnum;
93
94
0
  tuple = SearchSysCache1(PARTRELID,
95
0
              ObjectIdGetDatum(RelationGetRelid(relation)));
96
97
0
  if (!HeapTupleIsValid(tuple))
98
0
    elog(ERROR, "cache lookup failed for partition key of relation %u",
99
0
       RelationGetRelid(relation));
100
101
0
  partkeycxt = AllocSetContextCreate(CurTransactionContext,
102
0
                     "partition key",
103
0
                     ALLOCSET_SMALL_SIZES);
104
0
  MemoryContextCopyAndSetIdentifier(partkeycxt,
105
0
                    RelationGetRelationName(relation));
106
107
0
  key = (PartitionKey) MemoryContextAllocZero(partkeycxt,
108
0
                        sizeof(PartitionKeyData));
109
110
  /* Fixed-length attributes */
111
0
  form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
112
0
  key->strategy = form->partstrat;
113
0
  key->partnatts = form->partnatts;
114
115
  /* Validate partition strategy code */
116
0
  if (key->strategy != PARTITION_STRATEGY_LIST &&
117
0
    key->strategy != PARTITION_STRATEGY_RANGE &&
118
0
    key->strategy != PARTITION_STRATEGY_HASH)
119
0
    elog(ERROR, "invalid partition strategy \"%c\"", key->strategy);
120
121
  /*
122
   * We can rely on the first variable-length attribute being mapped to the
123
   * relevant field of the catalog's C struct, because all previous
124
   * attributes are non-nullable and fixed-length.
125
   */
126
0
  attrs = form->partattrs.values;
127
128
  /* But use the hard way to retrieve further variable-length attributes */
129
  /* Operator class */
130
0
  datum = SysCacheGetAttrNotNull(PARTRELID, tuple,
131
0
                   Anum_pg_partitioned_table_partclass);
132
0
  opclass = (oidvector *) DatumGetPointer(datum);
133
134
  /* Collation */
135
0
  datum = SysCacheGetAttrNotNull(PARTRELID, tuple,
136
0
                   Anum_pg_partitioned_table_partcollation);
137
0
  collation = (oidvector *) DatumGetPointer(datum);
138
139
  /* Expressions */
140
0
  datum = SysCacheGetAttr(PARTRELID, tuple,
141
0
              Anum_pg_partitioned_table_partexprs, &isnull);
142
0
  if (!isnull)
143
0
  {
144
0
    char     *exprString;
145
0
    Node     *expr;
146
147
0
    exprString = TextDatumGetCString(datum);
148
0
    expr = stringToNode(exprString);
149
0
    pfree(exprString);
150
151
    /*
152
     * Run the expressions through const-simplification since the planner
153
     * will be comparing them to similarly-processed qual clause operands,
154
     * and may fail to detect valid matches without this step; fix
155
     * opfuncids while at it.  We don't need to bother with
156
     * canonicalize_qual() though, because partition expressions should be
157
     * in canonical form already (ie, no need for OR-merging or constant
158
     * elimination).
159
     */
160
0
    expr = eval_const_expressions(NULL, expr);
161
0
    fix_opfuncids(expr);
162
163
0
    oldcxt = MemoryContextSwitchTo(partkeycxt);
164
0
    key->partexprs = (List *) copyObject(expr);
165
0
    MemoryContextSwitchTo(oldcxt);
166
0
  }
167
168
  /* Allocate assorted arrays in the partkeycxt, which we'll fill below */
169
0
  oldcxt = MemoryContextSwitchTo(partkeycxt);
170
0
  key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
171
0
  key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
172
0
  key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
173
0
  key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
174
175
0
  key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
176
0
  key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
177
0
  key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
178
0
  key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
179
0
  key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
180
0
  key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
181
0
  key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
182
0
  MemoryContextSwitchTo(oldcxt);
183
184
  /* determine support function number to search for */
185
0
  procnum = (key->strategy == PARTITION_STRATEGY_HASH) ?
186
0
    HASHEXTENDED_PROC : BTORDER_PROC;
187
188
  /* Copy partattrs and fill other per-attribute info */
189
0
  memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
190
0
  partexprs_item = list_head(key->partexprs);
191
0
  for (i = 0; i < key->partnatts; i++)
192
0
  {
193
0
    AttrNumber  attno = key->partattrs[i];
194
0
    HeapTuple opclasstup;
195
0
    Form_pg_opclass opclassform;
196
0
    Oid     funcid;
197
198
    /* Collect opfamily information */
199
0
    opclasstup = SearchSysCache1(CLAOID,
200
0
                   ObjectIdGetDatum(opclass->values[i]));
201
0
    if (!HeapTupleIsValid(opclasstup))
202
0
      elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
203
204
0
    opclassform = (Form_pg_opclass) GETSTRUCT(opclasstup);
205
0
    key->partopfamily[i] = opclassform->opcfamily;
206
0
    key->partopcintype[i] = opclassform->opcintype;
207
208
    /* Get a support function for the specified opfamily and datatypes */
209
0
    funcid = get_opfamily_proc(opclassform->opcfamily,
210
0
                   opclassform->opcintype,
211
0
                   opclassform->opcintype,
212
0
                   procnum);
213
0
    if (!OidIsValid(funcid))
214
0
      ereport(ERROR,
215
0
          (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
216
0
           errmsg("operator class \"%s\" of access method %s is missing support function %d for type %s",
217
0
              NameStr(opclassform->opcname),
218
0
              (key->strategy == PARTITION_STRATEGY_HASH) ?
219
0
              "hash" : "btree",
220
0
              procnum,
221
0
              format_type_be(opclassform->opcintype))));
222
223
0
    fmgr_info_cxt(funcid, &key->partsupfunc[i], partkeycxt);
224
225
    /* Collation */
226
0
    key->partcollation[i] = collation->values[i];
227
228
    /* Collect type information */
229
0
    if (attno != 0)
230
0
    {
231
0
      Form_pg_attribute att = TupleDescAttr(relation->rd_att, attno - 1);
232
233
0
      key->parttypid[i] = att->atttypid;
234
0
      key->parttypmod[i] = att->atttypmod;
235
0
      key->parttypcoll[i] = att->attcollation;
236
0
    }
237
0
    else
238
0
    {
239
0
      if (partexprs_item == NULL)
240
0
        elog(ERROR, "wrong number of partition key expressions");
241
242
0
      key->parttypid[i] = exprType(lfirst(partexprs_item));
243
0
      key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
244
0
      key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
245
246
0
      partexprs_item = lnext(key->partexprs, partexprs_item);
247
0
    }
248
0
    get_typlenbyvalalign(key->parttypid[i],
249
0
               &key->parttyplen[i],
250
0
               &key->parttypbyval[i],
251
0
               &key->parttypalign[i]);
252
253
0
    ReleaseSysCache(opclasstup);
254
0
  }
255
256
0
  ReleaseSysCache(tuple);
257
258
  /* Assert that we're not leaking any old data during assignments below */
259
0
  Assert(relation->rd_partkeycxt == NULL);
260
0
  Assert(relation->rd_partkey == NULL);
261
262
  /*
263
   * Success --- reparent our context and make the relcache point to the
264
   * newly constructed key
265
   */
266
0
  MemoryContextSetParent(partkeycxt, CacheMemoryContext);
267
0
  relation->rd_partkeycxt = partkeycxt;
268
0
  relation->rd_partkey = key;
269
0
}
270
271
/*
272
 * RelationGetPartitionQual
273
 *
274
 * Returns a list of partition quals
275
 */
276
List *
277
RelationGetPartitionQual(Relation rel)
278
0
{
279
  /* Quick exit */
280
0
  if (!rel->rd_rel->relispartition)
281
0
    return NIL;
282
283
0
  return generate_partition_qual(rel);
284
0
}
285
286
/*
287
 * get_partition_qual_relid
288
 *
289
 * Returns an expression tree describing the passed-in relation's partition
290
 * constraint.
291
 *
292
 * If the relation is not found, or is not a partition, or there is no
293
 * partition constraint, return NULL.  We must guard against the first two
294
 * cases because this supports a SQL function that could be passed any OID.
295
 * The last case can happen even if relispartition is true, when a default
296
 * partition is the only partition.
297
 */
298
Expr *
299
get_partition_qual_relid(Oid relid)
300
0
{
301
0
  Expr     *result = NULL;
302
303
  /* Do the work only if this relation exists and is a partition. */
304
0
  if (get_rel_relispartition(relid))
305
0
  {
306
0
    Relation  rel = relation_open(relid, AccessShareLock);
307
0
    List     *and_args;
308
309
0
    and_args = generate_partition_qual(rel);
310
311
    /* Convert implicit-AND list format to boolean expression */
312
0
    if (and_args == NIL)
313
0
      result = NULL;
314
0
    else if (list_length(and_args) > 1)
315
0
      result = makeBoolExpr(AND_EXPR, and_args, -1);
316
0
    else
317
0
      result = linitial(and_args);
318
319
    /* Keep the lock, to allow safe deparsing against the rel by caller. */
320
0
    relation_close(rel, NoLock);
321
0
  }
322
323
0
  return result;
324
0
}
325
326
/*
327
 * generate_partition_qual
328
 *
329
 * Generate partition predicate from rel's partition bound expression. The
330
 * function returns a NIL list if there is no predicate.
331
 *
332
 * We cache a copy of the result in the relcache entry, after constructing
333
 * it using the caller's context.  This approach avoids leaking any data
334
 * into long-lived cache contexts, especially if we fail partway through.
335
 */
336
static List *
337
generate_partition_qual(Relation rel)
338
0
{
339
0
  HeapTuple tuple;
340
0
  MemoryContext oldcxt;
341
0
  Datum   boundDatum;
342
0
  bool    isnull;
343
0
  List     *my_qual = NIL,
344
0
         *result = NIL;
345
0
  Oid     parentrelid;
346
0
  Relation  parent;
347
348
  /* Guard against stack overflow due to overly deep partition tree */
349
0
  check_stack_depth();
350
351
  /* If we already cached the result, just return a copy */
352
0
  if (rel->rd_partcheckvalid)
353
0
    return copyObject(rel->rd_partcheck);
354
355
  /*
356
   * Grab at least an AccessShareLock on the parent table.  Must do this
357
   * even if the partition has been partially detached, because transactions
358
   * concurrent with the detach might still be trying to use a partition
359
   * descriptor that includes it.
360
   */
361
0
  parentrelid = get_partition_parent(RelationGetRelid(rel), true);
362
0
  parent = relation_open(parentrelid, AccessShareLock);
363
364
  /* Get pg_class.relpartbound */
365
0
  tuple = SearchSysCache1(RELOID,
366
0
              ObjectIdGetDatum(RelationGetRelid(rel)));
367
0
  if (!HeapTupleIsValid(tuple))
368
0
    elog(ERROR, "cache lookup failed for relation %u",
369
0
       RelationGetRelid(rel));
370
371
0
  boundDatum = SysCacheGetAttr(RELOID, tuple,
372
0
                 Anum_pg_class_relpartbound,
373
0
                 &isnull);
374
0
  if (!isnull)
375
0
  {
376
0
    PartitionBoundSpec *bound;
377
378
0
    bound = castNode(PartitionBoundSpec,
379
0
             stringToNode(TextDatumGetCString(boundDatum)));
380
381
0
    my_qual = get_qual_from_partbound(parent, bound);
382
0
  }
383
384
0
  ReleaseSysCache(tuple);
385
386
  /* Add the parent's quals to the list (if any) */
387
0
  if (parent->rd_rel->relispartition)
388
0
    result = list_concat(generate_partition_qual(parent), my_qual);
389
0
  else
390
0
    result = my_qual;
391
392
  /*
393
   * Change Vars to have partition's attnos instead of the parent's. We do
394
   * this after we concatenate the parent's quals, because we want every Var
395
   * in it to bear this relation's attnos. It's safe to assume varno = 1
396
   * here.
397
   */
398
0
  result = map_partition_varattnos(result, 1, rel, parent);
399
400
  /* Assert that we're not leaking any old data during assignments below */
401
0
  Assert(rel->rd_partcheckcxt == NULL);
402
0
  Assert(rel->rd_partcheck == NIL);
403
404
  /*
405
   * Save a copy in the relcache.  The order of these operations is fairly
406
   * critical to avoid memory leaks and ensure that we don't leave a corrupt
407
   * relcache entry if we fail partway through copyObject.
408
   *
409
   * If, as is definitely possible, the partcheck list is NIL, then we do
410
   * not need to make a context to hold it.
411
   */
412
0
  if (result != NIL)
413
0
  {
414
0
    rel->rd_partcheckcxt = AllocSetContextCreate(CacheMemoryContext,
415
0
                           "partition constraint",
416
0
                           ALLOCSET_SMALL_SIZES);
417
0
    MemoryContextCopyAndSetIdentifier(rel->rd_partcheckcxt,
418
0
                      RelationGetRelationName(rel));
419
0
    oldcxt = MemoryContextSwitchTo(rel->rd_partcheckcxt);
420
0
    rel->rd_partcheck = copyObject(result);
421
0
    MemoryContextSwitchTo(oldcxt);
422
0
  }
423
0
  else
424
0
    rel->rd_partcheck = NIL;
425
0
  rel->rd_partcheckvalid = true;
426
427
  /* Keep the parent locked until commit */
428
0
  relation_close(parent, NoLock);
429
430
  /* Return the working copy to the caller */
431
0
  return result;
432
0
}