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