1 /*-------------------------------------------------------------------------
4 * Support routines for manipulating partition information cached in
7 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
8 * Portions Copyright (c) 1994, Regents of the University of California
11 * src/backend/utils/cache/partcache.c
13 *-------------------------------------------------------------------------
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"
37 static void RelationBuildPartitionKey(Relation relation
);
38 static List
*generate_partition_qual(Relation rel
);
41 * RelationGetPartitionKey -- get partition key, if relation is partitioned
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
51 RelationGetPartitionKey(Relation rel
)
53 if (rel
->rd_rel
->relkind
!= RELKIND_PARTITIONED_TABLE
)
56 if (unlikely(rel
->rd_partkey
== NULL
))
57 RelationBuildPartitionKey(rel
);
59 return rel
->rd_partkey
;
63 * RelationBuildPartitionKey
64 * Build partition key data of relation, and attach to relcache
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
78 RelationBuildPartitionKey(Relation relation
)
80 Form_pg_partitioned_table form
;
88 ListCell
*partexprs_item
;
90 MemoryContext partkeycxt
,
94 tuple
= SearchSysCache1(PARTRELID
,
95 ObjectIdGetDatum(RelationGetRelid(relation
)));
97 if (!HeapTupleIsValid(tuple
))
98 elog(ERROR
, "cache lookup failed for partition key of relation %u",
99 RelationGetRelid(relation
));
101 partkeycxt
= AllocSetContextCreate(CurTransactionContext
,
103 ALLOCSET_SMALL_SIZES
);
104 MemoryContextCopyAndSetIdentifier(partkeycxt
,
105 RelationGetRelationName(relation
));
107 key
= (PartitionKey
) MemoryContextAllocZero(partkeycxt
,
108 sizeof(PartitionKeyData
));
110 /* Fixed-length attributes */
111 form
= (Form_pg_partitioned_table
) GETSTRUCT(tuple
);
112 key
->strategy
= form
->partstrat
;
113 key
->partnatts
= form
->partnatts
;
115 /* Validate partition strategy code */
116 if (key
->strategy
!= PARTITION_STRATEGY_LIST
&&
117 key
->strategy
!= PARTITION_STRATEGY_RANGE
&&
118 key
->strategy
!= PARTITION_STRATEGY_HASH
)
119 elog(ERROR
, "invalid partition strategy \"%c\"", key
->strategy
);
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.
126 attrs
= form
->partattrs
.values
;
128 /* But use the hard way to retrieve further variable-length attributes */
130 datum
= SysCacheGetAttrNotNull(PARTRELID
, tuple
,
131 Anum_pg_partitioned_table_partclass
);
132 opclass
= (oidvector
*) DatumGetPointer(datum
);
135 datum
= SysCacheGetAttrNotNull(PARTRELID
, tuple
,
136 Anum_pg_partitioned_table_partcollation
);
137 collation
= (oidvector
*) DatumGetPointer(datum
);
140 datum
= SysCacheGetAttr(PARTRELID
, tuple
,
141 Anum_pg_partitioned_table_partexprs
, &isnull
);
147 exprString
= TextDatumGetCString(datum
);
148 expr
= stringToNode(exprString
);
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
160 expr
= eval_const_expressions(NULL
, expr
);
163 oldcxt
= MemoryContextSwitchTo(partkeycxt
);
164 key
->partexprs
= (List
*) copyObject(expr
);
165 MemoryContextSwitchTo(oldcxt
);
168 /* Allocate assorted arrays in the partkeycxt, which we'll fill below */
169 oldcxt
= MemoryContextSwitchTo(partkeycxt
);
170 key
->partattrs
= (AttrNumber
*) palloc0(key
->partnatts
* sizeof(AttrNumber
));
171 key
->partopfamily
= (Oid
*) palloc0(key
->partnatts
* sizeof(Oid
));
172 key
->partopcintype
= (Oid
*) palloc0(key
->partnatts
* sizeof(Oid
));
173 key
->partsupfunc
= (FmgrInfo
*) palloc0(key
->partnatts
* sizeof(FmgrInfo
));
175 key
->partcollation
= (Oid
*) palloc0(key
->partnatts
* sizeof(Oid
));
176 key
->parttypid
= (Oid
*) palloc0(key
->partnatts
* sizeof(Oid
));
177 key
->parttypmod
= (int32
*) palloc0(key
->partnatts
* sizeof(int32
));
178 key
->parttyplen
= (int16
*) palloc0(key
->partnatts
* sizeof(int16
));
179 key
->parttypbyval
= (bool *) palloc0(key
->partnatts
* sizeof(bool));
180 key
->parttypalign
= (char *) palloc0(key
->partnatts
* sizeof(char));
181 key
->parttypcoll
= (Oid
*) palloc0(key
->partnatts
* sizeof(Oid
));
182 MemoryContextSwitchTo(oldcxt
);
184 /* determine support function number to search for */
185 procnum
= (key
->strategy
== PARTITION_STRATEGY_HASH
) ?
186 HASHEXTENDED_PROC
: BTORDER_PROC
;
188 /* Copy partattrs and fill other per-attribute info */
189 memcpy(key
->partattrs
, attrs
, key
->partnatts
* sizeof(int16
));
190 partexprs_item
= list_head(key
->partexprs
);
191 for (i
= 0; i
< key
->partnatts
; i
++)
193 AttrNumber attno
= key
->partattrs
[i
];
194 HeapTuple opclasstup
;
195 Form_pg_opclass opclassform
;
198 /* Collect opfamily information */
199 opclasstup
= SearchSysCache1(CLAOID
,
200 ObjectIdGetDatum(opclass
->values
[i
]));
201 if (!HeapTupleIsValid(opclasstup
))
202 elog(ERROR
, "cache lookup failed for opclass %u", opclass
->values
[i
]);
204 opclassform
= (Form_pg_opclass
) GETSTRUCT(opclasstup
);
205 key
->partopfamily
[i
] = opclassform
->opcfamily
;
206 key
->partopcintype
[i
] = opclassform
->opcintype
;
208 /* Get a support function for the specified opfamily and datatypes */
209 funcid
= get_opfamily_proc(opclassform
->opcfamily
,
210 opclassform
->opcintype
,
211 opclassform
->opcintype
,
213 if (!OidIsValid(funcid
))
215 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION
),
216 errmsg("operator class \"%s\" of access method %s is missing support function %d for type %s",
217 NameStr(opclassform
->opcname
),
218 (key
->strategy
== PARTITION_STRATEGY_HASH
) ?
221 format_type_be(opclassform
->opcintype
))));
223 fmgr_info_cxt(funcid
, &key
->partsupfunc
[i
], partkeycxt
);
226 key
->partcollation
[i
] = collation
->values
[i
];
228 /* Collect type information */
231 Form_pg_attribute att
= TupleDescAttr(relation
->rd_att
, attno
- 1);
233 key
->parttypid
[i
] = att
->atttypid
;
234 key
->parttypmod
[i
] = att
->atttypmod
;
235 key
->parttypcoll
[i
] = att
->attcollation
;
239 if (partexprs_item
== NULL
)
240 elog(ERROR
, "wrong number of partition key expressions");
242 key
->parttypid
[i
] = exprType(lfirst(partexprs_item
));
243 key
->parttypmod
[i
] = exprTypmod(lfirst(partexprs_item
));
244 key
->parttypcoll
[i
] = exprCollation(lfirst(partexprs_item
));
246 partexprs_item
= lnext(key
->partexprs
, partexprs_item
);
248 get_typlenbyvalalign(key
->parttypid
[i
],
250 &key
->parttypbyval
[i
],
251 &key
->parttypalign
[i
]);
253 ReleaseSysCache(opclasstup
);
256 ReleaseSysCache(tuple
);
258 /* Assert that we're not leaking any old data during assignments below */
259 Assert(relation
->rd_partkeycxt
== NULL
);
260 Assert(relation
->rd_partkey
== NULL
);
263 * Success --- reparent our context and make the relcache point to the
264 * newly constructed key
266 MemoryContextSetParent(partkeycxt
, CacheMemoryContext
);
267 relation
->rd_partkeycxt
= partkeycxt
;
268 relation
->rd_partkey
= key
;
272 * RelationGetPartitionQual
274 * Returns a list of partition quals
277 RelationGetPartitionQual(Relation rel
)
280 if (!rel
->rd_rel
->relispartition
)
283 return generate_partition_qual(rel
);
287 * get_partition_qual_relid
289 * Returns an expression tree describing the passed-in relation's partition
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.
299 get_partition_qual_relid(Oid relid
)
303 /* Do the work only if this relation exists and is a partition. */
304 if (get_rel_relispartition(relid
))
306 Relation rel
= relation_open(relid
, AccessShareLock
);
309 and_args
= generate_partition_qual(rel
);
311 /* Convert implicit-AND list format to boolean expression */
314 else if (list_length(and_args
) > 1)
315 result
= makeBoolExpr(AND_EXPR
, and_args
, -1);
317 result
= linitial(and_args
);
319 /* Keep the lock, to allow safe deparsing against the rel by caller. */
320 relation_close(rel
, NoLock
);
327 * generate_partition_qual
329 * Generate partition predicate from rel's partition bound expression. The
330 * function returns a NIL list if there is no predicate.
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.
337 generate_partition_qual(Relation rel
)
340 MemoryContext oldcxt
;
348 /* Guard against stack overflow due to overly deep partition tree */
351 /* If we already cached the result, just return a copy */
352 if (rel
->rd_partcheckvalid
)
353 return copyObject(rel
->rd_partcheck
);
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.
361 parentrelid
= get_partition_parent(RelationGetRelid(rel
), true);
362 parent
= relation_open(parentrelid
, AccessShareLock
);
364 /* Get pg_class.relpartbound */
365 tuple
= SearchSysCache1(RELOID
,
366 ObjectIdGetDatum(RelationGetRelid(rel
)));
367 if (!HeapTupleIsValid(tuple
))
368 elog(ERROR
, "cache lookup failed for relation %u",
369 RelationGetRelid(rel
));
371 boundDatum
= SysCacheGetAttr(RELOID
, tuple
,
372 Anum_pg_class_relpartbound
,
376 PartitionBoundSpec
*bound
;
378 bound
= castNode(PartitionBoundSpec
,
379 stringToNode(TextDatumGetCString(boundDatum
)));
381 my_qual
= get_qual_from_partbound(parent
, bound
);
384 ReleaseSysCache(tuple
);
386 /* Add the parent's quals to the list (if any) */
387 if (parent
->rd_rel
->relispartition
)
388 result
= list_concat(generate_partition_qual(parent
), my_qual
);
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
398 result
= map_partition_varattnos(result
, 1, rel
, parent
);
400 /* Assert that we're not leaking any old data during assignments below */
401 Assert(rel
->rd_partcheckcxt
== NULL
);
402 Assert(rel
->rd_partcheck
== NIL
);
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.
409 * If, as is definitely possible, the partcheck list is NIL, then we do
410 * not need to make a context to hold it.
414 rel
->rd_partcheckcxt
= AllocSetContextCreate(CacheMemoryContext
,
415 "partition constraint",
416 ALLOCSET_SMALL_SIZES
);
417 MemoryContextCopyAndSetIdentifier(rel
->rd_partcheckcxt
,
418 RelationGetRelationName(rel
));
419 oldcxt
= MemoryContextSwitchTo(rel
->rd_partcheckcxt
);
420 rel
->rd_partcheck
= copyObject(result
);
421 MemoryContextSwitchTo(oldcxt
);
424 rel
->rd_partcheck
= NIL
;
425 rel
->rd_partcheckvalid
= true;
427 /* Keep the parent locked until commit */
428 relation_close(parent
, NoLock
);
430 /* Return the working copy to the caller */