Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * execPartition.c
4 : : * Support routines for partitioning.
5 : : *
6 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : * IDENTIFICATION
10 : : * src/backend/executor/execPartition.c
11 : : *
12 : : *-------------------------------------------------------------------------
13 : : */
14 : : #include "postgres.h"
15 : :
16 : : #include "access/table.h"
17 : : #include "access/tableam.h"
18 : : #include "access/tupconvert.h"
19 : : #include "catalog/index.h"
20 : : #include "catalog/partition.h"
21 : : #include "executor/execPartition.h"
22 : : #include "executor/executor.h"
23 : : #include "executor/nodeModifyTable.h"
24 : : #include "foreign/fdwapi.h"
25 : : #include "mb/pg_wchar.h"
26 : : #include "miscadmin.h"
27 : : #include "partitioning/partbounds.h"
28 : : #include "partitioning/partdesc.h"
29 : : #include "partitioning/partprune.h"
30 : : #include "rewrite/rewriteManip.h"
31 : : #include "utils/acl.h"
32 : : #include "utils/injection_point.h"
33 : : #include "utils/lsyscache.h"
34 : : #include "utils/partcache.h"
35 : : #include "utils/rls.h"
36 : : #include "utils/ruleutils.h"
37 : :
38 : :
39 : : /*-----------------------
40 : : * PartitionTupleRouting - Encapsulates all information required to
41 : : * route a tuple inserted into a partitioned table to one of its leaf
42 : : * partitions.
43 : : *
44 : : * partition_root
45 : : * The partitioned table that's the target of the command.
46 : : *
47 : : * partition_dispatch_info
48 : : * Array of 'max_dispatch' elements containing a pointer to a
49 : : * PartitionDispatch object for every partitioned table touched by tuple
50 : : * routing. The entry for the target partitioned table is *always*
51 : : * present in the 0th element of this array. See comment for
52 : : * PartitionDispatchData->indexes for details on how this array is
53 : : * indexed.
54 : : *
55 : : * nonleaf_partitions
56 : : * Array of 'max_dispatch' elements containing pointers to fake
57 : : * ResultRelInfo objects for nonleaf partitions, useful for checking
58 : : * the partition constraint.
59 : : *
60 : : * num_dispatch
61 : : * The current number of items stored in the 'partition_dispatch_info'
62 : : * array. Also serves as the index of the next free array element for
63 : : * new PartitionDispatch objects that need to be stored.
64 : : *
65 : : * max_dispatch
66 : : * The current allocated size of the 'partition_dispatch_info' array.
67 : : *
68 : : * partitions
69 : : * Array of 'max_partitions' elements containing a pointer to a
70 : : * ResultRelInfo for every leaf partition touched by tuple routing.
71 : : * Some of these are pointers to ResultRelInfos which are borrowed out of
72 : : * the owning ModifyTableState node. The remainder have been built
73 : : * especially for tuple routing. See comment for
74 : : * PartitionDispatchData->indexes for details on how this array is
75 : : * indexed.
76 : : *
77 : : * is_borrowed_rel
78 : : * Array of 'max_partitions' booleans recording whether a given entry
79 : : * in 'partitions' is a ResultRelInfo pointer borrowed from the owning
80 : : * ModifyTableState node, rather than being built here.
81 : : *
82 : : * num_partitions
83 : : * The current number of items stored in the 'partitions' array. Also
84 : : * serves as the index of the next free array element for new
85 : : * ResultRelInfo objects that need to be stored.
86 : : *
87 : : * max_partitions
88 : : * The current allocated size of the 'partitions' array.
89 : : *
90 : : * memcxt
91 : : * Memory context used to allocate subsidiary structs.
92 : : *-----------------------
93 : : */
94 : : struct PartitionTupleRouting
95 : : {
96 : : Relation partition_root;
97 : : PartitionDispatch *partition_dispatch_info;
98 : : ResultRelInfo **nonleaf_partitions;
99 : : int num_dispatch;
100 : : int max_dispatch;
101 : : ResultRelInfo **partitions;
102 : : bool *is_borrowed_rel;
103 : : int num_partitions;
104 : : int max_partitions;
105 : : MemoryContext memcxt;
106 : : };
107 : :
108 : : /*-----------------------
109 : : * PartitionDispatch - information about one partitioned table in a partition
110 : : * hierarchy required to route a tuple to any of its partitions. A
111 : : * PartitionDispatch is always encapsulated inside a PartitionTupleRouting
112 : : * struct and stored inside its 'partition_dispatch_info' array.
113 : : *
114 : : * reldesc
115 : : * Relation descriptor of the table
116 : : *
117 : : * key
118 : : * Partition key information of the table
119 : : *
120 : : * keystate
121 : : * Execution state required for expressions in the partition key
122 : : *
123 : : * partdesc
124 : : * Partition descriptor of the table
125 : : *
126 : : * tupslot
127 : : * A standalone TupleTableSlot initialized with this table's tuple
128 : : * descriptor, or NULL if no tuple conversion between the parent is
129 : : * required.
130 : : *
131 : : * tupmap
132 : : * TupleConversionMap to convert from the parent's rowtype to this table's
133 : : * rowtype (when extracting the partition key of a tuple just before
134 : : * routing it through this table). A NULL value is stored if no tuple
135 : : * conversion is required.
136 : : *
137 : : * indexes
138 : : * Array of partdesc->nparts elements. For leaf partitions the index
139 : : * corresponds to the partition's ResultRelInfo in the encapsulating
140 : : * PartitionTupleRouting's partitions array. For partitioned partitions,
141 : : * the index corresponds to the PartitionDispatch for it in its
142 : : * partition_dispatch_info array. -1 indicates we've not yet allocated
143 : : * anything in PartitionTupleRouting for the partition.
144 : : *-----------------------
145 : : */
146 : : typedef struct PartitionDispatchData
147 : : {
148 : : Relation reldesc;
149 : : PartitionKey key;
150 : : List *keystate; /* list of ExprState */
151 : : PartitionDesc partdesc;
152 : : TupleTableSlot *tupslot;
153 : : AttrMap *tupmap;
154 : : int indexes[FLEXIBLE_ARRAY_MEMBER];
155 : : } PartitionDispatchData;
156 : :
157 : :
158 : : static ResultRelInfo *ExecInitPartitionInfo(ModifyTableState *mtstate,
159 : : EState *estate, PartitionTupleRouting *proute,
160 : : PartitionDispatch dispatch,
161 : : ResultRelInfo *rootResultRelInfo,
162 : : int partidx);
163 : : static void ExecInitRoutingInfo(ModifyTableState *mtstate,
164 : : EState *estate,
165 : : PartitionTupleRouting *proute,
166 : : PartitionDispatch dispatch,
167 : : ResultRelInfo *partRelInfo,
168 : : int partidx,
169 : : bool is_borrowed_rel);
170 : : static PartitionDispatch ExecInitPartitionDispatchInfo(EState *estate,
171 : : PartitionTupleRouting *proute,
172 : : Oid partoid, PartitionDispatch parent_pd,
173 : : int partidx, ResultRelInfo *rootResultRelInfo);
174 : : static void FormPartitionKeyDatum(PartitionDispatch pd,
175 : : TupleTableSlot *slot,
176 : : EState *estate,
177 : : Datum *values,
178 : : bool *isnull);
179 : : static int get_partition_for_tuple(PartitionDispatch pd, const Datum *values,
180 : : const bool *isnull);
181 : : static char *ExecBuildSlotPartitionKeyDescription(Relation rel,
182 : : const Datum *values,
183 : : const bool *isnull,
184 : : int maxfieldlen);
185 : : static List *adjust_partition_colnos(List *colnos, ResultRelInfo *leaf_part_rri);
186 : : static List *adjust_partition_colnos_using_map(List *colnos, AttrMap *attrMap);
187 : : static PartitionPruneState *CreatePartitionPruneState(EState *estate,
188 : : PartitionPruneInfo *pruneinfo,
189 : : Bitmapset **all_leafpart_rtis);
190 : : static void InitPartitionPruneContext(PartitionPruneContext *context,
191 : : List *pruning_steps,
192 : : PartitionDesc partdesc,
193 : : PartitionKey partkey,
194 : : PlanState *planstate,
195 : : ExprContext *econtext);
196 : : static void InitExecPartitionPruneContexts(PartitionPruneState *prunestate,
197 : : PlanState *parent_plan,
198 : : Bitmapset *initially_valid_subplans,
199 : : int n_total_subplans);
200 : : static void find_matching_subplans_recurse(PartitionPruningData *prunedata,
201 : : PartitionedRelPruningData *pprune,
202 : : bool initial_prune,
203 : : Bitmapset **validsubplans,
204 : : Bitmapset **validsubplan_rtis);
205 : :
206 : :
207 : : /*
208 : : * ExecSetupPartitionTupleRouting - sets up information needed during
209 : : * tuple routing for partitioned tables, encapsulates it in
210 : : * PartitionTupleRouting, and returns it.
211 : : *
212 : : * Callers must use the returned PartitionTupleRouting during calls to
213 : : * ExecFindPartition(). The actual ResultRelInfo for a partition is only
214 : : * allocated when the partition is found for the first time.
215 : : *
216 : : * The current memory context is used to allocate this struct and all
217 : : * subsidiary structs that will be allocated from it later on. Typically
218 : : * it should be estate->es_query_cxt.
219 : : */
220 : : PartitionTupleRouting *
221 : 3462 : ExecSetupPartitionTupleRouting(EState *estate, Relation rel)
222 : : {
223 : : PartitionTupleRouting *proute;
224 : :
225 : : /*
226 : : * Here we attempt to expend as little effort as possible in setting up
227 : : * the PartitionTupleRouting. Each partition's ResultRelInfo is built on
228 : : * demand, only when we actually need to route a tuple to that partition.
229 : : * The reason for this is that a common case is for INSERT to insert a
230 : : * single tuple into a partitioned table and this must be fast.
231 : : */
232 : 3462 : proute = palloc0_object(PartitionTupleRouting);
233 : 3462 : proute->partition_root = rel;
234 : 3462 : proute->memcxt = CurrentMemoryContext;
235 : : /* Rest of members initialized by zeroing */
236 : :
237 : : /*
238 : : * Initialize this table's PartitionDispatch object. Here we pass in the
239 : : * parent as NULL as we don't need to care about any parent of the target
240 : : * partitioned table.
241 : : */
242 : 3462 : ExecInitPartitionDispatchInfo(estate, proute, RelationGetRelid(rel),
243 : : NULL, 0, NULL);
244 : :
245 : 3462 : return proute;
246 : : }
247 : :
248 : : /*
249 : : * ExecFindPartition -- Return the ResultRelInfo for the leaf partition that
250 : : * the tuple contained in *slot should belong to.
251 : : *
252 : : * If the partition's ResultRelInfo does not yet exist in 'proute' then we set
253 : : * one up or reuse one from mtstate's resultRelInfo array. When reusing a
254 : : * ResultRelInfo from the mtstate we verify that the relation is a valid
255 : : * target for INSERTs and initialize tuple routing information.
256 : : *
257 : : * rootResultRelInfo is the relation named in the query.
258 : : *
259 : : * estate must be non-NULL; we'll need it to compute any expressions in the
260 : : * partition keys. Also, its per-tuple contexts are used as evaluation
261 : : * scratch space.
262 : : *
263 : : * If no leaf partition is found, this routine errors out with the appropriate
264 : : * error message. An error may also be raised if the found target partition
265 : : * is not a valid target for an INSERT.
266 : : */
267 : : ResultRelInfo *
268 : 628392 : ExecFindPartition(ModifyTableState *mtstate,
269 : : ResultRelInfo *rootResultRelInfo,
270 : : PartitionTupleRouting *proute,
271 : : TupleTableSlot *slot, EState *estate)
272 : : {
273 : 628392 : PartitionDispatch *pd = proute->partition_dispatch_info;
274 : : Datum values[PARTITION_MAX_KEYS];
275 : : bool isnull[PARTITION_MAX_KEYS];
276 : : Relation rel;
277 : : PartitionDispatch dispatch;
278 : : PartitionDesc partdesc;
279 [ + + ]: 628392 : ExprContext *ecxt = GetPerTupleExprContext(estate);
280 : 628392 : TupleTableSlot *ecxt_scantuple_saved = ecxt->ecxt_scantuple;
281 : 628392 : TupleTableSlot *rootslot = slot;
282 : 628392 : TupleTableSlot *myslot = NULL;
283 : : MemoryContext oldcxt;
284 : 628392 : ResultRelInfo *rri = NULL;
285 : :
286 : : /* use per-tuple context here to avoid leaking memory */
287 [ + - ]: 628392 : oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
288 : :
289 : : /*
290 : : * First check the root table's partition constraint, if any. No point in
291 : : * routing the tuple if it doesn't belong in the root table itself.
292 : : */
293 [ + + ]: 628392 : if (rootResultRelInfo->ri_RelationDesc->rd_rel->relispartition)
294 : 2996 : ExecPartitionCheck(rootResultRelInfo, slot, estate, true);
295 : :
296 : : /* start with the root partitioned table */
297 : 628371 : dispatch = pd[0];
298 [ + + ]: 1334291 : while (dispatch != NULL)
299 : : {
300 : 706044 : int partidx = -1;
301 : : bool is_leaf;
302 : :
303 [ - + ]: 706044 : CHECK_FOR_INTERRUPTS();
304 : :
305 : 706044 : rel = dispatch->reldesc;
306 : 706044 : partdesc = dispatch->partdesc;
307 : :
308 : : /*
309 : : * Extract partition key from tuple. Expression evaluation machinery
310 : : * that FormPartitionKeyDatum() invokes expects ecxt_scantuple to
311 : : * point to the correct tuple slot. The slot might have changed from
312 : : * what was used for the parent table if the table of the current
313 : : * partitioning level has different tuple descriptor from the parent.
314 : : * So update ecxt_scantuple accordingly.
315 : : */
316 : 706044 : ecxt->ecxt_scantuple = slot;
317 : 706044 : FormPartitionKeyDatum(dispatch, slot, estate, values, isnull);
318 : :
319 : : /*
320 : : * If this partitioned table has no partitions or no partition for
321 : : * these values, error out.
322 : : */
323 [ + + + + ]: 1412052 : if (partdesc->nparts == 0 ||
324 : 706016 : (partidx = get_partition_for_tuple(dispatch, values, isnull)) < 0)
325 : : {
326 : : char *val_desc;
327 : :
328 : 102 : val_desc = ExecBuildSlotPartitionKeyDescription(rel,
329 : : values, isnull, 64);
330 : : Assert(OidIsValid(RelationGetRelid(rel)));
331 [ + - + + ]: 102 : ereport(ERROR,
332 : : (errcode(ERRCODE_CHECK_VIOLATION),
333 : : errmsg("no partition of relation \"%s\" found for row",
334 : : RelationGetRelationName(rel)),
335 : : val_desc ?
336 : : errdetail("Partition key of the failing row contains %s.",
337 : : val_desc) : 0,
338 : : errtable(rel)));
339 : : }
340 : :
341 : 705934 : is_leaf = partdesc->is_leaf[partidx];
342 [ + + ]: 705934 : if (is_leaf)
343 : : {
344 : : /*
345 : : * We've reached the leaf -- hurray, we're done. Look to see if
346 : : * we've already got a ResultRelInfo for this partition.
347 : : */
348 [ + + ]: 628260 : if (likely(dispatch->indexes[partidx] >= 0))
349 : : {
350 : : /* ResultRelInfo already built */
351 : : Assert(dispatch->indexes[partidx] < proute->num_partitions);
352 : 623474 : rri = proute->partitions[dispatch->indexes[partidx]];
353 : : }
354 : : else
355 : : {
356 : : /*
357 : : * If the partition is known in the owning ModifyTableState
358 : : * node, we can re-use that ResultRelInfo instead of creating
359 : : * a new one with ExecInitPartitionInfo().
360 : : */
361 : 4786 : rri = ExecLookupResultRelByOid(mtstate,
362 : 4786 : partdesc->oids[partidx],
363 : : true, false);
364 [ + + ]: 4786 : if (rri)
365 : : {
366 : 422 : ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
367 : :
368 : : /* Verify this ResultRelInfo allows INSERTs */
369 [ + - ]: 422 : CheckValidResultRel(rri, CMD_INSERT,
370 : : node ? node->onConflictAction : ONCONFLICT_NONE,
371 : : NIL, node);
372 : :
373 : : /*
374 : : * Initialize information needed to insert this and
375 : : * subsequent tuples routed to this partition.
376 : : */
377 : 422 : ExecInitRoutingInfo(mtstate, estate, proute, dispatch,
378 : : rri, partidx, true);
379 : : }
380 : : else
381 : : {
382 : : /* We need to create a new one. */
383 : 4364 : rri = ExecInitPartitionInfo(mtstate, estate, proute,
384 : : dispatch,
385 : : rootResultRelInfo, partidx);
386 : : }
387 : : }
388 : : Assert(rri != NULL);
389 : :
390 : : /* Signal to terminate the loop */
391 : 628247 : dispatch = NULL;
392 : : }
393 : : else
394 : : {
395 : : /*
396 : : * Partition is a sub-partitioned table; get the PartitionDispatch
397 : : */
398 [ + + ]: 77674 : if (likely(dispatch->indexes[partidx] >= 0))
399 : : {
400 : : /* Already built. */
401 : : Assert(dispatch->indexes[partidx] < proute->num_dispatch);
402 : :
403 : 76876 : rri = proute->nonleaf_partitions[dispatch->indexes[partidx]];
404 : :
405 : : /*
406 : : * Move down to the next partition level and search again
407 : : * until we find a leaf partition that matches this tuple
408 : : */
409 : 76876 : dispatch = pd[dispatch->indexes[partidx]];
410 : : }
411 : : else
412 : : {
413 : : /* Not yet built. Do that now. */
414 : : PartitionDispatch subdispatch;
415 : :
416 : : /*
417 : : * Create the new PartitionDispatch. We pass the current one
418 : : * in as the parent PartitionDispatch
419 : : */
420 : 798 : subdispatch = ExecInitPartitionDispatchInfo(estate,
421 : : proute,
422 : 798 : partdesc->oids[partidx],
423 : : dispatch, partidx,
424 : : mtstate->rootResultRelInfo);
425 : : Assert(dispatch->indexes[partidx] >= 0 &&
426 : : dispatch->indexes[partidx] < proute->num_dispatch);
427 : :
428 : 798 : rri = proute->nonleaf_partitions[dispatch->indexes[partidx]];
429 : 798 : dispatch = subdispatch;
430 : : }
431 : :
432 : : /*
433 : : * Convert the tuple to the new parent's layout, if different from
434 : : * the previous parent.
435 : : */
436 [ + + ]: 77674 : if (dispatch->tupslot)
437 : : {
438 : 41146 : AttrMap *map = dispatch->tupmap;
439 : 41146 : TupleTableSlot *tempslot = myslot;
440 : :
441 : 41146 : myslot = dispatch->tupslot;
442 : 41146 : slot = execute_attr_map_slot(map, slot, myslot);
443 : :
444 [ + + ]: 41146 : if (tempslot != NULL)
445 : 196 : ExecClearTuple(tempslot);
446 : : }
447 : : }
448 : :
449 : : /*
450 : : * If this partition is the default one, we must check its partition
451 : : * constraint now, which may have changed concurrently due to
452 : : * partitions being added to the parent.
453 : : *
454 : : * (We do this here, and do not rely on ExecInsert doing it, because
455 : : * we don't want to miss doing it for non-leaf partitions.)
456 : : */
457 [ + + ]: 705921 : if (partidx == partdesc->boundinfo->default_index)
458 : : {
459 : : /*
460 : : * The tuple must match the partition's layout for the constraint
461 : : * expression to be evaluated successfully. If the partition is
462 : : * sub-partitioned, that would already be the case due to the code
463 : : * above, but for a leaf partition the tuple still matches the
464 : : * parent's layout.
465 : : *
466 : : * Note that we have a map to convert from root to current
467 : : * partition, but not from immediate parent to current partition.
468 : : * So if we have to convert, do it from the root slot; if not, use
469 : : * the root slot as-is.
470 : : */
471 [ + + ]: 401 : if (is_leaf)
472 : : {
473 : 373 : TupleConversionMap *map = ExecGetRootToChildMap(rri, estate);
474 : :
475 [ + + ]: 373 : if (map)
476 : 106 : slot = execute_attr_map_slot(map->attrMap, rootslot,
477 : : rri->ri_PartitionTupleSlot);
478 : : else
479 : 267 : slot = rootslot;
480 : : }
481 : :
482 : 401 : ExecPartitionCheck(rri, slot, estate, true);
483 : : }
484 : : }
485 : :
486 : : /* Release the tuple in the lowest parent's dedicated slot. */
487 [ + + ]: 628247 : if (myslot != NULL)
488 : 40925 : ExecClearTuple(myslot);
489 : : /* and restore ecxt's scantuple */
490 : 628247 : ecxt->ecxt_scantuple = ecxt_scantuple_saved;
491 : 628247 : MemoryContextSwitchTo(oldcxt);
492 : :
493 : 628247 : return rri;
494 : : }
495 : :
496 : : /*
497 : : * IsIndexCompatibleAsArbiter
498 : : * Return true if two indexes are identical for INSERT ON CONFLICT
499 : : * purposes.
500 : : *
501 : : * Only indexes of the same relation are supported.
502 : : */
503 : : static bool
504 : 23 : IsIndexCompatibleAsArbiter(Relation arbiterIndexRelation,
505 : : IndexInfo *arbiterIndexInfo,
506 : : Relation indexRelation,
507 : : IndexInfo *indexInfo)
508 : : {
509 : : Assert(arbiterIndexRelation->rd_index->indrelid == indexRelation->rd_index->indrelid);
510 : :
511 : : /* must match whether they're unique */
512 [ - + ]: 23 : if (arbiterIndexInfo->ii_Unique != indexInfo->ii_Unique)
513 : 0 : return false;
514 : :
515 : : /* No support currently for comparing exclusion indexes. */
516 [ + - ]: 23 : if (arbiterIndexInfo->ii_ExclusionOps != NULL ||
517 [ - + ]: 23 : indexInfo->ii_ExclusionOps != NULL)
518 : 0 : return false;
519 : :
520 : : /* the "nulls not distinct" criterion must match */
521 : 23 : if (arbiterIndexInfo->ii_NullsNotDistinct !=
522 [ - + ]: 23 : indexInfo->ii_NullsNotDistinct)
523 : 0 : return false;
524 : :
525 : : /* number of key attributes must match */
526 : 23 : if (arbiterIndexInfo->ii_NumIndexKeyAttrs !=
527 [ - + ]: 23 : indexInfo->ii_NumIndexKeyAttrs)
528 : 0 : return false;
529 : :
530 [ + + ]: 30 : for (int i = 0; i < arbiterIndexInfo->ii_NumIndexKeyAttrs; i++)
531 : : {
532 : 23 : if (arbiterIndexRelation->rd_indcollation[i] !=
533 [ + + ]: 23 : indexRelation->rd_indcollation[i])
534 : 16 : return false;
535 : :
536 : 7 : if (arbiterIndexRelation->rd_opfamily[i] !=
537 [ - + ]: 7 : indexRelation->rd_opfamily[i])
538 : 0 : return false;
539 : :
540 : 7 : if (arbiterIndexRelation->rd_index->indkey.values[i] !=
541 [ - + ]: 7 : indexRelation->rd_index->indkey.values[i])
542 : 0 : return false;
543 : : }
544 : :
545 [ - + ]: 7 : if (list_difference(RelationGetIndexExpressions(arbiterIndexRelation),
546 : 7 : RelationGetIndexExpressions(indexRelation)) != NIL)
547 : 0 : return false;
548 : :
549 [ - + ]: 7 : if (list_difference(RelationGetIndexPredicate(arbiterIndexRelation),
550 : 7 : RelationGetIndexPredicate(indexRelation)) != NIL)
551 : 0 : return false;
552 : 7 : return true;
553 : : }
554 : :
555 : : /*
556 : : * ExecInitPartitionInfo
557 : : * Lock the partition and initialize ResultRelInfo. Also setup other
558 : : * information for the partition and store it in the next empty slot in
559 : : * the proute->partitions array.
560 : : *
561 : : * Returns the ResultRelInfo
562 : : */
563 : : static ResultRelInfo *
564 : 4364 : ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
565 : : PartitionTupleRouting *proute,
566 : : PartitionDispatch dispatch,
567 : : ResultRelInfo *rootResultRelInfo,
568 : : int partidx)
569 : : {
570 : 4364 : ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
571 : 4364 : Oid partOid = dispatch->partdesc->oids[partidx];
572 : : Relation partrel;
573 : 4364 : int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
574 : 4364 : Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc;
575 : : ResultRelInfo *leaf_part_rri;
576 : : MemoryContext oldcxt;
577 : 4364 : AttrMap *part_attmap = NULL;
578 : : bool found_whole_row;
579 : :
580 : 4364 : oldcxt = MemoryContextSwitchTo(proute->memcxt);
581 : :
582 : 4364 : partrel = table_open(partOid, RowExclusiveLock);
583 : :
584 : 4364 : leaf_part_rri = makeNode(ResultRelInfo);
585 : 4364 : InitResultRelInfo(leaf_part_rri,
586 : : partrel,
587 : : 0,
588 : : rootResultRelInfo,
589 : : estate->es_instrument);
590 : :
591 : : /*
592 : : * Verify result relation is a valid target for an INSERT. An UPDATE of a
593 : : * partition-key becomes a DELETE+INSERT operation, so this check is still
594 : : * required when the operation is CMD_UPDATE. It is also required for
595 : : * CMD_DELETE, because DELETE ... FOR PORTION OF inserts leftover rows.
596 : : */
597 [ + + ]: 4364 : CheckValidResultRel(leaf_part_rri, CMD_INSERT,
598 : : node ? node->onConflictAction : ONCONFLICT_NONE, NIL, node);
599 : :
600 : : /*
601 : : * Open partition indices. The user may have asked to check for conflicts
602 : : * within this leaf partition and do "nothing" instead of throwing an
603 : : * error. Be prepared in that case by initializing the index information
604 : : * needed by ExecInsert() to perform speculative insertions.
605 : : */
606 [ + + ]: 4357 : if (partrel->rd_rel->relhasindex &&
607 [ + - ]: 1241 : leaf_part_rri->ri_IndexRelationDescs == NULL)
608 : 1241 : ExecOpenIndices(leaf_part_rri,
609 [ + + ]: 2354 : (node != NULL &&
610 [ + + ]: 2354 : node->onConflictAction != ONCONFLICT_NONE));
611 : :
612 : : /*
613 : : * Build WITH CHECK OPTION constraints for the partition. Note that we
614 : : * didn't build the withCheckOptionList for partitions within the planner,
615 : : * but simple translation of varattnos will suffice. This only occurs for
616 : : * the INSERT case or in the case of UPDATE/DELETE/MERGE tuple routing
617 : : * where we didn't find a result rel to reuse. We reach here with DELETE
618 : : * only when inserting temporal leftovers.
619 : : */
620 [ + + + + ]: 4357 : if (node && node->withCheckOptionLists != NIL)
621 : : {
622 : : List *wcoList;
623 : 71 : List *wcoExprs = NIL;
624 : : ListCell *ll;
625 : :
626 : : /*
627 : : * In the case of INSERT on a partitioned table, there is only one
628 : : * plan. Likewise, there is only one WCO list, not one per partition.
629 : : * For UPDATE/DELETE/MERGE, there are as many WCO lists as there are
630 : : * plans.
631 : : */
632 : : Assert((node->operation == CMD_INSERT &&
633 : : list_length(node->withCheckOptionLists) == 1 &&
634 : : list_length(node->resultRelations) == 1) ||
635 : : (node->operation == CMD_UPDATE &&
636 : : list_length(node->withCheckOptionLists) ==
637 : : list_length(node->resultRelations)) ||
638 : : (node->operation == CMD_DELETE &&
639 : : list_length(node->withCheckOptionLists) ==
640 : : list_length(node->resultRelations)) ||
641 : : (node->operation == CMD_MERGE &&
642 : : list_length(node->withCheckOptionLists) ==
643 : : list_length(node->resultRelations)));
644 : :
645 : : /*
646 : : * Use the WCO list of the first plan as a reference to calculate
647 : : * attno's for the WCO list of this partition. In the INSERT case,
648 : : * that refers to the root partitioned table, whereas in the UPDATE
649 : : * tuple routing case, that refers to the first partition in the
650 : : * mtstate->resultRelInfo array. In any case, both that relation and
651 : : * this partition should have the same columns, so we should be able
652 : : * to map attributes successfully.
653 : : */
654 : 71 : wcoList = linitial(node->withCheckOptionLists);
655 : :
656 : : /*
657 : : * Convert Vars in it to contain this partition's attribute numbers.
658 : : */
659 : : part_attmap =
660 : 71 : build_attrmap_by_name(RelationGetDescr(partrel),
661 : : RelationGetDescr(firstResultRel),
662 : : false);
663 : : wcoList = (List *)
664 : 71 : map_variable_attnos((Node *) wcoList,
665 : : firstVarno, 0,
666 : : part_attmap,
667 : 71 : RelationGetForm(partrel)->reltype,
668 : : &found_whole_row);
669 : : /* We ignore the value of found_whole_row. */
670 : :
671 [ + - + + : 194 : foreach(ll, wcoList)
+ + ]
672 : : {
673 : 123 : WithCheckOption *wco = lfirst_node(WithCheckOption, ll);
674 : 123 : ExprState *wcoExpr = ExecInitQual(castNode(List, wco->qual),
675 : : &mtstate->ps);
676 : :
677 : 123 : wcoExprs = lappend(wcoExprs, wcoExpr);
678 : : }
679 : :
680 : 71 : leaf_part_rri->ri_WithCheckOptions = wcoList;
681 : 71 : leaf_part_rri->ri_WithCheckOptionExprs = wcoExprs;
682 : : }
683 : :
684 : : /*
685 : : * Build the RETURNING projection for the partition. Note that we didn't
686 : : * build the returningList for partitions within the planner, but simple
687 : : * translation of varattnos will suffice. This only occurs for the INSERT
688 : : * case or in the case of UPDATE/DELETE/MERGE tuple routing where we
689 : : * didn't find a result rel to reuse. We reach here with DELETE only when
690 : : * inserting temporal leftovers.
691 : : */
692 [ + + + + ]: 4357 : if (node && node->returningLists != NIL)
693 : : {
694 : : TupleTableSlot *slot;
695 : : ExprContext *econtext;
696 : : List *returningList;
697 : :
698 : : /* See the comment above for WCO lists. */
699 : : Assert((node->operation == CMD_INSERT &&
700 : : list_length(node->returningLists) == 1 &&
701 : : list_length(node->resultRelations) == 1) ||
702 : : (node->operation == CMD_UPDATE &&
703 : : list_length(node->returningLists) ==
704 : : list_length(node->resultRelations)) ||
705 : : (node->operation == CMD_DELETE &&
706 : : list_length(node->returningLists) ==
707 : : list_length(node->resultRelations)) ||
708 : : (node->operation == CMD_MERGE &&
709 : : list_length(node->returningLists) ==
710 : : list_length(node->resultRelations)));
711 : :
712 : : /*
713 : : * Use the RETURNING list of the first plan as a reference to
714 : : * calculate attno's for the RETURNING list of this partition. See
715 : : * the comment above for WCO lists for more details on why this is
716 : : * okay.
717 : : */
718 : 213 : returningList = linitial(node->returningLists);
719 : :
720 : : /*
721 : : * Convert Vars in it to contain this partition's attribute numbers.
722 : : */
723 [ + - ]: 213 : if (part_attmap == NULL)
724 : : part_attmap =
725 : 213 : build_attrmap_by_name(RelationGetDescr(partrel),
726 : : RelationGetDescr(firstResultRel),
727 : : false);
728 : : returningList = (List *)
729 : 213 : map_variable_attnos((Node *) returningList,
730 : : firstVarno, 0,
731 : : part_attmap,
732 : 213 : RelationGetForm(partrel)->reltype,
733 : : &found_whole_row);
734 : : /* We ignore the value of found_whole_row. */
735 : :
736 : 213 : leaf_part_rri->ri_returningList = returningList;
737 : :
738 : : /*
739 : : * Initialize the projection itself.
740 : : *
741 : : * Use the slot and the expression context that would have been set up
742 : : * in ExecInitModifyTable() for projection's output.
743 : : */
744 : : Assert(mtstate->ps.ps_ResultTupleSlot != NULL);
745 : 213 : slot = mtstate->ps.ps_ResultTupleSlot;
746 : : Assert(mtstate->ps.ps_ExprContext != NULL);
747 : 213 : econtext = mtstate->ps.ps_ExprContext;
748 : 213 : leaf_part_rri->ri_projectReturning =
749 : 213 : ExecBuildProjectionInfo(returningList, econtext, slot,
750 : : &mtstate->ps, RelationGetDescr(partrel));
751 : : }
752 : :
753 : : /* Set up information needed for routing tuples to the partition. */
754 : 4357 : ExecInitRoutingInfo(mtstate, estate, proute, dispatch,
755 : : leaf_part_rri, partidx, false);
756 : :
757 : : /*
758 : : * If there is an ON CONFLICT clause, initialize state for it.
759 : : */
760 [ + + + + ]: 4357 : if (node && node->onConflictAction != ONCONFLICT_NONE)
761 : : {
762 : 222 : TupleDesc partrelDesc = RelationGetDescr(partrel);
763 : 222 : ExprContext *econtext = mtstate->ps.ps_ExprContext;
764 : 222 : List *arbiterIndexes = NIL;
765 : 222 : int additional_arbiters = 0;
766 : :
767 : : /*
768 : : * If there is a list of arbiter indexes, map it to a list of indexes
769 : : * in the partition. We also add any "identical indexes" to any of
770 : : * those, to cover the case where one of them is concurrently being
771 : : * reindexed.
772 : : */
773 [ + + ]: 222 : if (rootResultRelInfo->ri_onConflictArbiterIndexes != NIL)
774 : : {
775 : 192 : List *unparented_idxs = NIL,
776 : 192 : *arbiters_listidxs = NIL,
777 : 192 : *ancestors_seen = NIL;
778 : :
779 [ + + ]: 407 : for (int listidx = 0; listidx < leaf_part_rri->ri_NumIndices; listidx++)
780 : : {
781 : : Oid indexoid;
782 : : List *ancestors;
783 : :
784 : : /*
785 : : * If one of this index's ancestors is in the root's arbiter
786 : : * list, then use this index as arbiter for this partition.
787 : : * Otherwise, if this index has no parent, track it for later,
788 : : * in case REINDEX CONCURRENTLY is working on one of the
789 : : * arbiters.
790 : : *
791 : : * However, if two indexes appear to have the same parent,
792 : : * treat the second of these as if it had no parent. This
793 : : * sounds counterintuitive, but it can happen if a transaction
794 : : * running REINDEX CONCURRENTLY commits right between those
795 : : * two indexes are checked by another process in this loop.
796 : : * This will have the effect of also treating that second
797 : : * index as arbiter.
798 : : *
799 : : * XXX get_partition_ancestors scans pg_inherits, which is not
800 : : * only slow, but also means the catalog snapshot can get
801 : : * invalidated each time through the loop (cf.
802 : : * GetNonHistoricCatalogSnapshot). Consider a syscache or
803 : : * some other way to cache?
804 : : */
805 : 215 : indexoid = RelationGetRelid(leaf_part_rri->ri_IndexRelationDescs[listidx]);
806 : 215 : ancestors = get_partition_ancestors(indexoid);
807 : 215 : INJECTION_POINT("exec-init-partition-after-get-partition-ancestors", NULL);
808 : :
809 [ + + ]: 215 : if (ancestors != NIL &&
810 [ + + ]: 193 : !list_member_oid(ancestors_seen, linitial_oid(ancestors)))
811 : : {
812 [ + - + - : 384 : foreach_oid(parent_idx, rootResultRelInfo->ri_onConflictArbiterIndexes)
+ + ]
813 : : {
814 [ + - ]: 192 : if (list_member_oid(ancestors, parent_idx))
815 : : {
816 : 192 : ancestors_seen = lappend_oid(ancestors_seen, linitial_oid(ancestors));
817 : 192 : arbiterIndexes = lappend_oid(arbiterIndexes, indexoid);
818 : 192 : arbiters_listidxs = lappend_int(arbiters_listidxs, listidx);
819 : 192 : break;
820 : : }
821 : : }
822 : : }
823 : : else
824 : 23 : unparented_idxs = lappend_int(unparented_idxs, listidx);
825 : :
826 : 215 : list_free(ancestors);
827 : : }
828 : :
829 : : /*
830 : : * If we found any indexes with no ancestors, it's possible that
831 : : * some arbiter index is undergoing concurrent reindex. Match all
832 : : * unparented indexes against arbiters; add unparented matching
833 : : * ones as "additional arbiters".
834 : : *
835 : : * This is critical so that all concurrent transactions use the
836 : : * same set as arbiters during REINDEX CONCURRENTLY, to avoid
837 : : * spurious "duplicate key" errors.
838 : : */
839 [ + + + - ]: 192 : if (unparented_idxs && arbiterIndexes)
840 : : {
841 [ + - + + : 69 : foreach_int(unparented_i, unparented_idxs)
+ + ]
842 : : {
843 : : Relation unparented_rel;
844 : : IndexInfo *unparented_ii;
845 : :
846 : 23 : unparented_rel = leaf_part_rri->ri_IndexRelationDescs[unparented_i];
847 : 23 : unparented_ii = leaf_part_rri->ri_IndexRelationInfo[unparented_i];
848 : :
849 : : Assert(!list_member_oid(arbiterIndexes,
850 : : unparented_rel->rd_index->indexrelid));
851 : :
852 : : /* Ignore indexes not ready */
853 [ - + ]: 23 : if (!unparented_ii->ii_ReadyForInserts)
854 : 0 : continue;
855 : :
856 [ + - + + : 62 : foreach_int(arbiter_i, arbiters_listidxs)
+ + ]
857 : : {
858 : : Relation arbiter_rel;
859 : : IndexInfo *arbiter_ii;
860 : :
861 : 23 : arbiter_rel = leaf_part_rri->ri_IndexRelationDescs[arbiter_i];
862 : 23 : arbiter_ii = leaf_part_rri->ri_IndexRelationInfo[arbiter_i];
863 : :
864 : : /*
865 : : * If the non-ancestor index is compatible with the
866 : : * arbiter, use the non-ancestor as arbiter too.
867 : : */
868 [ + + ]: 23 : if (IsIndexCompatibleAsArbiter(arbiter_rel,
869 : : arbiter_ii,
870 : : unparented_rel,
871 : : unparented_ii))
872 : : {
873 : 7 : arbiterIndexes = lappend_oid(arbiterIndexes,
874 : 7 : unparented_rel->rd_index->indexrelid);
875 : 7 : additional_arbiters++;
876 : 7 : break;
877 : : }
878 : : }
879 : : }
880 : : }
881 : 192 : list_free(unparented_idxs);
882 : 192 : list_free(arbiters_listidxs);
883 : 192 : list_free(ancestors_seen);
884 : : }
885 : :
886 : : /*
887 : : * We expect to find as many arbiter indexes on this partition as the
888 : : * root has, plus however many "additional arbiters" (to wit: those
889 : : * being concurrently rebuilt) we found.
890 : : */
891 : 222 : if (list_length(rootResultRelInfo->ri_onConflictArbiterIndexes) !=
892 [ - + ]: 222 : list_length(arbiterIndexes) - additional_arbiters)
893 [ # # ]: 0 : elog(ERROR, "invalid arbiter index list");
894 : 222 : leaf_part_rri->ri_onConflictArbiterIndexes = arbiterIndexes;
895 : :
896 : : /*
897 : : * In the DO UPDATE and DO SELECT cases, we have some more state to
898 : : * initialize.
899 : : */
900 [ + + ]: 222 : if (node->onConflictAction == ONCONFLICT_UPDATE ||
901 [ + + ]: 94 : node->onConflictAction == ONCONFLICT_SELECT)
902 : : {
903 : 184 : OnConflictActionState *onconfl = makeNode(OnConflictActionState);
904 : : TupleConversionMap *map;
905 : :
906 : 184 : map = ExecGetRootToChildMap(leaf_part_rri, estate);
907 : :
908 : : Assert(node->onConflictSet != NIL ||
909 : : node->onConflictAction == ONCONFLICT_SELECT);
910 : : Assert(rootResultRelInfo->ri_onConflict != NULL);
911 : :
912 : 184 : leaf_part_rri->ri_onConflict = onconfl;
913 : :
914 : : /* Lock strength for DO SELECT [FOR UPDATE/SHARE] */
915 : 184 : onconfl->oc_LockStrength =
916 : 184 : rootResultRelInfo->ri_onConflict->oc_LockStrength;
917 : :
918 : : /*
919 : : * Need a separate existing slot for each partition, as the
920 : : * partition could be of a different AM, even if the tuple
921 : : * descriptors match.
922 : : */
923 : 184 : onconfl->oc_Existing =
924 : 184 : table_slot_create(leaf_part_rri->ri_RelationDesc,
925 : 184 : &mtstate->ps.state->es_tupleTable);
926 : :
927 : : /*
928 : : * If the partition's tuple descriptor matches exactly the root
929 : : * parent (the common case), we can re-use most of the parent's ON
930 : : * CONFLICT action state, skipping a bunch of work. Otherwise, we
931 : : * need to create state specific to this partition.
932 : : */
933 [ + + ]: 184 : if (map == NULL)
934 : : {
935 : : /*
936 : : * It's safe to reuse these from the partition root, as we
937 : : * only process one tuple at a time (therefore we won't
938 : : * overwrite needed data in slots), and the results of any
939 : : * projections are independent of the underlying storage.
940 : : * Projections and where clauses themselves don't store state
941 : : * / are independent of the underlying storage.
942 : : */
943 : 102 : onconfl->oc_ProjSlot =
944 : 102 : rootResultRelInfo->ri_onConflict->oc_ProjSlot;
945 : 102 : onconfl->oc_ProjInfo =
946 : 102 : rootResultRelInfo->ri_onConflict->oc_ProjInfo;
947 : 102 : onconfl->oc_WhereClause =
948 : 102 : rootResultRelInfo->ri_onConflict->oc_WhereClause;
949 : : }
950 : : else
951 : : {
952 : : /*
953 : : * For ON CONFLICT DO UPDATE, translate expressions in
954 : : * onConflictSet to account for different attribute numbers.
955 : : * For that, map partition varattnos twice: first to catch the
956 : : * EXCLUDED pseudo-relation (INNER_VAR), and second to handle
957 : : * the main target relation (firstVarno).
958 : : */
959 [ + + ]: 82 : if (node->onConflictAction == ONCONFLICT_UPDATE)
960 : : {
961 : : List *onconflset;
962 : : List *onconflcols;
963 : :
964 : 50 : onconflset = copyObject(node->onConflictSet);
965 [ + + ]: 50 : if (part_attmap == NULL)
966 : : part_attmap =
967 : 46 : build_attrmap_by_name(RelationGetDescr(partrel),
968 : : RelationGetDescr(firstResultRel),
969 : : false);
970 : : onconflset = (List *)
971 : 50 : map_variable_attnos((Node *) onconflset,
972 : : INNER_VAR, 0,
973 : : part_attmap,
974 : 50 : RelationGetForm(partrel)->reltype,
975 : : &found_whole_row);
976 : : /* We ignore the value of found_whole_row. */
977 : : onconflset = (List *)
978 : 50 : map_variable_attnos((Node *) onconflset,
979 : : firstVarno, 0,
980 : : part_attmap,
981 : 50 : RelationGetForm(partrel)->reltype,
982 : : &found_whole_row);
983 : : /* We ignore the value of found_whole_row. */
984 : :
985 : : /*
986 : : * Finally, adjust the target colnos to match the
987 : : * partition.
988 : : */
989 : 50 : onconflcols = adjust_partition_colnos(node->onConflictCols,
990 : : leaf_part_rri);
991 : :
992 : : /* create the tuple slot for the UPDATE SET projection */
993 : 50 : onconfl->oc_ProjSlot =
994 : 50 : table_slot_create(partrel,
995 : 50 : &mtstate->ps.state->es_tupleTable);
996 : :
997 : : /* build UPDATE SET projection state */
998 : 50 : onconfl->oc_ProjInfo =
999 : 50 : ExecBuildUpdateProjection(onconflset,
1000 : : true,
1001 : : onconflcols,
1002 : : partrelDesc,
1003 : : econtext,
1004 : : onconfl->oc_ProjSlot,
1005 : : &mtstate->ps);
1006 : : }
1007 : :
1008 : : /*
1009 : : * For both ON CONFLICT DO UPDATE and ON CONFLICT DO SELECT,
1010 : : * there may be a WHERE clause. If so, initialize state where
1011 : : * it will be evaluated, mapping the attribute numbers
1012 : : * appropriately. As with onConflictSet, we need to map
1013 : : * partition varattnos twice, to catch both the EXCLUDED
1014 : : * pseudo-relation (INNER_VAR), and the main target relation
1015 : : * (firstVarno).
1016 : : */
1017 [ + + ]: 82 : if (node->onConflictWhere)
1018 : : {
1019 : : List *clause;
1020 : :
1021 [ - + ]: 36 : if (part_attmap == NULL)
1022 : : part_attmap =
1023 : 0 : build_attrmap_by_name(RelationGetDescr(partrel),
1024 : : RelationGetDescr(firstResultRel),
1025 : : false);
1026 : :
1027 : 36 : clause = copyObject((List *) node->onConflictWhere);
1028 : : clause = (List *)
1029 : 36 : map_variable_attnos((Node *) clause,
1030 : : INNER_VAR, 0,
1031 : : part_attmap,
1032 : 36 : RelationGetForm(partrel)->reltype,
1033 : : &found_whole_row);
1034 : : /* We ignore the value of found_whole_row. */
1035 : : clause = (List *)
1036 : 36 : map_variable_attnos((Node *) clause,
1037 : : firstVarno, 0,
1038 : : part_attmap,
1039 : 36 : RelationGetForm(partrel)->reltype,
1040 : : &found_whole_row);
1041 : : /* We ignore the value of found_whole_row. */
1042 : 36 : onconfl->oc_WhereClause =
1043 : 36 : ExecInitQual(clause, &mtstate->ps);
1044 : : }
1045 : : }
1046 : : }
1047 : : }
1048 : :
1049 : : /*
1050 : : * Since we've just initialized this ResultRelInfo, it's not in any list
1051 : : * attached to the estate as yet. Add it, so that it can be found later.
1052 : : *
1053 : : * Note that the entries in this list appear in no predetermined order,
1054 : : * because partition result rels are initialized as and when they're
1055 : : * needed.
1056 : : */
1057 : 4357 : MemoryContextSwitchTo(estate->es_query_cxt);
1058 : 4357 : estate->es_tuple_routing_result_relations =
1059 : 4357 : lappend(estate->es_tuple_routing_result_relations,
1060 : : leaf_part_rri);
1061 : :
1062 : : /*
1063 : : * Initialize information about this partition that's needed to handle
1064 : : * MERGE. We take the "first" result relation's mergeActionList as
1065 : : * reference and make copy for this relation, converting stuff that
1066 : : * references attribute numbers to match this relation's.
1067 : : *
1068 : : * This duplicates much of the logic in ExecInitMerge(), so if something
1069 : : * changes there, look here too.
1070 : : */
1071 [ + + + + ]: 4357 : if (node && node->operation == CMD_MERGE)
1072 : : {
1073 : 15 : List *firstMergeActionList = linitial(node->mergeActionLists);
1074 : : ListCell *lc;
1075 : 15 : ExprContext *econtext = mtstate->ps.ps_ExprContext;
1076 : : Node *joinCondition;
1077 : :
1078 [ + + ]: 15 : if (part_attmap == NULL)
1079 : : part_attmap =
1080 : 7 : build_attrmap_by_name(RelationGetDescr(partrel),
1081 : : RelationGetDescr(firstResultRel),
1082 : : false);
1083 : :
1084 [ + - ]: 15 : if (unlikely(!leaf_part_rri->ri_projectNewInfoValid))
1085 : 15 : ExecInitMergeTupleSlots(mtstate, leaf_part_rri);
1086 : :
1087 : : /* Initialize state for join condition checking. */
1088 : : joinCondition =
1089 : 15 : map_variable_attnos(linitial(node->mergeJoinConditions),
1090 : : firstVarno, 0,
1091 : : part_attmap,
1092 : 15 : RelationGetForm(partrel)->reltype,
1093 : : &found_whole_row);
1094 : : /* We ignore the value of found_whole_row. */
1095 : 15 : leaf_part_rri->ri_MergeJoinCondition =
1096 : 15 : ExecInitQual((List *) joinCondition, &mtstate->ps);
1097 : :
1098 [ + - + + : 37 : foreach(lc, firstMergeActionList)
+ + ]
1099 : : {
1100 : : /* Make a copy for this relation to be safe. */
1101 : 22 : MergeAction *action = copyObject(lfirst(lc));
1102 : : MergeActionState *action_state;
1103 : :
1104 : : /* Generate the action's state for this relation */
1105 : 22 : action_state = makeNode(MergeActionState);
1106 : 22 : action_state->mas_action = action;
1107 : :
1108 : : /* And put the action in the appropriate list */
1109 : 44 : leaf_part_rri->ri_MergeActions[action->matchKind] =
1110 : 22 : lappend(leaf_part_rri->ri_MergeActions[action->matchKind],
1111 : : action_state);
1112 : :
1113 [ + + + - ]: 22 : switch (action->commandType)
1114 : : {
1115 : 7 : case CMD_INSERT:
1116 : :
1117 : : /*
1118 : : * ExecCheckPlanOutput() already done on the targetlist
1119 : : * when "first" result relation initialized and it is same
1120 : : * for all result relations.
1121 : : */
1122 : 7 : action_state->mas_proj =
1123 : 7 : ExecBuildProjectionInfo(action->targetList, econtext,
1124 : : leaf_part_rri->ri_newTupleSlot,
1125 : : &mtstate->ps,
1126 : : RelationGetDescr(partrel));
1127 : 7 : break;
1128 : 11 : case CMD_UPDATE:
1129 : :
1130 : : /*
1131 : : * Convert updateColnos from "first" result relation
1132 : : * attribute numbers to this result rel's.
1133 : : */
1134 [ + - ]: 11 : if (part_attmap)
1135 : 11 : action->updateColnos =
1136 : 11 : adjust_partition_colnos_using_map(action->updateColnos,
1137 : : part_attmap);
1138 : 11 : action_state->mas_proj =
1139 : 11 : ExecBuildUpdateProjection(action->targetList,
1140 : : true,
1141 : : action->updateColnos,
1142 : 11 : RelationGetDescr(leaf_part_rri->ri_RelationDesc),
1143 : : econtext,
1144 : : leaf_part_rri->ri_newTupleSlot,
1145 : : NULL);
1146 : 11 : break;
1147 : 4 : case CMD_DELETE:
1148 : : case CMD_NOTHING:
1149 : : /* Nothing to do */
1150 : 4 : break;
1151 : :
1152 : 0 : default:
1153 [ # # ]: 0 : elog(ERROR, "unknown action in MERGE WHEN clause");
1154 : : }
1155 : :
1156 : : /* found_whole_row intentionally ignored. */
1157 : 22 : action->qual =
1158 : 22 : map_variable_attnos(action->qual,
1159 : : firstVarno, 0,
1160 : : part_attmap,
1161 : 22 : RelationGetForm(partrel)->reltype,
1162 : : &found_whole_row);
1163 : 22 : action_state->mas_whenqual =
1164 : 22 : ExecInitQual((List *) action->qual, &mtstate->ps);
1165 : : }
1166 : : }
1167 : 4357 : MemoryContextSwitchTo(oldcxt);
1168 : :
1169 : 4357 : return leaf_part_rri;
1170 : : }
1171 : :
1172 : : /*
1173 : : * ExecInitRoutingInfo
1174 : : * Set up information needed for translating tuples between root
1175 : : * partitioned table format and partition format, and keep track of it
1176 : : * in PartitionTupleRouting.
1177 : : */
1178 : : static void
1179 : 4779 : ExecInitRoutingInfo(ModifyTableState *mtstate,
1180 : : EState *estate,
1181 : : PartitionTupleRouting *proute,
1182 : : PartitionDispatch dispatch,
1183 : : ResultRelInfo *partRelInfo,
1184 : : int partidx,
1185 : : bool is_borrowed_rel)
1186 : : {
1187 : : MemoryContext oldcxt;
1188 : : int rri_index;
1189 : :
1190 : 4779 : oldcxt = MemoryContextSwitchTo(proute->memcxt);
1191 : :
1192 : : /*
1193 : : * Set up tuple conversion between root parent and the partition if the
1194 : : * two have different rowtypes. If conversion is indeed required, also
1195 : : * initialize a slot dedicated to storing this partition's converted
1196 : : * tuples. Various operations that are applied to tuples after routing,
1197 : : * such as checking constraints, will refer to this slot.
1198 : : */
1199 [ + + ]: 4779 : if (ExecGetRootToChildMap(partRelInfo, estate) != NULL)
1200 : : {
1201 : 928 : Relation partrel = partRelInfo->ri_RelationDesc;
1202 : :
1203 : : /*
1204 : : * This pins the partition's TupleDesc, which will be released at the
1205 : : * end of the command.
1206 : : */
1207 : 928 : partRelInfo->ri_PartitionTupleSlot =
1208 : 928 : table_slot_create(partrel, &estate->es_tupleTable);
1209 : : }
1210 : : else
1211 : 3851 : partRelInfo->ri_PartitionTupleSlot = NULL;
1212 : :
1213 : : /*
1214 : : * If the partition is a foreign table, let the FDW init itself for
1215 : : * routing tuples to the partition.
1216 : : */
1217 [ + + ]: 4779 : if (partRelInfo->ri_FdwRoutine != NULL &&
1218 [ + - ]: 46 : partRelInfo->ri_FdwRoutine->BeginForeignInsert != NULL)
1219 : 46 : partRelInfo->ri_FdwRoutine->BeginForeignInsert(mtstate, partRelInfo);
1220 : :
1221 : : /*
1222 : : * Determine if the FDW supports batch insert and determine the batch size
1223 : : * (a FDW may support batching, but it may be disabled for the
1224 : : * server/table or for this particular query).
1225 : : *
1226 : : * If the FDW does not support batching, we set the batch size to 1.
1227 : : */
1228 [ + + ]: 4773 : if (partRelInfo->ri_FdwRoutine != NULL &&
1229 [ + - ]: 40 : partRelInfo->ri_FdwRoutine->GetForeignModifyBatchSize &&
1230 [ + - ]: 40 : partRelInfo->ri_FdwRoutine->ExecForeignBatchInsert)
1231 : 40 : partRelInfo->ri_BatchSize =
1232 : 40 : partRelInfo->ri_FdwRoutine->GetForeignModifyBatchSize(partRelInfo);
1233 : : else
1234 : 4733 : partRelInfo->ri_BatchSize = 1;
1235 : :
1236 : : Assert(partRelInfo->ri_BatchSize >= 1);
1237 : :
1238 : 4773 : partRelInfo->ri_CopyMultiInsertBuffer = NULL;
1239 : :
1240 : : /*
1241 : : * Keep track of it in the PartitionTupleRouting->partitions array.
1242 : : */
1243 : : Assert(dispatch->indexes[partidx] == -1);
1244 : :
1245 : 4773 : rri_index = proute->num_partitions++;
1246 : :
1247 : : /* Allocate or enlarge the array, as needed */
1248 [ + + ]: 4773 : if (proute->num_partitions >= proute->max_partitions)
1249 : : {
1250 [ + + ]: 3265 : if (proute->max_partitions == 0)
1251 : : {
1252 : 3257 : proute->max_partitions = 8;
1253 : 3257 : proute->partitions = palloc_array(ResultRelInfo *, proute->max_partitions);
1254 : 3257 : proute->is_borrowed_rel = palloc_array(bool, proute->max_partitions);
1255 : : }
1256 : : else
1257 : : {
1258 : 8 : proute->max_partitions *= 2;
1259 : 8 : proute->partitions = repalloc_array(proute->partitions,
1260 : : ResultRelInfo *, proute->max_partitions);
1261 : 8 : proute->is_borrowed_rel = repalloc_array(proute->is_borrowed_rel,
1262 : : bool, proute->max_partitions);
1263 : : }
1264 : : }
1265 : :
1266 : 4773 : proute->partitions[rri_index] = partRelInfo;
1267 : 4773 : proute->is_borrowed_rel[rri_index] = is_borrowed_rel;
1268 : 4773 : dispatch->indexes[partidx] = rri_index;
1269 : :
1270 : 4773 : MemoryContextSwitchTo(oldcxt);
1271 : 4773 : }
1272 : :
1273 : : /*
1274 : : * ExecInitPartitionDispatchInfo
1275 : : * Lock the partitioned table (if not locked already) and initialize
1276 : : * PartitionDispatch for a partitioned table and store it in the next
1277 : : * available slot in the proute->partition_dispatch_info array. Also,
1278 : : * record the index into this array in the parent_pd->indexes[] array in
1279 : : * the partidx element so that we can properly retrieve the newly created
1280 : : * PartitionDispatch later.
1281 : : */
1282 : : static PartitionDispatch
1283 : 4260 : ExecInitPartitionDispatchInfo(EState *estate,
1284 : : PartitionTupleRouting *proute, Oid partoid,
1285 : : PartitionDispatch parent_pd, int partidx,
1286 : : ResultRelInfo *rootResultRelInfo)
1287 : : {
1288 : : Relation rel;
1289 : : PartitionDesc partdesc;
1290 : : PartitionDispatch pd;
1291 : : int dispatchidx;
1292 : : MemoryContext oldcxt;
1293 : :
1294 : : /*
1295 : : * For data modification, it is better that executor does not include
1296 : : * partitions being detached, except when running in snapshot-isolation
1297 : : * mode. This means that a read-committed transaction immediately gets a
1298 : : * "no partition for tuple" error when a tuple is inserted into a
1299 : : * partition that's being detached concurrently, but a transaction in
1300 : : * repeatable-read mode can still use such a partition.
1301 : : */
1302 [ + + ]: 4260 : if (estate->es_partition_directory == NULL)
1303 : 3438 : estate->es_partition_directory =
1304 : 3438 : CreatePartitionDirectory(estate->es_query_cxt,
1305 : : !IsolationUsesXactSnapshot());
1306 : :
1307 : 4260 : oldcxt = MemoryContextSwitchTo(proute->memcxt);
1308 : :
1309 : : /*
1310 : : * Only sub-partitioned tables need to be locked here. The root
1311 : : * partitioned table will already have been locked as it's referenced in
1312 : : * the query's rtable.
1313 : : */
1314 [ + + ]: 4260 : if (partoid != RelationGetRelid(proute->partition_root))
1315 : 798 : rel = table_open(partoid, RowExclusiveLock);
1316 : : else
1317 : 3462 : rel = proute->partition_root;
1318 : 4260 : partdesc = PartitionDirectoryLookup(estate->es_partition_directory, rel);
1319 : :
1320 : 4260 : pd = (PartitionDispatch) palloc(offsetof(PartitionDispatchData, indexes) +
1321 : 4260 : partdesc->nparts * sizeof(int));
1322 : 4260 : pd->reldesc = rel;
1323 : 4260 : pd->key = RelationGetPartitionKey(rel);
1324 : 4260 : pd->keystate = NIL;
1325 : 4260 : pd->partdesc = partdesc;
1326 [ + + ]: 4260 : if (parent_pd != NULL)
1327 : : {
1328 : 798 : TupleDesc tupdesc = RelationGetDescr(rel);
1329 : :
1330 : : /*
1331 : : * For sub-partitioned tables where the column order differs from its
1332 : : * direct parent partitioned table, we must store a tuple table slot
1333 : : * initialized with its tuple descriptor and a tuple conversion map to
1334 : : * convert a tuple from its parent's rowtype to its own. This is to
1335 : : * make sure that we are looking at the correct row using the correct
1336 : : * tuple descriptor when computing its partition key for tuple
1337 : : * routing.
1338 : : */
1339 : 798 : pd->tupmap = build_attrmap_by_name_if_req(RelationGetDescr(parent_pd->reldesc),
1340 : : tupdesc,
1341 : : false);
1342 : 798 : pd->tupslot = pd->tupmap ?
1343 [ + + ]: 798 : MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual) : NULL;
1344 : : }
1345 : : else
1346 : : {
1347 : : /* Not required for the root partitioned table */
1348 : 3462 : pd->tupmap = NULL;
1349 : 3462 : pd->tupslot = NULL;
1350 : : }
1351 : :
1352 : : /*
1353 : : * Initialize with -1 to signify that the corresponding partition's
1354 : : * ResultRelInfo or PartitionDispatch has not been created yet.
1355 : : */
1356 : 4260 : memset(pd->indexes, -1, sizeof(int) * partdesc->nparts);
1357 : :
1358 : : /* Track in PartitionTupleRouting for later use */
1359 : 4260 : dispatchidx = proute->num_dispatch++;
1360 : :
1361 : : /* Allocate or enlarge the array, as needed */
1362 [ + + ]: 4260 : if (proute->num_dispatch >= proute->max_dispatch)
1363 : : {
1364 [ + - ]: 3462 : if (proute->max_dispatch == 0)
1365 : : {
1366 : 3462 : proute->max_dispatch = 4;
1367 : 3462 : proute->partition_dispatch_info = palloc_array(PartitionDispatch, proute->max_dispatch);
1368 : 3462 : proute->nonleaf_partitions = palloc_array(ResultRelInfo *, proute->max_dispatch);
1369 : : }
1370 : : else
1371 : : {
1372 : 0 : proute->max_dispatch *= 2;
1373 : 0 : proute->partition_dispatch_info = repalloc_array(proute->partition_dispatch_info,
1374 : : PartitionDispatch,
1375 : : proute->max_dispatch);
1376 : 0 : proute->nonleaf_partitions = repalloc_array(proute->nonleaf_partitions,
1377 : : ResultRelInfo *,
1378 : : proute->max_dispatch);
1379 : : }
1380 : : }
1381 : 4260 : proute->partition_dispatch_info[dispatchidx] = pd;
1382 : :
1383 : : /*
1384 : : * If setting up a PartitionDispatch for a sub-partitioned table, we may
1385 : : * also need a minimally valid ResultRelInfo for checking the partition
1386 : : * constraint later; set that up now.
1387 : : */
1388 [ + + ]: 4260 : if (parent_pd)
1389 : : {
1390 : 798 : ResultRelInfo *rri = makeNode(ResultRelInfo);
1391 : :
1392 : 798 : InitResultRelInfo(rri, rel, 0, rootResultRelInfo, 0);
1393 : 798 : proute->nonleaf_partitions[dispatchidx] = rri;
1394 : : }
1395 : : else
1396 : 3462 : proute->nonleaf_partitions[dispatchidx] = NULL;
1397 : :
1398 : : /*
1399 : : * Finally, if setting up a PartitionDispatch for a sub-partitioned table,
1400 : : * install a downlink in the parent to allow quick descent.
1401 : : */
1402 [ + + ]: 4260 : if (parent_pd)
1403 : : {
1404 : : Assert(parent_pd->indexes[partidx] == -1);
1405 : 798 : parent_pd->indexes[partidx] = dispatchidx;
1406 : : }
1407 : :
1408 : 4260 : MemoryContextSwitchTo(oldcxt);
1409 : :
1410 : 4260 : return pd;
1411 : : }
1412 : :
1413 : : /*
1414 : : * ExecCleanupTupleRouting -- Clean up objects allocated for partition tuple
1415 : : * routing.
1416 : : *
1417 : : * Close all the partitioned tables, leaf partitions, and their indices.
1418 : : */
1419 : : void
1420 : 2900 : ExecCleanupTupleRouting(ModifyTableState *mtstate,
1421 : : PartitionTupleRouting *proute)
1422 : : {
1423 : : int i;
1424 : :
1425 : : /*
1426 : : * Remember, proute->partition_dispatch_info[0] corresponds to the root
1427 : : * partitioned table, which we must not try to close, because it is the
1428 : : * main target table of the query that will be closed by callers such as
1429 : : * ExecEndPlan() or DoCopy(). Also, tupslot is NULL for the root
1430 : : * partitioned table.
1431 : : */
1432 [ + + ]: 3549 : for (i = 1; i < proute->num_dispatch; i++)
1433 : : {
1434 : 649 : PartitionDispatch pd = proute->partition_dispatch_info[i];
1435 : :
1436 : 649 : table_close(pd->reldesc, NoLock);
1437 : :
1438 [ + + ]: 649 : if (pd->tupslot)
1439 : 306 : ExecDropSingleTupleTableSlot(pd->tupslot);
1440 : : }
1441 : :
1442 [ + + ]: 7262 : for (i = 0; i < proute->num_partitions; i++)
1443 : : {
1444 : 4362 : ResultRelInfo *resultRelInfo = proute->partitions[i];
1445 : :
1446 : : /* Allow any FDWs to shut down */
1447 [ + + ]: 4362 : if (resultRelInfo->ri_FdwRoutine != NULL &&
1448 [ + - ]: 34 : resultRelInfo->ri_FdwRoutine->EndForeignInsert != NULL)
1449 : 34 : resultRelInfo->ri_FdwRoutine->EndForeignInsert(mtstate->ps.state,
1450 : : resultRelInfo);
1451 : :
1452 : : /*
1453 : : * Close it if it's not one of the result relations borrowed from the
1454 : : * owning ModifyTableState; those will be closed by ExecEndPlan().
1455 : : */
1456 [ + + ]: 4362 : if (proute->is_borrowed_rel[i])
1457 : 388 : continue;
1458 : :
1459 : 3974 : ExecCloseIndices(resultRelInfo);
1460 : 3974 : table_close(resultRelInfo->ri_RelationDesc, NoLock);
1461 : : }
1462 : 2900 : }
1463 : :
1464 : : /* ----------------
1465 : : * FormPartitionKeyDatum
1466 : : * Construct values[] and isnull[] arrays for the partition key
1467 : : * of a tuple.
1468 : : *
1469 : : * pd Partition dispatch object of the partitioned table
1470 : : * slot Heap tuple from which to extract partition key
1471 : : * estate executor state for evaluating any partition key
1472 : : * expressions (must be non-NULL)
1473 : : * values Array of partition key Datums (output area)
1474 : : * isnull Array of is-null indicators (output area)
1475 : : *
1476 : : * the ecxt_scantuple slot of estate's per-tuple expr context must point to
1477 : : * the heap tuple passed in.
1478 : : * ----------------
1479 : : */
1480 : : static void
1481 : 706044 : FormPartitionKeyDatum(PartitionDispatch pd,
1482 : : TupleTableSlot *slot,
1483 : : EState *estate,
1484 : : Datum *values,
1485 : : bool *isnull)
1486 : : {
1487 : : ListCell *partexpr_item;
1488 : : int i;
1489 : :
1490 [ + + + + ]: 706044 : if (pd->key->partexprs != NIL && pd->keystate == NIL)
1491 : : {
1492 : : /* Check caller has set up context correctly */
1493 : : Assert(estate != NULL &&
1494 : : GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
1495 : :
1496 : : /* First time through, set up expression evaluation state */
1497 : 388 : pd->keystate = ExecPrepareExprList(pd->key->partexprs, estate);
1498 : : }
1499 : :
1500 : 706044 : partexpr_item = list_head(pd->keystate);
1501 [ + + ]: 1427296 : for (i = 0; i < pd->key->partnatts; i++)
1502 : : {
1503 : 721252 : AttrNumber keycol = pd->key->partattrs[i];
1504 : : Datum datum;
1505 : : bool isNull;
1506 : :
1507 [ + + ]: 721252 : if (keycol != 0)
1508 : : {
1509 : : /* Plain column; get the value directly from the heap tuple */
1510 : 662788 : datum = slot_getattr(slot, keycol, &isNull);
1511 : : }
1512 : : else
1513 : : {
1514 : : /* Expression; need to evaluate it */
1515 [ - + ]: 58464 : if (partexpr_item == NULL)
1516 [ # # ]: 0 : elog(ERROR, "wrong number of partition key expressions");
1517 : 58464 : datum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
1518 [ + - ]: 58464 : GetPerTupleExprContext(estate),
1519 : : &isNull);
1520 : 58464 : partexpr_item = lnext(pd->keystate, partexpr_item);
1521 : : }
1522 : 721252 : values[i] = datum;
1523 : 721252 : isnull[i] = isNull;
1524 : : }
1525 : :
1526 [ - + ]: 706044 : if (partexpr_item != NULL)
1527 [ # # ]: 0 : elog(ERROR, "wrong number of partition key expressions");
1528 : 706044 : }
1529 : :
1530 : : /*
1531 : : * The number of times the same partition must be found in a row before we
1532 : : * switch from a binary search for the given values to just checking if the
1533 : : * values belong to the last found partition. This must be above 0.
1534 : : */
1535 : : #define PARTITION_CACHED_FIND_THRESHOLD 16
1536 : :
1537 : : /*
1538 : : * get_partition_for_tuple
1539 : : * Finds partition of relation which accepts the partition key specified
1540 : : * in 'values' and 'isnull'.
1541 : : *
1542 : : * Calling this function can be quite expensive when LIST and RANGE
1543 : : * partitioned tables have many partitions. This is due to the binary search
1544 : : * that's done to find the correct partition. Many of the use cases for LIST
1545 : : * and RANGE partitioned tables make it likely that the same partition is
1546 : : * found in subsequent ExecFindPartition() calls. This is especially true for
1547 : : * cases such as RANGE partitioned tables on a TIMESTAMP column where the
1548 : : * partition key is the current time. When asked to find a partition for a
1549 : : * RANGE or LIST partitioned table, we store the datums index into the
1550 : : * PartitionDesc for the given 'values'. The datums index is the index into
1551 : : * the PartitionBoundInfo.datums array. On subsequent calls to
1552 : : * ExecFindPartition, if we keep finding the same datums index
1553 : : * PARTITION_CACHED_FIND_THRESHOLD times in a row, then we'll enable caching
1554 : : * logic, and instead of performing a binary search to find the correct
1555 : : * partition, we'll just double-check that the given 'values' match the datums
1556 : : * for the cached datums index in the PartitionBoundInfo.datums array, and if
1557 : : * so, we'll return the partition index for that element, thus skipping the
1558 : : * need for the binary search. If the current 'values' don't match, then we
1559 : : * fall back on doing a binary search. In this case, unless we find 'values'
1560 : : * belong to the DEFAULT partition, we'll reset the number of times we've hit
1561 : : * the same datums index so that we don't attempt to use the cache again until
1562 : : * we've found the same datums index at least PARTITION_CACHED_FIND_THRESHOLD
1563 : : * times in a row. This prevents us from doing a lot of unnecessary
1564 : : * comparisons when the datums index changes frequently.
1565 : : *
1566 : : * For cases where the datums index changes on each lookup, the amount of
1567 : : * additional work required just amounts to recording the last found datums
1568 : : * index, then resetting the found counter. This is cheap and does not appear
1569 : : * to cause any meaningful slowdowns.
1570 : : *
1571 : : * No caching of partitions is done when the last found partition is the
1572 : : * DEFAULT or NULL partition. For the case of the DEFAULT partition, there
1573 : : * is no datums index to cache for the lookup values, so we cannot confirm
1574 : : * the indexes match. For the NULL partition, this is just so cheap that
1575 : : * there's no sense in caching.
1576 : : *
1577 : : * Return value is index of the partition (>= 0 and < partdesc->nparts) if one
1578 : : * found or -1 if none found.
1579 : : */
1580 : : static int
1581 : 706016 : get_partition_for_tuple(PartitionDispatch pd, const Datum *values, const bool *isnull)
1582 : : {
1583 : 706016 : int bound_offset = -1;
1584 : 706016 : int part_index = -1;
1585 : 706016 : PartitionKey key = pd->key;
1586 : 706016 : PartitionDesc partdesc = pd->partdesc;
1587 : 706016 : PartitionBoundInfo boundinfo = partdesc->boundinfo;
1588 : :
1589 : : /*
1590 : : * In the switch statement below, when we perform a cached lookup for
1591 : : * RANGE and LIST partitioned tables, if we find that PartitionBoundInfo's
1592 : : * datums at the last found datums index match 'values', we return the
1593 : : * partition index of the cached datums index right away. We do this
1594 : : * instead of breaking out of the switch, as we don't want to execute the
1595 : : * code about the DEFAULT partition or do any updates to any of the
1596 : : * cache-related fields. That would be a waste of effort, as we already
1597 : : * know it's not the DEFAULT partition and have no need to increment the
1598 : : * number of times we found the same partition any higher than
1599 : : * PARTITION_CACHED_FIND_THRESHOLD.
1600 : : */
1601 : :
1602 : : /* Route as appropriate based on partitioning strategy. */
1603 [ + + + - ]: 706016 : switch (key->strategy)
1604 : : {
1605 : 106093 : case PARTITION_STRATEGY_HASH:
1606 : : {
1607 : : uint64 rowHash;
1608 : :
1609 : : /* hash partitioning is too cheap to bother caching */
1610 : 106093 : rowHash = compute_partition_hash_value(key->partnatts,
1611 : : key->partsupfunc,
1612 : 106093 : key->partcollation,
1613 : : values, isnull);
1614 : :
1615 : : /*
1616 : : * HASH partitions can't have a DEFAULT partition and we don't
1617 : : * do any caching work for them, so just return the part index
1618 : : */
1619 : 106085 : return boundinfo->indexes[rowHash % boundinfo->nindexes];
1620 : : }
1621 : :
1622 : 113755 : case PARTITION_STRATEGY_LIST:
1623 [ + + ]: 113755 : if (isnull[0])
1624 : : {
1625 : : /* this is far too cheap to bother doing any caching */
1626 [ + + ]: 96 : if (partition_bound_accepts_nulls(boundinfo))
1627 : : {
1628 : : /*
1629 : : * When there is a NULL partition we just return that
1630 : : * directly. We don't have a bound_offset so it's not
1631 : : * valid to drop into the code after the switch which
1632 : : * checks and updates the cache fields. We perhaps should
1633 : : * be invalidating the details of the last cached
1634 : : * partition but there's no real need to. Keeping those
1635 : : * fields set gives a chance at matching to the cached
1636 : : * datums index on the next lookup.
1637 : : */
1638 : 72 : return boundinfo->null_index;
1639 : : }
1640 : : }
1641 : : else
1642 : : {
1643 : : bool equal;
1644 : :
1645 [ + + ]: 113659 : if (partdesc->last_found_count >= PARTITION_CACHED_FIND_THRESHOLD)
1646 : : {
1647 : 15600 : int last_datum_offset = partdesc->last_found_datum_index;
1648 : 15600 : Datum lastDatum = boundinfo->datums[last_datum_offset][0];
1649 : : int32 cmpval;
1650 : :
1651 : : /* does the last found datum index match this datum? */
1652 : 15600 : cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
1653 : 15600 : key->partcollation[0],
1654 : : lastDatum,
1655 : : values[0]));
1656 : :
1657 [ + + ]: 15600 : if (cmpval == 0)
1658 : 15364 : return boundinfo->indexes[last_datum_offset];
1659 : :
1660 : : /* fall-through and do a manual lookup */
1661 : : }
1662 : :
1663 : 98295 : bound_offset = partition_list_bsearch(key->partsupfunc,
1664 : : key->partcollation,
1665 : : boundinfo,
1666 : : values[0], &equal);
1667 [ + + + + ]: 98295 : if (bound_offset >= 0 && equal)
1668 : 98034 : part_index = boundinfo->indexes[bound_offset];
1669 : : }
1670 : 98319 : break;
1671 : :
1672 : 486168 : case PARTITION_STRATEGY_RANGE:
1673 : : {
1674 : 486168 : bool equal = false,
1675 : 486168 : range_partkey_has_null = false;
1676 : : int i;
1677 : :
1678 : : /*
1679 : : * No range includes NULL, so this will be accepted by the
1680 : : * default partition if there is one, and otherwise rejected.
1681 : : */
1682 [ + + ]: 987264 : for (i = 0; i < key->partnatts; i++)
1683 : : {
1684 [ + + ]: 501132 : if (isnull[i])
1685 : : {
1686 : 36 : range_partkey_has_null = true;
1687 : 36 : break;
1688 : : }
1689 : : }
1690 : :
1691 : : /* NULLs belong in the DEFAULT partition */
1692 [ + + ]: 486168 : if (range_partkey_has_null)
1693 : 36 : break;
1694 : :
1695 [ + + ]: 486132 : if (partdesc->last_found_count >= PARTITION_CACHED_FIND_THRESHOLD)
1696 : : {
1697 : 141448 : int last_datum_offset = partdesc->last_found_datum_index;
1698 : 141448 : Datum *lastDatums = boundinfo->datums[last_datum_offset];
1699 : 141448 : PartitionRangeDatumKind *kind = boundinfo->kind[last_datum_offset];
1700 : : int32 cmpval;
1701 : :
1702 : : /* check if the value is >= to the lower bound */
1703 : 141448 : cmpval = partition_rbound_datum_cmp(key->partsupfunc,
1704 : : key->partcollation,
1705 : : lastDatums,
1706 : : kind,
1707 : : values,
1708 : 141448 : key->partnatts);
1709 : :
1710 : : /*
1711 : : * If it's equal to the lower bound then no need to check
1712 : : * the upper bound.
1713 : : */
1714 [ + + ]: 141448 : if (cmpval == 0)
1715 : 141235 : return boundinfo->indexes[last_datum_offset + 1];
1716 : :
1717 [ + + + - ]: 137516 : if (cmpval < 0 && last_datum_offset + 1 < boundinfo->ndatums)
1718 : : {
1719 : : /* check if the value is below the upper bound */
1720 : 137476 : lastDatums = boundinfo->datums[last_datum_offset + 1];
1721 : 137476 : kind = boundinfo->kind[last_datum_offset + 1];
1722 : 137476 : cmpval = partition_rbound_datum_cmp(key->partsupfunc,
1723 : : key->partcollation,
1724 : : lastDatums,
1725 : : kind,
1726 : : values,
1727 : 137476 : key->partnatts);
1728 : :
1729 [ + + ]: 137476 : if (cmpval > 0)
1730 : 137303 : return boundinfo->indexes[last_datum_offset + 1];
1731 : : }
1732 : : /* fall-through and do a manual lookup */
1733 : : }
1734 : :
1735 : 344897 : bound_offset = partition_range_datum_bsearch(key->partsupfunc,
1736 : : key->partcollation,
1737 : : boundinfo,
1738 : 344897 : key->partnatts,
1739 : : values,
1740 : : &equal);
1741 : :
1742 : : /*
1743 : : * The bound at bound_offset is less than or equal to the
1744 : : * tuple value, so the bound at offset+1 is the upper bound of
1745 : : * the partition we're looking for, if there actually exists
1746 : : * one.
1747 : : */
1748 : 344897 : part_index = boundinfo->indexes[bound_offset + 1];
1749 : : }
1750 : 344897 : break;
1751 : :
1752 : 0 : default:
1753 [ # # ]: 0 : elog(ERROR, "unexpected partition strategy: %d",
1754 : : (int) key->strategy);
1755 : : }
1756 : :
1757 : : /*
1758 : : * part_index < 0 means we failed to find a partition of this parent. Use
1759 : : * the default partition, if there is one.
1760 : : */
1761 [ + + ]: 443252 : if (part_index < 0)
1762 : : {
1763 : : /*
1764 : : * No need to reset the cache fields here. The next set of values
1765 : : * might end up belonging to the cached partition, so leaving the
1766 : : * cache alone improves the chances of a cache hit on the next lookup.
1767 : : */
1768 : 475 : return boundinfo->default_index;
1769 : : }
1770 : :
1771 : : /* we should only make it here when the code above set bound_offset */
1772 : : Assert(bound_offset >= 0);
1773 : :
1774 : : /*
1775 : : * Attend to the cache fields. If the bound_offset matches the last
1776 : : * cached bound offset then we've found the same partition as last time,
1777 : : * so bump the count by one. If all goes well, we'll eventually reach
1778 : : * PARTITION_CACHED_FIND_THRESHOLD and try the cache path next time
1779 : : * around. Otherwise, we'll reset the cache count back to 1 to mark that
1780 : : * we've landed on this datums index for the first time.
1781 : : */
1782 [ + + ]: 442777 : if (bound_offset == partdesc->last_found_datum_index)
1783 : 307748 : partdesc->last_found_count++;
1784 : : else
1785 : : {
1786 : 135029 : partdesc->last_found_count = 1;
1787 : 135029 : partdesc->last_found_datum_index = bound_offset;
1788 : : }
1789 : :
1790 : 442777 : return part_index;
1791 : : }
1792 : :
1793 : : /*
1794 : : * ExecBuildSlotPartitionKeyDescription
1795 : : *
1796 : : * This works very much like BuildIndexValueDescription() and is currently
1797 : : * used for building error messages when ExecFindPartition() fails to find
1798 : : * partition for a row.
1799 : : */
1800 : : static char *
1801 : 102 : ExecBuildSlotPartitionKeyDescription(Relation rel,
1802 : : const Datum *values,
1803 : : const bool *isnull,
1804 : : int maxfieldlen)
1805 : : {
1806 : : StringInfoData buf;
1807 : 102 : PartitionKey key = RelationGetPartitionKey(rel);
1808 : 102 : int partnatts = get_partition_natts(key);
1809 : : int i;
1810 : 102 : Oid relid = RelationGetRelid(rel);
1811 : : AclResult aclresult;
1812 : :
1813 [ - + ]: 102 : if (check_enable_rls(relid, InvalidOid, true) == RLS_ENABLED)
1814 : 0 : return NULL;
1815 : :
1816 : : /* If the user has table-level access, just go build the description. */
1817 : 102 : aclresult = pg_class_aclcheck(relid, GetUserId(), ACL_SELECT);
1818 [ + + ]: 102 : if (aclresult != ACLCHECK_OK)
1819 : : {
1820 : : /*
1821 : : * Step through the columns of the partition key and make sure the
1822 : : * user has SELECT rights on all of them.
1823 : : */
1824 [ + + ]: 16 : for (i = 0; i < partnatts; i++)
1825 : : {
1826 : 12 : AttrNumber attnum = get_partition_col_attnum(key, i);
1827 : :
1828 : : /*
1829 : : * If this partition key column is an expression, we return no
1830 : : * detail rather than try to figure out what column(s) the
1831 : : * expression includes and if the user has SELECT rights on them.
1832 : : */
1833 [ + + + + ]: 20 : if (attnum == InvalidAttrNumber ||
1834 : 8 : pg_attribute_aclcheck(relid, attnum, GetUserId(),
1835 : : ACL_SELECT) != ACLCHECK_OK)
1836 : 8 : return NULL;
1837 : : }
1838 : : }
1839 : :
1840 : 94 : initStringInfo(&buf);
1841 : 94 : appendStringInfo(&buf, "(%s) = (",
1842 : : pg_get_partkeydef_columns(relid, true));
1843 : :
1844 [ + + ]: 224 : for (i = 0; i < partnatts; i++)
1845 : : {
1846 : : char *val;
1847 : : int vallen;
1848 : :
1849 [ + + ]: 130 : if (isnull[i])
1850 : 20 : val = "null";
1851 : : else
1852 : : {
1853 : : Oid foutoid;
1854 : : bool typisvarlena;
1855 : :
1856 : 110 : getTypeOutputInfo(get_partition_col_typid(key, i),
1857 : : &foutoid, &typisvarlena);
1858 : 110 : val = OidOutputFunctionCall(foutoid, values[i]);
1859 : : }
1860 : :
1861 [ + + ]: 130 : if (i > 0)
1862 : 36 : appendStringInfoString(&buf, ", ");
1863 : :
1864 : : /* truncate if needed */
1865 : 130 : vallen = strlen(val);
1866 [ + - ]: 130 : if (vallen <= maxfieldlen)
1867 : 130 : appendBinaryStringInfo(&buf, val, vallen);
1868 : : else
1869 : : {
1870 : 0 : vallen = pg_mbcliplen(val, vallen, maxfieldlen);
1871 : 0 : appendBinaryStringInfo(&buf, val, vallen);
1872 : 0 : appendStringInfoString(&buf, "...");
1873 : : }
1874 : : }
1875 : :
1876 : 94 : appendStringInfoChar(&buf, ')');
1877 : :
1878 : 94 : return buf.data;
1879 : : }
1880 : :
1881 : : /*
1882 : : * adjust_partition_colnos
1883 : : * Adjust the list of UPDATE target column numbers to account for
1884 : : * attribute differences between the parent and the partition.
1885 : : *
1886 : : * Note: mustn't be called if no adjustment is required.
1887 : : */
1888 : : static List *
1889 : 50 : adjust_partition_colnos(List *colnos, ResultRelInfo *leaf_part_rri)
1890 : : {
1891 : 50 : TupleConversionMap *map = ExecGetChildToRootMap(leaf_part_rri);
1892 : :
1893 : : Assert(map != NULL);
1894 : :
1895 : 50 : return adjust_partition_colnos_using_map(colnos, map->attrMap);
1896 : : }
1897 : :
1898 : : /*
1899 : : * adjust_partition_colnos_using_map
1900 : : * Like adjust_partition_colnos, but uses a caller-supplied map instead
1901 : : * of assuming to map from the "root" result relation.
1902 : : *
1903 : : * Note: mustn't be called if no adjustment is required.
1904 : : */
1905 : : static List *
1906 : 61 : adjust_partition_colnos_using_map(List *colnos, AttrMap *attrMap)
1907 : : {
1908 : 61 : List *new_colnos = NIL;
1909 : : ListCell *lc;
1910 : :
1911 : : Assert(attrMap != NULL); /* else we shouldn't be here */
1912 : :
1913 [ + - + + : 150 : foreach(lc, colnos)
+ + ]
1914 : : {
1915 : 89 : AttrNumber parentattrno = lfirst_int(lc);
1916 : :
1917 [ + - ]: 89 : if (parentattrno <= 0 ||
1918 [ + - ]: 89 : parentattrno > attrMap->maplen ||
1919 [ - + ]: 89 : attrMap->attnums[parentattrno - 1] == 0)
1920 [ # # ]: 0 : elog(ERROR, "unexpected attno %d in target column list",
1921 : : parentattrno);
1922 : 89 : new_colnos = lappend_int(new_colnos,
1923 : 89 : attrMap->attnums[parentattrno - 1]);
1924 : : }
1925 : :
1926 : 61 : return new_colnos;
1927 : : }
1928 : :
1929 : : /*-------------------------------------------------------------------------
1930 : : * Run-Time Partition Pruning Support.
1931 : : *
1932 : : * The following series of functions exist to support the removal of unneeded
1933 : : * subplans for queries against partitioned tables. The supporting functions
1934 : : * here are designed to work with any plan type which supports an arbitrary
1935 : : * number of subplans, e.g. Append, MergeAppend.
1936 : : *
1937 : : * When pruning involves comparison of a partition key to a constant, it's
1938 : : * done by the planner. However, if we have a comparison to a non-constant
1939 : : * but not volatile expression, that presents an opportunity for run-time
1940 : : * pruning by the executor, allowing irrelevant partitions to be skipped
1941 : : * dynamically.
1942 : : *
1943 : : * We must distinguish expressions containing PARAM_EXEC Params from
1944 : : * expressions that don't contain those. Even though a PARAM_EXEC Param is
1945 : : * considered to be a stable expression, it can change value from one plan
1946 : : * node scan to the next during query execution. Stable comparison
1947 : : * expressions that don't involve such Params allow partition pruning to be
1948 : : * done once during executor startup. Expressions that do involve such Params
1949 : : * require us to prune separately for each scan of the parent plan node.
1950 : : *
1951 : : * Note that pruning away unneeded subplans during executor startup has the
1952 : : * added benefit of not having to initialize the unneeded subplans at all.
1953 : : *
1954 : : *
1955 : : * Functions:
1956 : : *
1957 : : * ExecDoInitialPruning:
1958 : : * Perform runtime "initial" pruning, if necessary, to determine the set
1959 : : * of child subnodes that need to be initialized during ExecInitNode() for
1960 : : * all plan nodes that contain a PartitionPruneInfo.
1961 : : *
1962 : : * ExecInitPartitionExecPruning:
1963 : : * Updates the PartitionPruneState found at given part_prune_index in
1964 : : * EState.es_part_prune_states for use during "exec" pruning if required.
1965 : : * Also returns the set of subplans to initialize that would be stored at
1966 : : * part_prune_index in EState.es_part_prune_results by
1967 : : * ExecDoInitialPruning(). Maps in PartitionPruneState are updated to
1968 : : * account for initial pruning possibly having eliminated some of the
1969 : : * subplans.
1970 : : *
1971 : : * ExecFindMatchingSubPlans:
1972 : : * Returns indexes of matching subplans after evaluating the expressions
1973 : : * that are safe to evaluate at a given point. This function is first
1974 : : * called during ExecDoInitialPruning() to find the initially matching
1975 : : * subplans based on performing the initial pruning steps and then must be
1976 : : * called again each time the value of a Param listed in
1977 : : * PartitionPruneState's 'execparamids' changes.
1978 : : *-------------------------------------------------------------------------
1979 : : */
1980 : :
1981 : :
1982 : : /*
1983 : : * ExecDoInitialPruning
1984 : : * Perform runtime "initial" pruning, if necessary, to determine the set
1985 : : * of child subnodes that need to be initialized during ExecInitNode() for
1986 : : * plan nodes that support partition pruning.
1987 : : *
1988 : : * This function iterates over each PartitionPruneInfo entry in
1989 : : * estate->es_part_prune_infos. For each entry, it creates a PartitionPruneState
1990 : : * and adds it to es_part_prune_states. ExecInitPartitionExecPruning() accesses
1991 : : * these states through their corresponding indexes in es_part_prune_states and
1992 : : * assign each state to the parent node's PlanState, from where it will be used
1993 : : * for "exec" pruning.
1994 : : *
1995 : : * If initial pruning steps exist for a PartitionPruneInfo entry, this function
1996 : : * executes those pruning steps and stores the result as a bitmapset of valid
1997 : : * child subplans, identifying which subplans should be initialized for
1998 : : * execution. The results are saved in estate->es_part_prune_results.
1999 : : *
2000 : : * If no initial pruning is performed for a given PartitionPruneInfo, a NULL
2001 : : * entry is still added to es_part_prune_results to maintain alignment with
2002 : : * es_part_prune_infos. This ensures that ExecInitPartitionExecPruning() can
2003 : : * use the same index to retrieve the pruning results.
2004 : : */
2005 : : void
2006 : 369564 : ExecDoInitialPruning(EState *estate)
2007 : : {
2008 : : ListCell *lc;
2009 : :
2010 [ + + + + : 370104 : foreach(lc, estate->es_part_prune_infos)
+ + ]
2011 : : {
2012 : 540 : PartitionPruneInfo *pruneinfo = lfirst_node(PartitionPruneInfo, lc);
2013 : : PartitionPruneState *prunestate;
2014 : 540 : Bitmapset *validsubplans = NULL;
2015 : 540 : Bitmapset *all_leafpart_rtis = NULL;
2016 : 540 : Bitmapset *validsubplan_rtis = NULL;
2017 : :
2018 : : /* Create and save the PartitionPruneState. */
2019 : 540 : prunestate = CreatePartitionPruneState(estate, pruneinfo,
2020 : : &all_leafpart_rtis);
2021 : 540 : estate->es_part_prune_states = lappend(estate->es_part_prune_states,
2022 : : prunestate);
2023 : :
2024 : : /*
2025 : : * Perform initial pruning steps, if any, and save the result
2026 : : * bitmapset or NULL as described in the header comment.
2027 : : */
2028 [ + + ]: 540 : if (prunestate->do_initial_prune)
2029 : 301 : validsubplans = ExecFindMatchingSubPlans(prunestate, true,
2030 : : &validsubplan_rtis);
2031 : : else
2032 : 239 : validsubplan_rtis = all_leafpart_rtis;
2033 : :
2034 : 540 : estate->es_unpruned_relids = bms_add_members(estate->es_unpruned_relids,
2035 : : validsubplan_rtis);
2036 : 540 : estate->es_part_prune_results = lappend(estate->es_part_prune_results,
2037 : : validsubplans);
2038 : : }
2039 : 369564 : }
2040 : :
2041 : : /*
2042 : : * ExecInitPartitionExecPruning
2043 : : * Initialize the data structures needed for runtime "exec" partition
2044 : : * pruning and return the result of initial pruning, if available.
2045 : : *
2046 : : * 'relids' identifies the relation to which both the parent plan and the
2047 : : * PartitionPruneInfo given by 'part_prune_index' belong.
2048 : : *
2049 : : * On return, *initially_valid_subplans is assigned the set of indexes of
2050 : : * child subplans that must be initialized along with the parent plan node.
2051 : : * Initial pruning would have been performed by ExecDoInitialPruning(), if
2052 : : * necessary, and the bitmapset of surviving subplans' indexes would have
2053 : : * been stored as the part_prune_index'th element of
2054 : : * EState.es_part_prune_results.
2055 : : *
2056 : : * If subplans were indeed pruned during initial pruning, the subplan_map
2057 : : * arrays in the returned PartitionPruneState are re-sequenced to exclude those
2058 : : * subplans, but only if the maps will be needed for subsequent execution
2059 : : * pruning passes.
2060 : : */
2061 : : PartitionPruneState *
2062 : 542 : ExecInitPartitionExecPruning(PlanState *planstate,
2063 : : int n_total_subplans,
2064 : : int part_prune_index,
2065 : : Bitmapset *relids,
2066 : : Bitmapset **initially_valid_subplans)
2067 : : {
2068 : : PartitionPruneState *prunestate;
2069 : 542 : EState *estate = planstate->state;
2070 : : PartitionPruneInfo *pruneinfo;
2071 : :
2072 : : /* Obtain the pruneinfo we need. */
2073 : 542 : pruneinfo = list_nth_node(PartitionPruneInfo, estate->es_part_prune_infos,
2074 : : part_prune_index);
2075 : :
2076 : : /* Its relids better match the plan node's or the planner messed up. */
2077 [ - + ]: 542 : if (!bms_equal(relids, pruneinfo->relids))
2078 [ # # ]: 0 : elog(ERROR, "wrong pruneinfo with relids=%s found at part_prune_index=%d contained in plan node with relids=%s",
2079 : : bmsToString(pruneinfo->relids), part_prune_index,
2080 : : bmsToString(relids));
2081 : :
2082 : : /*
2083 : : * The PartitionPruneState would have been created by
2084 : : * ExecDoInitialPruning() and stored as the part_prune_index'th element of
2085 : : * EState.es_part_prune_states.
2086 : : */
2087 : 542 : prunestate = list_nth(estate->es_part_prune_states, part_prune_index);
2088 : : Assert(prunestate != NULL);
2089 : :
2090 : : /* Use the result of initial pruning done by ExecDoInitialPruning(). */
2091 [ + + ]: 542 : if (prunestate->do_initial_prune)
2092 : 302 : *initially_valid_subplans = list_nth_node(Bitmapset,
2093 : : estate->es_part_prune_results,
2094 : : part_prune_index);
2095 : : else
2096 : : {
2097 : : /* No pruning, so we'll need to initialize all subplans */
2098 : : Assert(n_total_subplans > 0);
2099 : 240 : *initially_valid_subplans = bms_add_range(NULL, 0,
2100 : : n_total_subplans - 1);
2101 : : }
2102 : :
2103 : : /*
2104 : : * The exec pruning state must also be initialized, if needed, before it
2105 : : * can be used for pruning during execution.
2106 : : *
2107 : : * This also re-sequences subplan indexes contained in prunestate to
2108 : : * account for any that were removed due to initial pruning; refer to the
2109 : : * condition in InitExecPartitionPruneContexts() that is used to determine
2110 : : * whether to do this. If no exec pruning needs to be done, we would thus
2111 : : * leave the maps to be in an invalid state, but that's ok since that data
2112 : : * won't be consulted again (cf initial Assert in
2113 : : * ExecFindMatchingSubPlans).
2114 : : */
2115 [ + + ]: 542 : if (prunestate->do_exec_prune)
2116 : 268 : InitExecPartitionPruneContexts(prunestate, planstate,
2117 : : *initially_valid_subplans,
2118 : : n_total_subplans);
2119 : :
2120 : 542 : return prunestate;
2121 : : }
2122 : :
2123 : : /*
2124 : : * CreatePartitionPruneState
2125 : : * Build the data structure required for calling ExecFindMatchingSubPlans
2126 : : *
2127 : : * This includes PartitionPruneContexts (stored in each
2128 : : * PartitionedRelPruningData corresponding to a PartitionedRelPruneInfo),
2129 : : * which hold the ExprStates needed to evaluate pruning expressions, and
2130 : : * mapping arrays to convert partition indexes from the pruning logic
2131 : : * into subplan indexes in the parent plan node's list of child subplans.
2132 : : *
2133 : : * 'pruneinfo' is a PartitionPruneInfo as generated by
2134 : : * make_partition_pruneinfo. Here we build a PartitionPruneState containing a
2135 : : * PartitionPruningData for each partitioning hierarchy (i.e., each sublist of
2136 : : * pruneinfo->prune_infos), each of which contains a PartitionedRelPruningData
2137 : : * for each PartitionedRelPruneInfo appearing in that sublist. This two-level
2138 : : * system is needed to keep from confusing the different hierarchies when a
2139 : : * UNION ALL contains multiple partitioned tables as children. The data
2140 : : * stored in each PartitionedRelPruningData can be re-used each time we
2141 : : * re-evaluate which partitions match the pruning steps provided in each
2142 : : * PartitionedRelPruneInfo.
2143 : : *
2144 : : * Note that only the PartitionPruneContexts for initial pruning are
2145 : : * initialized here. Those required for exec pruning are initialized later in
2146 : : * ExecInitPartitionExecPruning(), as they depend on the availability of the
2147 : : * parent plan node's PlanState.
2148 : : *
2149 : : * If initial pruning steps are to be skipped (e.g., during EXPLAIN
2150 : : * (GENERIC_PLAN)), *all_leafpart_rtis will be populated with the RT indexes of
2151 : : * all leaf partitions whose scanning subnode is included in the parent plan
2152 : : * node's list of child plans. The caller must add these RT indexes to
2153 : : * estate->es_unpruned_relids.
2154 : : */
2155 : : static PartitionPruneState *
2156 : 540 : CreatePartitionPruneState(EState *estate, PartitionPruneInfo *pruneinfo,
2157 : : Bitmapset **all_leafpart_rtis)
2158 : : {
2159 : : PartitionPruneState *prunestate;
2160 : : int n_part_hierarchies;
2161 : : ListCell *lc;
2162 : : int i;
2163 : :
2164 : : /*
2165 : : * Expression context that will be used by partkey_datum_from_expr() to
2166 : : * evaluate expressions for comparison against partition bounds.
2167 : : */
2168 : 540 : ExprContext *econtext = CreateExprContext(estate);
2169 : :
2170 : : /* For data reading, executor always includes detached partitions */
2171 [ + + ]: 540 : if (estate->es_partition_directory == NULL)
2172 : 508 : estate->es_partition_directory =
2173 : 508 : CreatePartitionDirectory(estate->es_query_cxt, false);
2174 : :
2175 : 540 : n_part_hierarchies = list_length(pruneinfo->prune_infos);
2176 : : Assert(n_part_hierarchies > 0);
2177 : :
2178 : : /*
2179 : : * Allocate the data structure
2180 : : */
2181 : : prunestate = (PartitionPruneState *)
2182 : 540 : palloc(offsetof(PartitionPruneState, partprunedata) +
2183 : : sizeof(PartitionPruningData *) * n_part_hierarchies);
2184 : :
2185 : : /* Save ExprContext for use during InitExecPartitionPruneContexts(). */
2186 : 540 : prunestate->econtext = econtext;
2187 : 540 : prunestate->execparamids = NULL;
2188 : : /* other_subplans can change at runtime, so we need our own copy */
2189 : 540 : prunestate->other_subplans = bms_copy(pruneinfo->other_subplans);
2190 : 540 : prunestate->do_initial_prune = false; /* may be set below */
2191 : 540 : prunestate->do_exec_prune = false; /* may be set below */
2192 : 540 : prunestate->num_partprunedata = n_part_hierarchies;
2193 : :
2194 : : /*
2195 : : * Create a short-term memory context which we'll use when making calls to
2196 : : * the partition pruning functions. This avoids possible memory leaks,
2197 : : * since the pruning functions call comparison functions that aren't under
2198 : : * our control.
2199 : : */
2200 : 540 : prunestate->prune_context =
2201 : 540 : AllocSetContextCreate(CurrentMemoryContext,
2202 : : "Partition Prune",
2203 : : ALLOCSET_DEFAULT_SIZES);
2204 : :
2205 : 540 : i = 0;
2206 [ + - + + : 1096 : foreach(lc, pruneinfo->prune_infos)
+ + ]
2207 : : {
2208 : 556 : List *partrelpruneinfos = lfirst_node(List, lc);
2209 : 556 : int npartrelpruneinfos = list_length(partrelpruneinfos);
2210 : : PartitionPruningData *prunedata;
2211 : : ListCell *lc2;
2212 : : int j;
2213 : :
2214 : : prunedata = (PartitionPruningData *)
2215 : 556 : palloc(offsetof(PartitionPruningData, partrelprunedata) +
2216 : 556 : npartrelpruneinfos * sizeof(PartitionedRelPruningData));
2217 : 556 : prunestate->partprunedata[i] = prunedata;
2218 : 556 : prunedata->num_partrelprunedata = npartrelpruneinfos;
2219 : :
2220 : 556 : j = 0;
2221 [ + - + + : 1652 : foreach(lc2, partrelpruneinfos)
+ + ]
2222 : : {
2223 : 1096 : PartitionedRelPruneInfo *pinfo = lfirst_node(PartitionedRelPruneInfo, lc2);
2224 : 1096 : PartitionedRelPruningData *pprune = &prunedata->partrelprunedata[j];
2225 : : Relation partrel;
2226 : : PartitionDesc partdesc;
2227 : : PartitionKey partkey;
2228 : :
2229 : : /*
2230 : : * We can rely on the copies of the partitioned table's partition
2231 : : * key and partition descriptor appearing in its relcache entry,
2232 : : * because that entry will be held open and locked for the
2233 : : * duration of this executor run.
2234 : : */
2235 : 1096 : partrel = ExecGetRangeTableRelation(estate, pinfo->rtindex, false);
2236 : :
2237 : : /* Remember for InitExecPartitionPruneContexts(). */
2238 : 1096 : pprune->partrel = partrel;
2239 : :
2240 : 1096 : partkey = RelationGetPartitionKey(partrel);
2241 : 1096 : partdesc = PartitionDirectoryLookup(estate->es_partition_directory,
2242 : : partrel);
2243 : :
2244 : : /*
2245 : : * Initialize the subplan_map and subpart_map.
2246 : : *
2247 : : * The set of partitions that exist now might not be the same that
2248 : : * existed when the plan was made. The normal case is that it is;
2249 : : * optimize for that case with a quick comparison, and just copy
2250 : : * the subplan_map and make subpart_map, leafpart_rti_map point to
2251 : : * the ones in PruneInfo.
2252 : : *
2253 : : * For the case where they aren't identical, we could have more
2254 : : * partitions on either side; or even exactly the same number of
2255 : : * them on both but the set of OIDs doesn't match fully. Handle
2256 : : * this by creating new subplan_map and subpart_map arrays that
2257 : : * corresponds to the ones in the PruneInfo where the new
2258 : : * partition descriptor's OIDs match. Any that don't match can be
2259 : : * set to -1, as if they were pruned. By construction, both
2260 : : * arrays are in partition bounds order.
2261 : : */
2262 : 1096 : pprune->nparts = partdesc->nparts;
2263 : 1096 : pprune->subplan_map = palloc_array(int, partdesc->nparts);
2264 : :
2265 [ + + ]: 1096 : if (partdesc->nparts == pinfo->nparts &&
2266 : 1095 : memcmp(partdesc->oids, pinfo->relid_map,
2267 [ + + ]: 1095 : sizeof(int) * partdesc->nparts) == 0)
2268 : : {
2269 : 1015 : pprune->subpart_map = pinfo->subpart_map;
2270 : 1015 : pprune->leafpart_rti_map = pinfo->leafpart_rti_map;
2271 : 1015 : memcpy(pprune->subplan_map, pinfo->subplan_map,
2272 : 1015 : sizeof(int) * pinfo->nparts);
2273 : : }
2274 : : else
2275 : : {
2276 : 81 : int pd_idx = 0;
2277 : : int pp_idx;
2278 : :
2279 : : /*
2280 : : * When the partition arrays are not identical, there could be
2281 : : * some new ones but it's also possible that one was removed;
2282 : : * we cope with both situations by walking the arrays and
2283 : : * discarding those that don't match.
2284 : : *
2285 : : * If the number of partitions on both sides match, it's still
2286 : : * possible that one partition has been detached and another
2287 : : * attached. Cope with that by creating a map that skips any
2288 : : * mismatches.
2289 : : */
2290 : 81 : pprune->subpart_map = palloc_array(int, partdesc->nparts);
2291 : 81 : pprune->leafpart_rti_map = palloc_array(int, partdesc->nparts);
2292 : :
2293 [ + + ]: 345 : for (pp_idx = 0; pp_idx < partdesc->nparts; pp_idx++)
2294 : : {
2295 : : /* Skip any InvalidOid relid_map entries */
2296 [ + + ]: 409 : while (pd_idx < pinfo->nparts &&
2297 [ + + ]: 329 : !OidIsValid(pinfo->relid_map[pd_idx]))
2298 : 145 : pd_idx++;
2299 : :
2300 : 264 : recheck:
2301 [ + + ]: 264 : if (pd_idx < pinfo->nparts &&
2302 [ + + ]: 184 : pinfo->relid_map[pd_idx] == partdesc->oids[pp_idx])
2303 : : {
2304 : : /* match... */
2305 : 118 : pprune->subplan_map[pp_idx] =
2306 : 118 : pinfo->subplan_map[pd_idx];
2307 : 118 : pprune->subpart_map[pp_idx] =
2308 : 118 : pinfo->subpart_map[pd_idx];
2309 : 118 : pprune->leafpart_rti_map[pp_idx] =
2310 : 118 : pinfo->leafpart_rti_map[pd_idx];
2311 : 118 : pd_idx++;
2312 : 118 : continue;
2313 : : }
2314 : :
2315 : : /*
2316 : : * There isn't an exact match in the corresponding
2317 : : * positions of both arrays. Peek ahead in
2318 : : * pinfo->relid_map to see if we have a match for the
2319 : : * current partition in partdesc. Normally if a match
2320 : : * exists it's just one element ahead, and it means the
2321 : : * planner saw one extra partition that we no longer see
2322 : : * now (its concurrent detach finished just in between);
2323 : : * so we skip that one by updating pd_idx to the new
2324 : : * location and jumping above. We can then continue to
2325 : : * match the rest of the elements after skipping the OID
2326 : : * with no match; no future matches are tried for the
2327 : : * element that was skipped, because we know the arrays to
2328 : : * be in the same order.
2329 : : *
2330 : : * If we don't see a match anywhere in the rest of the
2331 : : * pinfo->relid_map array, that means we see an element
2332 : : * now that the planner didn't see, so mark that one as
2333 : : * pruned and move on.
2334 : : */
2335 [ + + ]: 188 : for (int pd_idx2 = pd_idx + 1; pd_idx2 < pinfo->nparts; pd_idx2++)
2336 : : {
2337 [ - + ]: 42 : if (pd_idx2 >= pinfo->nparts)
2338 : 0 : break;
2339 [ - + ]: 42 : if (pinfo->relid_map[pd_idx2] == partdesc->oids[pp_idx])
2340 : : {
2341 : 0 : pd_idx = pd_idx2;
2342 : 0 : goto recheck;
2343 : : }
2344 : : }
2345 : :
2346 : 146 : pprune->subpart_map[pp_idx] = -1;
2347 : 146 : pprune->subplan_map[pp_idx] = -1;
2348 : 146 : pprune->leafpart_rti_map[pp_idx] = 0;
2349 : : }
2350 : : }
2351 : :
2352 : : /* present_parts is also subject to later modification */
2353 : 1096 : pprune->present_parts = bms_copy(pinfo->present_parts);
2354 : :
2355 : : /*
2356 : : * Only initial_context is initialized here. exec_context is
2357 : : * initialized during ExecInitPartitionExecPruning() when the
2358 : : * parent plan's PlanState is available.
2359 : : *
2360 : : * Note that we must skip execution-time (both "init" and "exec")
2361 : : * partition pruning in EXPLAIN (GENERIC_PLAN), since parameter
2362 : : * values may be missing.
2363 : : */
2364 : 1096 : pprune->initial_pruning_steps = pinfo->initial_pruning_steps;
2365 [ + + ]: 1096 : if (pinfo->initial_pruning_steps &&
2366 [ + + ]: 373 : !(econtext->ecxt_estate->es_top_eflags & EXEC_FLAG_EXPLAIN_GENERIC))
2367 : : {
2368 : 369 : InitPartitionPruneContext(&pprune->initial_context,
2369 : : pprune->initial_pruning_steps,
2370 : : partdesc, partkey, NULL,
2371 : : econtext);
2372 : : /* Record whether initial pruning is needed at any level */
2373 : 369 : prunestate->do_initial_prune = true;
2374 : : }
2375 : 1096 : pprune->exec_pruning_steps = pinfo->exec_pruning_steps;
2376 [ + + ]: 1096 : if (pinfo->exec_pruning_steps &&
2377 [ + - ]: 343 : !(econtext->ecxt_estate->es_top_eflags & EXEC_FLAG_EXPLAIN_GENERIC))
2378 : : {
2379 : : /* Record whether exec pruning is needed at any level */
2380 : 343 : prunestate->do_exec_prune = true;
2381 : : }
2382 : :
2383 : : /*
2384 : : * Accumulate the IDs of all PARAM_EXEC Params affecting the
2385 : : * partitioning decisions at this plan node.
2386 : : */
2387 : 2192 : prunestate->execparamids = bms_add_members(prunestate->execparamids,
2388 : 1096 : pinfo->execparamids);
2389 : :
2390 : : /*
2391 : : * Return all leaf partition indexes if we're skipping pruning in
2392 : : * the EXPLAIN (GENERIC_PLAN) case.
2393 : : */
2394 [ + + + + ]: 1096 : if (pinfo->initial_pruning_steps && !prunestate->do_initial_prune)
2395 : : {
2396 : 4 : int part_index = -1;
2397 : :
2398 : 12 : while ((part_index = bms_next_member(pprune->present_parts,
2399 [ + + ]: 12 : part_index)) >= 0)
2400 : : {
2401 : 8 : Index rtindex = pprune->leafpart_rti_map[part_index];
2402 : :
2403 [ + - ]: 8 : if (rtindex)
2404 : 8 : *all_leafpart_rtis = bms_add_member(*all_leafpart_rtis,
2405 : : rtindex);
2406 : : }
2407 : : }
2408 : :
2409 : 1096 : j++;
2410 : : }
2411 : 556 : i++;
2412 : : }
2413 : :
2414 : 540 : return prunestate;
2415 : : }
2416 : :
2417 : : /*
2418 : : * Initialize a PartitionPruneContext for the given list of pruning steps.
2419 : : */
2420 : : static void
2421 : 713 : InitPartitionPruneContext(PartitionPruneContext *context,
2422 : : List *pruning_steps,
2423 : : PartitionDesc partdesc,
2424 : : PartitionKey partkey,
2425 : : PlanState *planstate,
2426 : : ExprContext *econtext)
2427 : : {
2428 : : int n_steps;
2429 : : int partnatts;
2430 : : ListCell *lc;
2431 : :
2432 : 713 : n_steps = list_length(pruning_steps);
2433 : :
2434 : 713 : context->strategy = partkey->strategy;
2435 : 713 : context->partnatts = partnatts = partkey->partnatts;
2436 : 713 : context->nparts = partdesc->nparts;
2437 : 713 : context->boundinfo = partdesc->boundinfo;
2438 : 713 : context->partcollation = partkey->partcollation;
2439 : 713 : context->partsupfunc = partkey->partsupfunc;
2440 : :
2441 : : /* We'll look up type-specific support functions as needed */
2442 : 713 : context->stepcmpfuncs = palloc0_array(FmgrInfo, n_steps * partnatts);
2443 : :
2444 : 713 : context->ppccontext = CurrentMemoryContext;
2445 : 713 : context->planstate = planstate;
2446 : 713 : context->exprcontext = econtext;
2447 : :
2448 : : /* Initialize expression state for each expression we need */
2449 : 713 : context->exprstates = palloc0_array(ExprState *, n_steps * partnatts);
2450 [ + - + + : 1872 : foreach(lc, pruning_steps)
+ + ]
2451 : : {
2452 : 1159 : PartitionPruneStepOp *step = (PartitionPruneStepOp *) lfirst(lc);
2453 : 1159 : ListCell *lc2 = list_head(step->exprs);
2454 : : int keyno;
2455 : :
2456 : : /* not needed for other step kinds */
2457 [ + + ]: 1159 : if (!IsA(step, PartitionPruneStepOp))
2458 : 193 : continue;
2459 : :
2460 : : Assert(list_length(step->exprs) <= partnatts);
2461 : :
2462 [ + + ]: 2032 : for (keyno = 0; keyno < partnatts; keyno++)
2463 : : {
2464 [ + + ]: 1066 : if (bms_is_member(keyno, step->nullkeys))
2465 : 4 : continue;
2466 : :
2467 [ + + ]: 1062 : if (lc2 != NULL)
2468 : : {
2469 : 998 : Expr *expr = lfirst(lc2);
2470 : :
2471 : : /* not needed for Consts */
2472 [ + + ]: 998 : if (!IsA(expr, Const))
2473 : : {
2474 : 933 : int stateidx = PruneCxtStateIdx(partnatts,
2475 : : step->step.step_id,
2476 : : keyno);
2477 : :
2478 : : /*
2479 : : * When planstate is NULL, pruning_steps is known not to
2480 : : * contain any expressions that depend on the parent plan.
2481 : : * Information of any available EXTERN parameters must be
2482 : : * passed explicitly in that case, which the caller must
2483 : : * have made available via econtext.
2484 : : */
2485 [ + + ]: 933 : if (planstate == NULL)
2486 : 545 : context->exprstates[stateidx] =
2487 : 545 : ExecInitExprWithParams(expr,
2488 : : econtext->ecxt_param_list_info);
2489 : : else
2490 : 388 : context->exprstates[stateidx] =
2491 : 388 : ExecInitExpr(expr, context->planstate);
2492 : : }
2493 : 998 : lc2 = lnext(step->exprs, lc2);
2494 : : }
2495 : : }
2496 : : }
2497 : 713 : }
2498 : :
2499 : : /*
2500 : : * InitExecPartitionPruneContexts
2501 : : * Initialize exec pruning contexts deferred by CreatePartitionPruneState()
2502 : : *
2503 : : * This function finalizes exec pruning setup for a PartitionPruneState by
2504 : : * initializing contexts for pruning steps that require the parent plan's
2505 : : * PlanState. It iterates over PartitionPruningData entries and sets up the
2506 : : * necessary execution contexts for pruning during query execution.
2507 : : *
2508 : : * Also fix the mapping of partition indexes to subplan indexes contained in
2509 : : * prunestate by considering the new list of subplans that survived initial
2510 : : * pruning.
2511 : : *
2512 : : * Current values of the indexes present in PartitionPruneState count all the
2513 : : * subplans that would be present before initial pruning was done. If initial
2514 : : * pruning got rid of some of the subplans, any subsequent pruning passes will
2515 : : * be looking at a different set of target subplans to choose from than those
2516 : : * in the pre-initial-pruning set, so the maps in PartitionPruneState
2517 : : * containing those indexes must be updated to reflect the new indexes of
2518 : : * subplans in the post-initial-pruning set.
2519 : : */
2520 : : static void
2521 : 268 : InitExecPartitionPruneContexts(PartitionPruneState *prunestate,
2522 : : PlanState *parent_plan,
2523 : : Bitmapset *initially_valid_subplans,
2524 : : int n_total_subplans)
2525 : : {
2526 : : EState *estate;
2527 : 268 : int *new_subplan_indexes = NULL;
2528 : : Bitmapset *new_other_subplans;
2529 : : int i;
2530 : : int newidx;
2531 : 268 : bool fix_subplan_map = false;
2532 : :
2533 : : Assert(prunestate->do_exec_prune);
2534 : : Assert(parent_plan != NULL);
2535 : 268 : estate = parent_plan->state;
2536 : :
2537 : : /*
2538 : : * No need to fix subplans maps if initial pruning didn't eliminate any
2539 : : * subplans.
2540 : : */
2541 [ + + ]: 268 : if (bms_num_members(initially_valid_subplans) < n_total_subplans)
2542 : : {
2543 : 32 : fix_subplan_map = true;
2544 : :
2545 : : /*
2546 : : * First we must build a temporary array which maps old subplan
2547 : : * indexes to new ones. For convenience of initialization, we use
2548 : : * 1-based indexes in this array and leave pruned items as 0.
2549 : : */
2550 : 32 : new_subplan_indexes = palloc0_array(int, n_total_subplans);
2551 : 32 : newidx = 1;
2552 : 32 : i = -1;
2553 [ + + ]: 124 : while ((i = bms_next_member(initially_valid_subplans, i)) >= 0)
2554 : : {
2555 : : Assert(i < n_total_subplans);
2556 : 92 : new_subplan_indexes[i] = newidx++;
2557 : : }
2558 : : }
2559 : :
2560 : : /*
2561 : : * Now we can update each PartitionedRelPruneInfo's subplan_map with new
2562 : : * subplan indexes. We must also recompute its present_parts bitmap.
2563 : : */
2564 [ + + ]: 552 : for (i = 0; i < prunestate->num_partprunedata; i++)
2565 : : {
2566 : 284 : PartitionPruningData *prunedata = prunestate->partprunedata[i];
2567 : : int j;
2568 : :
2569 : : /*
2570 : : * Within each hierarchy, we perform this loop in back-to-front order
2571 : : * so that we determine present_parts for the lowest-level partitioned
2572 : : * tables first. This way we can tell whether a sub-partitioned
2573 : : * table's partitions were entirely pruned so we can exclude it from
2574 : : * the current level's present_parts.
2575 : : */
2576 [ + + ]: 872 : for (j = prunedata->num_partrelprunedata - 1; j >= 0; j--)
2577 : : {
2578 : 588 : PartitionedRelPruningData *pprune = &prunedata->partrelprunedata[j];
2579 : 588 : int nparts = pprune->nparts;
2580 : : int k;
2581 : :
2582 : : /* Initialize PartitionPruneContext for exec pruning, if needed. */
2583 [ + + ]: 588 : if (pprune->exec_pruning_steps != NIL)
2584 : : {
2585 : : PartitionKey partkey;
2586 : : PartitionDesc partdesc;
2587 : :
2588 : : /*
2589 : : * See the comment in CreatePartitionPruneState() regarding
2590 : : * the usage of partdesc and partkey.
2591 : : */
2592 : 344 : partkey = RelationGetPartitionKey(pprune->partrel);
2593 : 344 : partdesc = PartitionDirectoryLookup(estate->es_partition_directory,
2594 : : pprune->partrel);
2595 : :
2596 : 344 : InitPartitionPruneContext(&pprune->exec_context,
2597 : : pprune->exec_pruning_steps,
2598 : : partdesc, partkey, parent_plan,
2599 : : prunestate->econtext);
2600 : : }
2601 : :
2602 [ + + ]: 588 : if (!fix_subplan_map)
2603 : 460 : continue;
2604 : :
2605 : : /* We just rebuild present_parts from scratch */
2606 : 128 : bms_free(pprune->present_parts);
2607 : 128 : pprune->present_parts = NULL;
2608 : :
2609 [ + + ]: 472 : for (k = 0; k < nparts; k++)
2610 : : {
2611 : 344 : int oldidx = pprune->subplan_map[k];
2612 : : int subidx;
2613 : :
2614 : : /*
2615 : : * If this partition existed as a subplan then change the old
2616 : : * subplan index to the new subplan index. The new index may
2617 : : * become -1 if the partition was pruned above, or it may just
2618 : : * come earlier in the subplan list due to some subplans being
2619 : : * removed earlier in the list. If it's a subpartition, add
2620 : : * it to present_parts unless it's entirely pruned.
2621 : : */
2622 [ + + ]: 344 : if (oldidx >= 0)
2623 : : {
2624 : : Assert(oldidx < n_total_subplans);
2625 : 264 : pprune->subplan_map[k] = new_subplan_indexes[oldidx] - 1;
2626 : :
2627 [ + + ]: 264 : if (new_subplan_indexes[oldidx] > 0)
2628 : 76 : pprune->present_parts =
2629 : 76 : bms_add_member(pprune->present_parts, k);
2630 : : }
2631 [ + - ]: 80 : else if ((subidx = pprune->subpart_map[k]) >= 0)
2632 : : {
2633 : : PartitionedRelPruningData *subprune;
2634 : :
2635 : 80 : subprune = &prunedata->partrelprunedata[subidx];
2636 : :
2637 [ + + ]: 80 : if (!bms_is_empty(subprune->present_parts))
2638 : 32 : pprune->present_parts =
2639 : 32 : bms_add_member(pprune->present_parts, k);
2640 : : }
2641 : : }
2642 : : }
2643 : : }
2644 : :
2645 : : /*
2646 : : * If we fixed subplan maps, we must also recompute the other_subplans
2647 : : * set, since indexes in it may change.
2648 : : */
2649 [ + + ]: 268 : if (fix_subplan_map)
2650 : : {
2651 : 32 : new_other_subplans = NULL;
2652 : 32 : i = -1;
2653 [ + + ]: 48 : while ((i = bms_next_member(prunestate->other_subplans, i)) >= 0)
2654 : 16 : new_other_subplans = bms_add_member(new_other_subplans,
2655 : 16 : new_subplan_indexes[i] - 1);
2656 : :
2657 : 32 : bms_free(prunestate->other_subplans);
2658 : 32 : prunestate->other_subplans = new_other_subplans;
2659 : :
2660 : 32 : pfree(new_subplan_indexes);
2661 : : }
2662 : 268 : }
2663 : :
2664 : : /*
2665 : : * ExecFindMatchingSubPlans
2666 : : * Determine which subplans match the pruning steps detailed in
2667 : : * 'prunestate' for the current comparison expression values.
2668 : : *
2669 : : * Pass initial_prune if PARAM_EXEC Params cannot yet be evaluated. This
2670 : : * differentiates the initial executor-time pruning step from later
2671 : : * runtime pruning.
2672 : : *
2673 : : * The caller must pass a non-NULL validsubplan_rtis during initial pruning
2674 : : * to collect the RT indexes of leaf partitions whose subnodes will be
2675 : : * executed. These RT indexes are later added to EState.es_unpruned_relids.
2676 : : */
2677 : : Bitmapset *
2678 : 2602 : ExecFindMatchingSubPlans(PartitionPruneState *prunestate,
2679 : : bool initial_prune,
2680 : : Bitmapset **validsubplan_rtis)
2681 : : {
2682 : 2602 : Bitmapset *result = NULL;
2683 : : MemoryContext oldcontext;
2684 : : int i;
2685 : :
2686 : : /*
2687 : : * Either we're here on the initial prune done during pruning
2688 : : * initialization, or we're at a point where PARAM_EXEC Params can be
2689 : : * evaluated *and* there are steps in which to do so.
2690 : : */
2691 : : Assert(initial_prune || prunestate->do_exec_prune);
2692 : : Assert(validsubplan_rtis != NULL || !initial_prune);
2693 : :
2694 : : /*
2695 : : * Switch to a temp context to avoid leaking memory in the executor's
2696 : : * query-lifespan memory context.
2697 : : */
2698 : 2602 : oldcontext = MemoryContextSwitchTo(prunestate->prune_context);
2699 : :
2700 : : /*
2701 : : * For each hierarchy, do the pruning tests, and add nondeletable
2702 : : * subplans' indexes to "result".
2703 : : */
2704 [ + + ]: 5232 : for (i = 0; i < prunestate->num_partprunedata; i++)
2705 : : {
2706 : 2630 : PartitionPruningData *prunedata = prunestate->partprunedata[i];
2707 : : PartitionedRelPruningData *pprune;
2708 : :
2709 : : /*
2710 : : * We pass the zeroth item, belonging to the root table of the
2711 : : * hierarchy, and find_matching_subplans_recurse() takes care of
2712 : : * recursing to other (lower-level) parents as needed.
2713 : : */
2714 : 2630 : pprune = &prunedata->partrelprunedata[0];
2715 : 2630 : find_matching_subplans_recurse(prunedata, pprune, initial_prune,
2716 : : &result, validsubplan_rtis);
2717 : :
2718 : : /*
2719 : : * Expression eval may have used space in ExprContext too. Avoid
2720 : : * accessing exec_context during initial pruning, as it is not valid
2721 : : * at that stage.
2722 : : */
2723 [ + + + + ]: 2630 : if (!initial_prune && pprune->exec_pruning_steps)
2724 : 2265 : ResetExprContext(pprune->exec_context.exprcontext);
2725 : : }
2726 : :
2727 : : /* Add in any subplans that partition pruning didn't account for */
2728 : 2602 : result = bms_add_members(result, prunestate->other_subplans);
2729 : :
2730 : 2602 : MemoryContextSwitchTo(oldcontext);
2731 : :
2732 : : /* Copy result out of the temp context before we reset it */
2733 : 2602 : result = bms_copy(result);
2734 [ + + ]: 2602 : if (validsubplan_rtis)
2735 : 301 : *validsubplan_rtis = bms_copy(*validsubplan_rtis);
2736 : :
2737 : 2602 : MemoryContextReset(prunestate->prune_context);
2738 : :
2739 : 2602 : return result;
2740 : : }
2741 : :
2742 : : /*
2743 : : * find_matching_subplans_recurse
2744 : : * Recursive worker function for ExecFindMatchingSubPlans
2745 : : *
2746 : : * Adds valid (non-prunable) subplan IDs to *validsubplans. If
2747 : : * *validsubplan_rtis is non-NULL, it also adds the RT indexes of their
2748 : : * corresponding partitions, but only if they are leaf partitions.
2749 : : */
2750 : : static void
2751 : 2906 : find_matching_subplans_recurse(PartitionPruningData *prunedata,
2752 : : PartitionedRelPruningData *pprune,
2753 : : bool initial_prune,
2754 : : Bitmapset **validsubplans,
2755 : : Bitmapset **validsubplan_rtis)
2756 : : {
2757 : : Bitmapset *partset;
2758 : : int i;
2759 : :
2760 : : /* Guard against stack overflow due to overly deep partition hierarchy. */
2761 : 2906 : check_stack_depth();
2762 : :
2763 : : /*
2764 : : * Prune as appropriate, if we have pruning steps matching the current
2765 : : * execution context. Otherwise just include all partitions at this
2766 : : * level.
2767 : : */
2768 [ + + + + ]: 2906 : if (initial_prune && pprune->initial_pruning_steps)
2769 : 357 : partset = get_matching_partitions(&pprune->initial_context,
2770 : : pprune->initial_pruning_steps);
2771 [ + + + + ]: 2549 : else if (!initial_prune && pprune->exec_pruning_steps)
2772 : 2321 : partset = get_matching_partitions(&pprune->exec_context,
2773 : : pprune->exec_pruning_steps);
2774 : : else
2775 : 228 : partset = pprune->present_parts;
2776 : :
2777 : : /* Translate partset into subplan indexes */
2778 : 2906 : i = -1;
2779 [ + + ]: 4119 : while ((i = bms_next_member(partset, i)) >= 0)
2780 : : {
2781 [ + + ]: 1213 : if (pprune->subplan_map[i] >= 0)
2782 : : {
2783 : 1872 : *validsubplans = bms_add_member(*validsubplans,
2784 : 936 : pprune->subplan_map[i]);
2785 : :
2786 : : /*
2787 : : * Only report leaf partitions. Non-leaf partitions may appear
2788 : : * here when they use an unflattened Append or MergeAppend.
2789 : : */
2790 [ + + + + ]: 936 : if (validsubplan_rtis && pprune->leafpart_rti_map[i])
2791 : 451 : *validsubplan_rtis = bms_add_member(*validsubplan_rtis,
2792 : 451 : pprune->leafpart_rti_map[i]);
2793 : : }
2794 : : else
2795 : : {
2796 : 277 : int partidx = pprune->subpart_map[i];
2797 : :
2798 [ + + ]: 277 : if (partidx >= 0)
2799 : 276 : find_matching_subplans_recurse(prunedata,
2800 : : &prunedata->partrelprunedata[partidx],
2801 : : initial_prune, validsubplans,
2802 : : validsubplan_rtis);
2803 : : else
2804 : : {
2805 : : /*
2806 : : * We get here if the planner already pruned all the sub-
2807 : : * partitions for this partition. Silently ignore this
2808 : : * partition in this case. The end result is the same: we
2809 : : * would have pruned all partitions just the same, but we
2810 : : * don't have any pruning steps to execute to verify this.
2811 : : */
2812 : : }
2813 : : }
2814 : : }
2815 : 2906 : }
|