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