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