1 /*-------------------------------------------------------------------------
2 *
3 * partition.c
4 * Partitioning related data structures and functions.
5 *
6 * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
8 *
9 *
10 * IDENTIFICATION
11 * src/backend/catalog/partition.c
12 *
13 *-------------------------------------------------------------------------
14 */
15 #include "postgres.h"
16
17 #include "access/genam.h"
18 #include "access/htup_details.h"
19 #include "access/table.h"
20 #include "access/tupconvert.h"
21 #include "access/sysattr.h"
22 #include "catalog/indexing.h"
23 #include "catalog/partition.h"
24 #include "catalog/pg_inherits.h"
25 #include "catalog/pg_partitioned_table.h"
26 #include "nodes/makefuncs.h"
27 #include "optimizer/optimizer.h"
28 #include "partitioning/partbounds.h"
29 #include "rewrite/rewriteManip.h"
30 #include "utils/fmgroids.h"
31 #include "utils/partcache.h"
32 #include "utils/rel.h"
33 #include "utils/syscache.h"
34
35
36 static Oid get_partition_parent_worker(Relation inhRel, Oid relid);
37 static void get_partition_ancestors_worker(Relation inhRel, Oid relid,
38 List **ancestors);
39
40 /*
41 * get_partition_parent
42 * Obtain direct parent of given relation
43 *
44 * Returns inheritance parent of a partition by scanning pg_inherits
45 *
46 * Note: Because this function assumes that the relation whose OID is passed
47 * as an argument will have precisely one parent, it should only be called
48 * when it is known that the relation is a partition.
49 */
50 Oid
get_partition_parent(Oid relid)51 get_partition_parent(Oid relid)
52 {
53 Relation catalogRelation;
54 Oid result;
55
56 catalogRelation = table_open(InheritsRelationId, AccessShareLock);
57
58 result = get_partition_parent_worker(catalogRelation, relid);
59
60 if (!OidIsValid(result))
61 elog(ERROR, "could not find tuple for parent of relation %u", relid);
62
63 table_close(catalogRelation, AccessShareLock);
64
65 return result;
66 }
67
68 /*
69 * get_partition_parent_worker
70 * Scan the pg_inherits relation to return the OID of the parent of the
71 * given relation
72 */
73 static Oid
get_partition_parent_worker(Relation inhRel,Oid relid)74 get_partition_parent_worker(Relation inhRel, Oid relid)
75 {
76 SysScanDesc scan;
77 ScanKeyData key[2];
78 Oid result = InvalidOid;
79 HeapTuple tuple;
80
81 ScanKeyInit(&key[0],
82 Anum_pg_inherits_inhrelid,
83 BTEqualStrategyNumber, F_OIDEQ,
84 ObjectIdGetDatum(relid));
85 ScanKeyInit(&key[1],
86 Anum_pg_inherits_inhseqno,
87 BTEqualStrategyNumber, F_INT4EQ,
88 Int32GetDatum(1));
89
90 scan = systable_beginscan(inhRel, InheritsRelidSeqnoIndexId, true,
91 NULL, 2, key);
92 tuple = systable_getnext(scan);
93 if (HeapTupleIsValid(tuple))
94 {
95 Form_pg_inherits form = (Form_pg_inherits) GETSTRUCT(tuple);
96
97 result = form->inhparent;
98 }
99
100 systable_endscan(scan);
101
102 return result;
103 }
104
105 /*
106 * get_partition_ancestors
107 * Obtain ancestors of given relation
108 *
109 * Returns a list of ancestors of the given relation.
110 *
111 * Note: Because this function assumes that the relation whose OID is passed
112 * as an argument and each ancestor will have precisely one parent, it should
113 * only be called when it is known that the relation is a partition.
114 */
115 List *
get_partition_ancestors(Oid relid)116 get_partition_ancestors(Oid relid)
117 {
118 List *result = NIL;
119 Relation inhRel;
120
121 inhRel = table_open(InheritsRelationId, AccessShareLock);
122
123 get_partition_ancestors_worker(inhRel, relid, &result);
124
125 table_close(inhRel, AccessShareLock);
126
127 return result;
128 }
129
130 /*
131 * get_partition_ancestors_worker
132 * recursive worker for get_partition_ancestors
133 */
134 static void
get_partition_ancestors_worker(Relation inhRel,Oid relid,List ** ancestors)135 get_partition_ancestors_worker(Relation inhRel, Oid relid, List **ancestors)
136 {
137 Oid parentOid;
138
139 /* Recursion ends at the topmost level, ie., when there's no parent */
140 parentOid = get_partition_parent_worker(inhRel, relid);
141 if (parentOid == InvalidOid)
142 return;
143
144 *ancestors = lappend_oid(*ancestors, parentOid);
145 get_partition_ancestors_worker(inhRel, parentOid, ancestors);
146 }
147
148 /*
149 * index_get_partition
150 * Return the OID of index of the given partition that is a child
151 * of the given index, or InvalidOid if there isn't one.
152 */
153 Oid
index_get_partition(Relation partition,Oid indexId)154 index_get_partition(Relation partition, Oid indexId)
155 {
156 List *idxlist = RelationGetIndexList(partition);
157 ListCell *l;
158
159 foreach(l, idxlist)
160 {
161 Oid partIdx = lfirst_oid(l);
162 HeapTuple tup;
163 Form_pg_class classForm;
164 bool ispartition;
165
166 tup = SearchSysCache1(RELOID, ObjectIdGetDatum(partIdx));
167 if (!HeapTupleIsValid(tup))
168 elog(ERROR, "cache lookup failed for relation %u", partIdx);
169 classForm = (Form_pg_class) GETSTRUCT(tup);
170 ispartition = classForm->relispartition;
171 ReleaseSysCache(tup);
172 if (!ispartition)
173 continue;
174 if (get_partition_parent(partIdx) == indexId)
175 {
176 list_free(idxlist);
177 return partIdx;
178 }
179 }
180
181 list_free(idxlist);
182 return InvalidOid;
183 }
184
185 /*
186 * map_partition_varattnos - maps varattno of any Vars in expr from the
187 * attno's of 'from_rel' to the attno's of 'to_rel' partition, each of which
188 * may be either a leaf partition or a partitioned table, but both of which
189 * must be from the same partitioning hierarchy.
190 *
191 * Even though all of the same column names must be present in all relations
192 * in the hierarchy, and they must also have the same types, the attnos may
193 * be different.
194 *
195 * If found_whole_row is not NULL, *found_whole_row returns whether a
196 * whole-row variable was found in the input expression.
197 *
198 * Note: this will work on any node tree, so really the argument and result
199 * should be declared "Node *". But a substantial majority of the callers
200 * are working on Lists, so it's less messy to do the casts internally.
201 */
202 List *
map_partition_varattnos(List * expr,int fromrel_varno,Relation to_rel,Relation from_rel,bool * found_whole_row)203 map_partition_varattnos(List *expr, int fromrel_varno,
204 Relation to_rel, Relation from_rel,
205 bool *found_whole_row)
206 {
207 bool my_found_whole_row = false;
208
209 if (expr != NIL)
210 {
211 AttrNumber *part_attnos;
212
213 part_attnos = convert_tuples_by_name_map(RelationGetDescr(to_rel),
214 RelationGetDescr(from_rel),
215 gettext_noop("could not convert row type"));
216 expr = (List *) map_variable_attnos((Node *) expr,
217 fromrel_varno, 0,
218 part_attnos,
219 RelationGetDescr(from_rel)->natts,
220 RelationGetForm(to_rel)->reltype,
221 &my_found_whole_row);
222 }
223
224 if (found_whole_row)
225 *found_whole_row = my_found_whole_row;
226
227 return expr;
228 }
229
230 /*
231 * Checks if any of the 'attnums' is a partition key attribute for rel
232 *
233 * Sets *used_in_expr if any of the 'attnums' is found to be referenced in some
234 * partition key expression. It's possible for a column to be both used
235 * directly and as part of an expression; if that happens, *used_in_expr may
236 * end up as either true or false. That's OK for current uses of this
237 * function, because *used_in_expr is only used to tailor the error message
238 * text.
239 */
240 bool
has_partition_attrs(Relation rel,Bitmapset * attnums,bool * used_in_expr)241 has_partition_attrs(Relation rel, Bitmapset *attnums, bool *used_in_expr)
242 {
243 PartitionKey key;
244 int partnatts;
245 List *partexprs;
246 ListCell *partexprs_item;
247 int i;
248
249 if (attnums == NULL || rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
250 return false;
251
252 key = RelationGetPartitionKey(rel);
253 partnatts = get_partition_natts(key);
254 partexprs = get_partition_exprs(key);
255
256 partexprs_item = list_head(partexprs);
257 for (i = 0; i < partnatts; i++)
258 {
259 AttrNumber partattno = get_partition_col_attnum(key, i);
260
261 if (partattno != 0)
262 {
263 if (bms_is_member(partattno - FirstLowInvalidHeapAttributeNumber,
264 attnums))
265 {
266 if (used_in_expr)
267 *used_in_expr = false;
268 return true;
269 }
270 }
271 else
272 {
273 /* Arbitrary expression */
274 Node *expr = (Node *) lfirst(partexprs_item);
275 Bitmapset *expr_attrs = NULL;
276
277 /* Find all attributes referenced */
278 pull_varattnos(expr, 1, &expr_attrs);
279 partexprs_item = lnext(partexprs_item);
280
281 if (bms_overlap(attnums, expr_attrs))
282 {
283 if (used_in_expr)
284 *used_in_expr = true;
285 return true;
286 }
287 }
288 }
289
290 return false;
291 }
292
293 /*
294 * get_default_partition_oid
295 *
296 * Given a relation OID, return the OID of the default partition, if one
297 * exists. Use get_default_oid_from_partdesc where possible, for
298 * efficiency.
299 */
300 Oid
get_default_partition_oid(Oid parentId)301 get_default_partition_oid(Oid parentId)
302 {
303 HeapTuple tuple;
304 Oid defaultPartId = InvalidOid;
305
306 tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(parentId));
307
308 if (HeapTupleIsValid(tuple))
309 {
310 Form_pg_partitioned_table part_table_form;
311
312 part_table_form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
313 defaultPartId = part_table_form->partdefid;
314 ReleaseSysCache(tuple);
315 }
316
317 return defaultPartId;
318 }
319
320 /*
321 * update_default_partition_oid
322 *
323 * Update pg_partitioned_table.partdefid with a new default partition OID.
324 */
325 void
update_default_partition_oid(Oid parentId,Oid defaultPartId)326 update_default_partition_oid(Oid parentId, Oid defaultPartId)
327 {
328 HeapTuple tuple;
329 Relation pg_partitioned_table;
330 Form_pg_partitioned_table part_table_form;
331
332 pg_partitioned_table = table_open(PartitionedRelationId, RowExclusiveLock);
333
334 tuple = SearchSysCacheCopy1(PARTRELID, ObjectIdGetDatum(parentId));
335
336 if (!HeapTupleIsValid(tuple))
337 elog(ERROR, "cache lookup failed for partition key of relation %u",
338 parentId);
339
340 part_table_form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
341 part_table_form->partdefid = defaultPartId;
342 CatalogTupleUpdate(pg_partitioned_table, &tuple->t_self, tuple);
343
344 heap_freetuple(tuple);
345 table_close(pg_partitioned_table, RowExclusiveLock);
346 }
347
348 /*
349 * get_proposed_default_constraint
350 *
351 * This function returns the negation of new_part_constraints, which
352 * would be an integral part of the default partition constraints after
353 * addition of the partition to which the new_part_constraints belongs.
354 */
355 List *
get_proposed_default_constraint(List * new_part_constraints)356 get_proposed_default_constraint(List *new_part_constraints)
357 {
358 Expr *defPartConstraint;
359
360 defPartConstraint = make_ands_explicit(new_part_constraints);
361
362 /*
363 * Derive the partition constraints of default partition by negating the
364 * given partition constraints. The partition constraint never evaluates
365 * to NULL, so negating it like this is safe.
366 */
367 defPartConstraint = makeBoolExpr(NOT_EXPR,
368 list_make1(defPartConstraint),
369 -1);
370
371 /* Simplify, to put the negated expression into canonical form */
372 defPartConstraint =
373 (Expr *) eval_const_expressions(NULL,
374 (Node *) defPartConstraint);
375 defPartConstraint = canonicalize_qual(defPartConstraint, true);
376
377 return make_ands_implicit(defPartConstraint);
378 }
379