Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * partprune.c
4 : : * Support for partition pruning during query planning and execution
5 : : *
6 : : * This module implements partition pruning using the information contained in
7 : : * a table's partition descriptor, query clauses, and run-time parameters.
8 : : *
9 : : * During planning, clauses that can be matched to the table's partition key
10 : : * are turned into a set of "pruning steps", which are then executed to
11 : : * identify a set of partitions (as indexes in the RelOptInfo->part_rels
12 : : * array) that satisfy the constraints in the step. Partitions not in the set
13 : : * are said to have been pruned.
14 : : *
15 : : * A base pruning step may involve expressions whose values are only known
16 : : * during execution, such as Params, in which case pruning cannot occur
17 : : * entirely during planning. In that case, such steps are included alongside
18 : : * the plan, so that they can be used by the executor for further pruning.
19 : : *
20 : : * There are two kinds of pruning steps. A "base" pruning step represents
21 : : * tests on partition key column(s), typically comparisons to expressions.
22 : : * A "combine" pruning step represents a Boolean connector (AND/OR), and
23 : : * combines the outputs of some previous steps using the appropriate
24 : : * combination method.
25 : : *
26 : : * See gen_partprune_steps_internal() for more details on step generation.
27 : : *
28 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
29 : : * Portions Copyright (c) 1994, Regents of the University of California
30 : : *
31 : : * IDENTIFICATION
32 : : * src/backend/partitioning/partprune.c
33 : : *
34 : : *-------------------------------------------------------------------------
35 : : */
36 : : #include "postgres.h"
37 : :
38 : : #include "access/hash.h"
39 : : #include "access/nbtree.h"
40 : : #include "catalog/pg_operator.h"
41 : : #include "catalog/pg_opfamily.h"
42 : : #include "catalog/pg_proc.h"
43 : : #include "catalog/pg_type.h"
44 : : #include "executor/executor.h"
45 : : #include "miscadmin.h"
46 : : #include "nodes/makefuncs.h"
47 : : #include "nodes/nodeFuncs.h"
48 : : #include "optimizer/appendinfo.h"
49 : : #include "optimizer/cost.h"
50 : : #include "optimizer/optimizer.h"
51 : : #include "optimizer/pathnode.h"
52 : : #include "optimizer/placeholder.h"
53 : : #include "parser/parsetree.h"
54 : : #include "partitioning/partbounds.h"
55 : : #include "partitioning/partprune.h"
56 : : #include "utils/array.h"
57 : : #include "utils/lsyscache.h"
58 : :
59 : :
60 : : /*
61 : : * Information about a clause matched with a partition key.
62 : : */
63 : : typedef struct PartClauseInfo
64 : : {
65 : : int keyno; /* Partition key number (0 to partnatts - 1) */
66 : : Oid opno; /* operator used to compare partkey to expr */
67 : : bool op_is_ne; /* is clause's original operator <> ? */
68 : : Expr *expr; /* expr the partition key is compared to */
69 : : Oid cmpfn; /* Oid of function to compare 'expr' to the
70 : : * partition key */
71 : : int op_strategy; /* btree strategy identifying the operator */
72 : : } PartClauseInfo;
73 : :
74 : : /*
75 : : * PartClauseMatchStatus
76 : : * Describes the result of match_clause_to_partition_key()
77 : : */
78 : : typedef enum PartClauseMatchStatus
79 : : {
80 : : PARTCLAUSE_NOMATCH,
81 : : PARTCLAUSE_MATCH_CLAUSE,
82 : : PARTCLAUSE_MATCH_NULLNESS,
83 : : PARTCLAUSE_MATCH_STEPS,
84 : : PARTCLAUSE_MATCH_CONTRADICT,
85 : : PARTCLAUSE_UNSUPPORTED,
86 : : } PartClauseMatchStatus;
87 : :
88 : : /*
89 : : * PartClauseTarget
90 : : * Identifies which qual clauses we can use for generating pruning steps
91 : : */
92 : : typedef enum PartClauseTarget
93 : : {
94 : : PARTTARGET_PLANNER, /* want to prune during planning */
95 : : PARTTARGET_INITIAL, /* want to prune during executor startup */
96 : : PARTTARGET_EXEC, /* want to prune during each plan node scan */
97 : : } PartClauseTarget;
98 : :
99 : : /*
100 : : * GeneratePruningStepsContext
101 : : * Information about the current state of generation of "pruning steps"
102 : : * for a given set of clauses
103 : : *
104 : : * gen_partprune_steps() initializes and returns an instance of this struct.
105 : : *
106 : : * Note that has_mutable_op, has_mutable_arg, and has_exec_param are set if
107 : : * we found any potentially-useful-for-pruning clause having those properties,
108 : : * whether or not we actually used the clause in the steps list. This
109 : : * definition allows us to skip the PARTTARGET_EXEC pass in some cases.
110 : : */
111 : : typedef struct GeneratePruningStepsContext
112 : : {
113 : : /* Copies of input arguments for gen_partprune_steps: */
114 : : RelOptInfo *rel; /* the partitioned relation */
115 : : PartClauseTarget target; /* use-case we're generating steps for */
116 : : /* Result data: */
117 : : List *steps; /* list of PartitionPruneSteps */
118 : : bool has_mutable_op; /* clauses include any stable operators */
119 : : bool has_mutable_arg; /* clauses include any mutable comparison
120 : : * values, *other than* exec params */
121 : : bool has_exec_param; /* clauses include any PARAM_EXEC params */
122 : : bool contradictory; /* clauses were proven self-contradictory */
123 : : /* Working state: */
124 : : int next_step_id;
125 : : } GeneratePruningStepsContext;
126 : :
127 : : /* The result of performing one PartitionPruneStep */
128 : : typedef struct PruneStepResult
129 : : {
130 : : /*
131 : : * The offsets of bounds (in a table's boundinfo) whose partition is
132 : : * selected by the pruning step.
133 : : */
134 : : Bitmapset *bound_offsets;
135 : :
136 : : bool scan_default; /* Scan the default partition? */
137 : : bool scan_null; /* Scan the partition for NULL values? */
138 : : } PruneStepResult;
139 : :
140 : :
141 : : static List *add_part_relids(List *allpartrelids, Bitmapset *partrelids);
142 : : static List *make_partitionedrel_pruneinfo(PlannerInfo *root,
143 : : RelOptInfo *parentrel,
144 : : List *prunequal,
145 : : Bitmapset *partrelids,
146 : : int *relid_subplan_map,
147 : : Bitmapset **matchedsubplans);
148 : : static void gen_partprune_steps(RelOptInfo *rel, List *clauses,
149 : : PartClauseTarget target,
150 : : GeneratePruningStepsContext *context);
151 : : static List *gen_partprune_steps_internal(GeneratePruningStepsContext *context,
152 : : List *clauses);
153 : : static PartitionPruneStep *gen_prune_step_op(GeneratePruningStepsContext *context,
154 : : StrategyNumber opstrategy, bool op_is_ne,
155 : : List *exprs, List *cmpfns, Bitmapset *nullkeys);
156 : : static PartitionPruneStep *gen_prune_step_combine(GeneratePruningStepsContext *context,
157 : : List *source_stepids,
158 : : PartitionPruneCombineOp combineOp);
159 : : static List *gen_prune_steps_from_opexps(GeneratePruningStepsContext *context,
160 : : List **keyclauses, Bitmapset *nullkeys);
161 : : static PartClauseMatchStatus match_clause_to_partition_key(GeneratePruningStepsContext *context,
162 : : Expr *clause, const Expr *partkey, int partkeyidx,
163 : : bool *clause_is_not_null,
164 : : PartClauseInfo **pc, List **clause_steps);
165 : : static List *get_steps_using_prefix(GeneratePruningStepsContext *context,
166 : : StrategyNumber step_opstrategy,
167 : : bool step_op_is_ne,
168 : : Expr *step_lastexpr,
169 : : Oid step_lastcmpfn,
170 : : Bitmapset *step_nullkeys,
171 : : List *prefix);
172 : : static List *get_steps_using_prefix_recurse(GeneratePruningStepsContext *context,
173 : : StrategyNumber step_opstrategy,
174 : : bool step_op_is_ne,
175 : : Expr *step_lastexpr,
176 : : Oid step_lastcmpfn,
177 : : Bitmapset *step_nullkeys,
178 : : List *prefix,
179 : : ListCell *start,
180 : : List *step_exprs,
181 : : List *step_cmpfns);
182 : : static PruneStepResult *get_matching_hash_bounds(PartitionPruneContext *context,
183 : : StrategyNumber opstrategy, const Datum *values, int nvalues,
184 : : FmgrInfo *partsupfunc, Bitmapset *nullkeys);
185 : : static PruneStepResult *get_matching_list_bounds(PartitionPruneContext *context,
186 : : StrategyNumber opstrategy, Datum value, int nvalues,
187 : : FmgrInfo *partsupfunc, Bitmapset *nullkeys);
188 : : static PruneStepResult *get_matching_range_bounds(PartitionPruneContext *context,
189 : : StrategyNumber opstrategy, const Datum *values, int nvalues,
190 : : FmgrInfo *partsupfunc, Bitmapset *nullkeys);
191 : : static Bitmapset *pull_exec_paramids(Expr *expr);
192 : : static bool pull_exec_paramids_walker(Node *node, Bitmapset **context);
193 : : static Bitmapset *get_partkey_exec_paramids(List *steps);
194 : : static PruneStepResult *perform_pruning_base_step(PartitionPruneContext *context,
195 : : PartitionPruneStepOp *opstep);
196 : : static PruneStepResult *perform_pruning_combine_step(PartitionPruneContext *context,
197 : : PartitionPruneStepCombine *cstep,
198 : : PruneStepResult **step_results);
199 : : static PartClauseMatchStatus match_boolean_partition_clause(Oid partopfamily,
200 : : Expr *clause,
201 : : const Expr *partkey,
202 : : Expr **outconst,
203 : : bool *notclause);
204 : : static void partkey_datum_from_expr(PartitionPruneContext *context,
205 : : Expr *expr, int stateidx,
206 : : Datum *value, bool *isnull);
207 : :
208 : :
209 : : /*
210 : : * make_partition_pruneinfo
211 : : * Checks if the given set of quals can be used to build pruning steps
212 : : * that the executor can use to prune away unneeded partitions. If
213 : : * suitable quals are found then a PartitionPruneInfo is built and tagged
214 : : * onto the PlannerInfo's partPruneInfos list.
215 : : *
216 : : * The return value is the 0-based index of the item added to the
217 : : * partPruneInfos list or -1 if nothing was added.
218 : : *
219 : : * 'parentrel' is the RelOptInfo for an appendrel, and 'subpaths' is the list
220 : : * of scan paths for its child rels.
221 : : * 'prunequal' is a list of potential pruning quals (i.e., restriction
222 : : * clauses that are applicable to the appendrel).
223 : : */
224 : : int
2834 tgl@sss.pgh.pa.us 225 :CBC 7120 : make_partition_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
226 : : List *subpaths,
227 : : List *prunequal)
228 : : {
229 : : PartitionPruneInfo *pruneinfo;
230 : 7120 : Bitmapset *allmatchedsubplans = NULL;
231 : : List *allpartrelids;
232 : : List *prunerelinfos;
233 : : int *relid_subplan_map;
234 : : ListCell *lc;
235 : : int i;
236 : :
237 : : /*
238 : : * Scan the subpaths to see which ones are scans of partition child
239 : : * relations, and identify their parent partitioned rels. (Note: we must
240 : : * restrict the parent partitioned rels to be parentrel or children of
241 : : * parentrel, otherwise we couldn't translate prunequal to match.)
242 : : *
243 : : * Also construct a temporary array to map from partition-child-relation
244 : : * relid to the index in 'subpaths' of the scan plan for that partition.
245 : : * (Use of "subplan" rather than "subpath" is a bit of a misnomer, but
246 : : * we'll let it stand.) For convenience, we use 1-based indexes here, so
247 : : * that zero can represent an un-filled array entry.
248 : : */
1919 249 : 7120 : allpartrelids = NIL;
145 michael@paquier.xyz 250 :GNC 7120 : relid_subplan_map = palloc0_array(int, root->simple_rel_array_size);
251 : :
2950 alvherre@alvh.no-ip. 252 :CBC 7120 : i = 1;
253 [ + - + + : 21078 : foreach(lc, subpaths)
+ + ]
254 : : {
255 : 13958 : Path *path = (Path *) lfirst(lc);
256 : 13958 : RelOptInfo *pathrel = path->parent;
257 : :
258 : : /* We don't consider partitioned joins here */
1919 tgl@sss.pgh.pa.us 259 [ + - ]: 13958 : if (pathrel->reloptkind == RELOPT_OTHER_MEMBER_REL)
260 : : {
261 : 13958 : RelOptInfo *prel = pathrel;
262 : 13958 : Bitmapset *partrelids = NULL;
263 : :
264 : : /*
265 : : * Traverse up to the pathrel's topmost partitioned parent,
266 : : * collecting parent relids as we go; but stop if we reach
267 : : * parentrel. (Normally, a pathrel's topmost partitioned parent
268 : : * is either parentrel or a UNION ALL appendrel child of
269 : : * parentrel. But when handling partitionwise joins of
270 : : * multi-level partitioning trees, we can see an append path whose
271 : : * parentrel is an intermediate partitioned table.)
272 : : */
273 : : do
274 : : {
275 : : AppendRelInfo *appinfo;
276 : :
277 [ - + ]: 16727 : Assert(prel->relid < root->simple_rel_array_size);
278 : 16727 : appinfo = root->append_rel_array[prel->relid];
279 : 16727 : prel = find_base_rel(root, appinfo->parent_relid);
280 [ + + + - : 16727 : if (!IS_PARTITIONED_REL(prel))
+ - + - +
- ]
281 : : break; /* reached a non-partitioned parent */
282 : : /* accept this level as an interesting parent */
283 : 13645 : partrelids = bms_add_member(partrelids, prel->relid);
284 [ + + ]: 13645 : if (prel == parentrel)
285 : 10876 : break; /* don't traverse above parentrel */
286 [ + - ]: 2769 : } while (prel->reloptkind == RELOPT_OTHER_MEMBER_REL);
287 : :
288 [ + + ]: 13958 : if (partrelids)
289 : : {
290 : : /*
291 : : * Found some relevant parent partitions, which may or may not
292 : : * overlap with partition trees we already found. Add new
293 : : * information to the allpartrelids list.
294 : : */
295 : 11091 : allpartrelids = add_part_relids(allpartrelids, partrelids);
296 : : /* Also record the subplan in relid_subplan_map[] */
297 : : /* No duplicates please */
298 [ - + ]: 11091 : Assert(relid_subplan_map[pathrel->relid] == 0);
299 : 11091 : relid_subplan_map[pathrel->relid] = i;
300 : : }
301 : : }
302 : 13958 : i++;
303 : : }
304 : :
305 : : /*
306 : : * We now build a PartitionedRelPruneInfo for each topmost partitioned rel
307 : : * (omitting any that turn out not to have useful pruning quals).
308 : : */
2834 309 : 7120 : prunerelinfos = NIL;
1919 310 [ + + + + : 13125 : foreach(lc, allpartrelids)
+ + ]
311 : : {
312 : 6005 : Bitmapset *partrelids = (Bitmapset *) lfirst(lc);
313 : : List *pinfolist;
2834 314 : 6005 : Bitmapset *matchedsubplans = NULL;
315 : :
316 : 6005 : pinfolist = make_partitionedrel_pruneinfo(root, parentrel,
317 : : prunequal,
318 : : partrelids,
319 : : relid_subplan_map,
320 : : &matchedsubplans);
321 : :
322 : : /* When pruning is possible, record the matched subplans */
323 [ + + ]: 6005 : if (pinfolist != NIL)
324 : : {
325 : 500 : prunerelinfos = lappend(prunerelinfos, pinfolist);
326 : 500 : allmatchedsubplans = bms_join(matchedsubplans,
327 : : allmatchedsubplans);
328 : : }
329 : : }
330 : :
331 : 7120 : pfree(relid_subplan_map);
332 : :
333 : : /*
334 : : * If none of the partition hierarchies had any useful run-time pruning
335 : : * quals, then we can just not bother with run-time pruning.
336 : : */
337 [ + + ]: 7120 : if (prunerelinfos == NIL)
460 amitlan@postgresql.o 338 : 6630 : return -1;
339 : :
340 : : /* Else build the result data structure */
2834 tgl@sss.pgh.pa.us 341 : 490 : pruneinfo = makeNode(PartitionPruneInfo);
460 amitlan@postgresql.o 342 : 490 : pruneinfo->relids = bms_copy(parentrel->relids);
2834 tgl@sss.pgh.pa.us 343 : 490 : pruneinfo->prune_infos = prunerelinfos;
344 : :
345 : : /*
346 : : * Some subplans may not belong to any of the identified partitioned rels.
347 : : * This can happen for UNION ALL queries which include a non-partitioned
348 : : * table, or when some of the hierarchies aren't run-time prunable. Build
349 : : * a bitmapset of the indexes of all such subplans, so that the executor
350 : : * can identify which subplans should never be pruned.
351 : : */
352 [ + + ]: 490 : if (bms_num_members(allmatchedsubplans) < list_length(subpaths))
353 : : {
354 : : Bitmapset *other_subplans;
355 : :
356 : : /* Create the complement of allmatchedsubplans */
357 : 30 : other_subplans = bms_add_range(NULL, 0, list_length(subpaths) - 1);
358 : 30 : other_subplans = bms_del_members(other_subplans, allmatchedsubplans);
359 : :
360 : 30 : pruneinfo->other_subplans = other_subplans;
361 : : }
362 : : else
363 : 460 : pruneinfo->other_subplans = NULL;
364 : :
460 amitlan@postgresql.o 365 : 490 : root->partPruneInfos = lappend(root->partPruneInfos, pruneinfo);
366 : :
367 : 490 : return list_length(root->partPruneInfos) - 1;
368 : : }
369 : :
370 : : /*
371 : : * add_part_relids
372 : : * Add new info to a list of Bitmapsets of partitioned relids.
373 : : *
374 : : * Within 'allpartrelids', there is one Bitmapset for each topmost parent
375 : : * partitioned rel. Each Bitmapset contains the RT indexes of the topmost
376 : : * parent as well as its relevant non-leaf child partitions. Since (by
377 : : * construction of the rangetable list) parent partitions must have lower
378 : : * RT indexes than their children, we can distinguish the topmost parent
379 : : * as being the lowest set bit in the Bitmapset.
380 : : *
381 : : * 'partrelids' contains the RT indexes of a parent partitioned rel, and
382 : : * possibly some non-leaf children, that are newly identified as parents of
383 : : * some subpath rel passed to make_partition_pruneinfo(). These are added
384 : : * to an appropriate member of 'allpartrelids'.
385 : : *
386 : : * Note that the list contains only RT indexes of partitioned tables that
387 : : * are parents of some scan-level relation appearing in the 'subpaths' that
388 : : * make_partition_pruneinfo() is dealing with. Also, "topmost" parents are
389 : : * not allowed to be higher than the 'parentrel' associated with the append
390 : : * path. In this way, we avoid expending cycles on partitioned rels that
391 : : * can't contribute useful pruning information for the problem at hand.
392 : : * (It is possible for 'parentrel' to be a child partitioned table, and it
393 : : * is also possible for scan-level relations to be child partitioned tables
394 : : * rather than leaf partitions. Hence we must construct this relation set
395 : : * with reference to the particular append path we're dealing with, rather
396 : : * than looking at the full partitioning structure represented in the
397 : : * RelOptInfos.)
398 : : */
399 : : static List *
1919 tgl@sss.pgh.pa.us 400 : 11091 : add_part_relids(List *allpartrelids, Bitmapset *partrelids)
401 : : {
402 : : Index targetpart;
403 : : ListCell *lc;
404 : :
405 : : /* We can easily get the lowest set bit this way: */
406 : 11091 : targetpart = bms_next_member(partrelids, -1);
407 [ - + ]: 11091 : Assert(targetpart > 0);
408 : :
409 : : /* Look for a matching topmost parent */
410 [ + + + + : 11151 : foreach(lc, allpartrelids)
+ + ]
411 : : {
412 : 5146 : Bitmapset *currpartrelids = (Bitmapset *) lfirst(lc);
413 : 5146 : Index currtarget = bms_next_member(currpartrelids, -1);
414 : :
415 [ + + ]: 5146 : if (targetpart == currtarget)
416 : : {
417 : : /* Found a match, so add any new RT indexes to this hierarchy */
418 : 5086 : currpartrelids = bms_add_members(currpartrelids, partrelids);
419 : 5086 : lfirst(lc) = currpartrelids;
420 : 5086 : return allpartrelids;
421 : : }
422 : : }
423 : : /* No match, so add the new partition hierarchy to the list */
424 : 6005 : return lappend(allpartrelids, partrelids);
425 : : }
426 : :
427 : : /*
428 : : * make_partitionedrel_pruneinfo
429 : : * Build a List of PartitionedRelPruneInfos, one for each interesting
430 : : * partitioned rel in a partitioning hierarchy. These can be used in the
431 : : * executor to allow additional partition pruning to take place.
432 : : *
433 : : * parentrel: rel associated with the appendpath being considered
434 : : * prunequal: potential pruning quals, represented for parentrel
435 : : * partrelids: Set of RT indexes identifying relevant partitioned tables
436 : : * within a single partitioning hierarchy
437 : : * relid_subplan_map[]: maps child relation relids to subplan indexes
438 : : * matchedsubplans: on success, receives the set of subplan indexes which
439 : : * were matched to this partition hierarchy
440 : : *
441 : : * If we cannot find any useful run-time pruning steps, return NIL.
442 : : * However, on success, each rel identified in partrelids will have
443 : : * an element in the result list, even if some of them are useless.
444 : : */
445 : : static List *
2834 446 : 6005 : make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
447 : : List *prunequal,
448 : : Bitmapset *partrelids,
449 : : int *relid_subplan_map,
450 : : Bitmapset **matchedsubplans)
451 : : {
452 : 6005 : RelOptInfo *targetpart = NULL;
453 : 6005 : List *pinfolist = NIL;
454 : 6005 : bool doruntimeprune = false;
455 : : int *relid_subpart_map;
456 : 6005 : Bitmapset *subplansfound = NULL;
457 : : ListCell *lc;
458 : : int rti;
459 : : int i;
460 : :
461 : : /*
462 : : * Examine each partitioned rel, constructing a temporary array to map
463 : : * from planner relids to index of the partitioned rel, and building a
464 : : * PartitionedRelPruneInfo for each partitioned rel.
465 : : *
466 : : * In this phase we discover whether runtime pruning is needed at all; if
467 : : * not, we can avoid doing further work.
468 : : */
145 michael@paquier.xyz 469 :GNC 6005 : relid_subpart_map = palloc0_array(int, root->simple_rel_array_size);
470 : :
2950 alvherre@alvh.no-ip. 471 :CBC 6005 : i = 1;
2010 drowley@postgresql.o 472 : 6005 : rti = -1;
473 [ + + ]: 13331 : while ((rti = bms_next_member(partrelids, rti)) > 0)
474 : : {
2950 alvherre@alvh.no-ip. 475 : 7331 : RelOptInfo *subpart = find_base_rel(root, rti);
476 : : PartitionedRelPruneInfo *pinfo;
477 : : List *partprunequal;
478 : : List *initial_pruning_steps;
479 : : List *exec_pruning_steps;
480 : : Bitmapset *execparamids;
481 : : GeneratePruningStepsContext context;
482 : :
483 : : /*
484 : : * Fill the mapping array.
485 : : *
486 : : * relid_subpart_map maps relid of a non-leaf partition to the index
487 : : * in the returned PartitionedRelPruneInfo list of the info for that
488 : : * partition. We use 1-based indexes here, so that zero can represent
489 : : * an un-filled array entry.
490 : : */
2601 tgl@sss.pgh.pa.us 491 [ - + ]: 7331 : Assert(rti < root->simple_rel_array_size);
492 : 7331 : relid_subpart_map[rti] = i++;
493 : :
494 : : /*
495 : : * Translate pruning qual, if necessary, for this partition.
496 : : *
497 : : * The first item in the list is the target partitioned relation.
498 : : */
2950 alvherre@alvh.no-ip. 499 [ + + ]: 7331 : if (!targetpart)
500 : : {
501 : 6005 : targetpart = subpart;
502 : :
503 : : /*
504 : : * The prunequal is presented to us as a qual for 'parentrel'.
505 : : * Frequently this rel is the same as targetpart, so we can skip
506 : : * an adjust_appendrel_attrs step. But it might not be, and then
507 : : * we have to translate. We update the prunequal parameter here,
508 : : * because in later iterations of the loop for child partitions,
509 : : * we want to translate from parent to child variables.
510 : : */
2827 tgl@sss.pgh.pa.us 511 [ + + ]: 6005 : if (!bms_equal(parentrel->relids, subpart->relids))
512 : : {
513 : : int nappinfos;
2834 514 : 50 : AppendRelInfo **appinfos = find_appinfos_by_relids(root,
515 : : subpart->relids,
516 : : &nappinfos);
517 : :
518 : 50 : prunequal = (List *) adjust_appendrel_attrs(root, (Node *)
519 : : prunequal,
520 : : nappinfos,
521 : : appinfos);
522 : :
523 : 50 : pfree(appinfos);
524 : : }
525 : :
2950 alvherre@alvh.no-ip. 526 : 6005 : partprunequal = prunequal;
527 : : }
528 : : else
529 : : {
530 : : /*
531 : : * For sub-partitioned tables the columns may not be in the same
532 : : * order as the parent, so we must translate the prunequal to make
533 : : * it compatible with this relation.
534 : : */
535 : : partprunequal = (List *)
536 : 1326 : adjust_appendrel_attrs_multilevel(root,
537 : : (Node *) prunequal,
538 : : subpart,
539 : : targetpart);
540 : : }
541 : :
542 : : /*
543 : : * Convert pruning qual to pruning steps. We may need to do this
544 : : * twice, once to obtain executor startup pruning steps, and once for
545 : : * executor per-scan pruning steps. This first pass creates startup
546 : : * pruning steps and detects whether there's any possibly-useful quals
547 : : * that would require per-scan pruning.
548 : : */
2545 tgl@sss.pgh.pa.us 549 : 7331 : gen_partprune_steps(subpart, partprunequal, PARTTARGET_INITIAL,
550 : : &context);
551 : :
552 [ + + ]: 7331 : if (context.contradictory)
553 : : {
554 : : /*
555 : : * This shouldn't happen as the planner should have detected this
556 : : * earlier. However, we do use additional quals from parameterized
557 : : * paths here. These do only compare Params to the partition key,
558 : : * so this shouldn't cause the discovery of any new qual
559 : : * contradictions that were not previously discovered as the Param
560 : : * values are unknown during planning. Anyway, we'd better do
561 : : * something sane here, so let's just disable run-time pruning.
562 : : */
2950 alvherre@alvh.no-ip. 563 : 5 : return NIL;
564 : : }
565 : :
566 : : /*
567 : : * If no mutable operators or expressions appear in usable pruning
568 : : * clauses, then there's no point in running startup pruning, because
569 : : * plan-time pruning should have pruned everything prunable.
570 : : */
2545 tgl@sss.pgh.pa.us 571 [ + + + + ]: 7326 : if (context.has_mutable_op || context.has_mutable_arg)
572 : 314 : initial_pruning_steps = context.steps;
573 : : else
574 : 7012 : initial_pruning_steps = NIL;
575 : :
576 : : /*
577 : : * If no exec Params appear in potentially-usable pruning clauses,
578 : : * then there's no point in even thinking about per-scan pruning.
579 : : */
580 [ + + ]: 7326 : if (context.has_exec_param)
581 : : {
582 : : /* ... OK, we'd better think about it */
583 : 341 : gen_partprune_steps(subpart, partprunequal, PARTTARGET_EXEC,
584 : : &context);
585 : :
586 [ - + ]: 341 : if (context.contradictory)
587 : : {
588 : : /* As above, skip run-time pruning if anything fishy happens */
2545 tgl@sss.pgh.pa.us 589 :UBC 0 : return NIL;
590 : : }
591 : :
2545 tgl@sss.pgh.pa.us 592 :CBC 341 : exec_pruning_steps = context.steps;
593 : :
594 : : /*
595 : : * Detect which exec Params actually got used; the fact that some
596 : : * were in available clauses doesn't mean we actually used them.
597 : : * Skip per-scan pruning if there are none.
598 : : */
599 : 341 : execparamids = get_partkey_exec_paramids(exec_pruning_steps);
600 : :
601 [ - + ]: 341 : if (bms_is_empty(execparamids))
2545 tgl@sss.pgh.pa.us 602 :UBC 0 : exec_pruning_steps = NIL;
603 : : }
604 : : else
605 : : {
606 : : /* No exec Params anywhere, so forget about scan-time pruning */
2545 tgl@sss.pgh.pa.us 607 :CBC 6985 : exec_pruning_steps = NIL;
608 : 6985 : execparamids = NULL;
609 : : }
610 : :
611 [ + + + + ]: 7326 : if (initial_pruning_steps || exec_pruning_steps)
612 : 640 : doruntimeprune = true;
613 : :
614 : : /* Begin constructing the PartitionedRelPruneInfo for this rel */
2601 615 : 7326 : pinfo = makeNode(PartitionedRelPruneInfo);
616 : 7326 : pinfo->rtindex = rti;
2545 617 : 7326 : pinfo->initial_pruning_steps = initial_pruning_steps;
618 : 7326 : pinfo->exec_pruning_steps = exec_pruning_steps;
619 : 7326 : pinfo->execparamids = execparamids;
620 : : /* Remaining fields will be filled in the next loop */
621 : :
2601 622 : 7326 : pinfolist = lappend(pinfolist, pinfo);
623 : : }
624 : :
625 [ + + ]: 6000 : if (!doruntimeprune)
626 : : {
627 : : /* No run-time pruning required. */
628 : 5500 : pfree(relid_subpart_map);
629 : 5500 : return NIL;
630 : : }
631 : :
632 : : /*
633 : : * Run-time pruning will be required, so initialize other information.
634 : : * That includes two maps -- one needed to convert partition indexes of
635 : : * leaf partitions to the indexes of their subplans in the subplan list,
636 : : * another needed to convert partition indexes of sub-partitioned
637 : : * partitions to the indexes of their PartitionedRelPruneInfo in the
638 : : * PartitionedRelPruneInfo list.
639 : : */
640 [ + - + + : 1364 : foreach(lc, pinfolist)
+ + ]
641 : : {
642 : 864 : PartitionedRelPruneInfo *pinfo = lfirst(lc);
643 : 864 : RelOptInfo *subpart = find_base_rel(root, pinfo->rtindex);
644 : : Bitmapset *present_parts;
645 : 864 : int nparts = subpart->nparts;
646 : : int *subplan_map;
647 : : int *subpart_map;
648 : : Oid *relid_map;
649 : : int *leafpart_rti_map;
650 : :
651 : : /*
652 : : * Construct the subplan and subpart maps for this partitioning level.
653 : : * Here we convert to zero-based indexes, with -1 for empty entries.
654 : : * Also construct a Bitmapset of all partitions that are present (that
655 : : * is, not pruned already).
656 : : */
2886 657 : 864 : subplan_map = (int *) palloc(nparts * sizeof(int));
2593 658 : 864 : memset(subplan_map, -1, nparts * sizeof(int));
2950 alvherre@alvh.no-ip. 659 : 864 : subpart_map = (int *) palloc(nparts * sizeof(int));
2593 tgl@sss.pgh.pa.us 660 : 864 : memset(subpart_map, -1, nparts * sizeof(int));
661 : 864 : relid_map = (Oid *) palloc0(nparts * sizeof(Oid));
452 amitlan@postgresql.o 662 : 864 : leafpart_rti_map = (int *) palloc0(nparts * sizeof(int));
2950 alvherre@alvh.no-ip. 663 : 864 : present_parts = NULL;
664 : :
1736 drowley@postgresql.o 665 : 864 : i = -1;
666 [ + + ]: 3247 : while ((i = bms_next_member(subpart->live_parts, i)) >= 0)
667 : : {
2950 alvherre@alvh.no-ip. 668 : 2383 : RelOptInfo *partrel = subpart->part_rels[i];
669 : : int subplanidx;
670 : : int subpartidx;
671 : :
1736 drowley@postgresql.o 672 [ - + ]: 2383 : Assert(partrel != NULL);
673 : :
2593 tgl@sss.pgh.pa.us 674 : 2383 : subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1;
675 : 2383 : subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1;
2616 rhaas@postgresql.org 676 [ + - ]: 2383 : relid_map[i] = planner_rt_fetch(partrel->relid, root)->relid;
677 : :
678 : : /*
679 : : * Track the RT indexes of "leaf" partitions so they can be
680 : : * included in the PlannerGlobal.prunableRelids set, indicating
681 : : * relations that may be pruned during executor startup.
682 : : *
683 : : * Only leaf partitions with a valid subplan that are prunable
684 : : * using initial pruning are added to prunableRelids. So
685 : : * partitions without a subplan due to constraint exclusion will
686 : : * remain in PlannedStmt.unprunableRelids.
687 : : */
2834 tgl@sss.pgh.pa.us 688 [ + + ]: 2383 : if (subplanidx >= 0)
689 : : {
690 : 2014 : present_parts = bms_add_member(present_parts, i);
691 : :
692 : : /*
693 : : * Non-leaf partitions may appear here when they use an
694 : : * unflattened Append or MergeAppend. These should not be
695 : : * included in prunableRelids.
696 : : */
434 amitlan@postgresql.o 697 [ + + ]: 2014 : if (partrel->nparts == -1)
698 : 1989 : leafpart_rti_map[i] = (int) partrel->relid;
699 : :
700 : : /* Record finding this subplan */
2834 tgl@sss.pgh.pa.us 701 : 2014 : subplansfound = bms_add_member(subplansfound, subplanidx);
702 : : }
703 [ + + ]: 369 : else if (subpartidx >= 0)
2950 alvherre@alvh.no-ip. 704 : 364 : present_parts = bms_add_member(present_parts, i);
705 : : }
706 : :
707 : : /*
708 : : * Ensure there were no stray PartitionedRelPruneInfo generated for
709 : : * partitioned tables that we have no sub-paths or
710 : : * sub-PartitionedRelPruneInfo for.
711 : : */
2010 drowley@postgresql.o 712 [ - + ]: 864 : Assert(!bms_is_empty(present_parts));
713 : :
714 : : /* Record the maps and other information. */
2950 alvherre@alvh.no-ip. 715 : 864 : pinfo->present_parts = present_parts;
716 : 864 : pinfo->nparts = nparts;
2886 tgl@sss.pgh.pa.us 717 : 864 : pinfo->subplan_map = subplan_map;
2950 alvherre@alvh.no-ip. 718 : 864 : pinfo->subpart_map = subpart_map;
2616 rhaas@postgresql.org 719 : 864 : pinfo->relid_map = relid_map;
452 amitlan@postgresql.o 720 : 864 : pinfo->leafpart_rti_map = leafpart_rti_map;
721 : : }
722 : :
2950 alvherre@alvh.no-ip. 723 : 500 : pfree(relid_subpart_map);
724 : :
2834 tgl@sss.pgh.pa.us 725 : 500 : *matchedsubplans = subplansfound;
726 : :
727 : 500 : return pinfolist;
728 : : }
729 : :
730 : : /*
731 : : * gen_partprune_steps
732 : : * Process 'clauses' (typically a rel's baserestrictinfo list of clauses)
733 : : * and create a list of "partition pruning steps".
734 : : *
735 : : * 'target' tells whether to generate pruning steps for planning (use
736 : : * immutable clauses only), or for executor startup (use any allowable
737 : : * clause except ones containing PARAM_EXEC Params), or for executor
738 : : * per-scan pruning (use any allowable clause).
739 : : *
740 : : * 'context' is an output argument that receives the steps list as well as
741 : : * some subsidiary flags; see the GeneratePruningStepsContext typedef.
742 : : */
743 : : static void
2545 744 : 16128 : gen_partprune_steps(RelOptInfo *rel, List *clauses, PartClauseTarget target,
745 : : GeneratePruningStepsContext *context)
746 : : {
747 : : /* Initialize all output values to zero/false/NULL */
748 : 16128 : memset(context, 0, sizeof(GeneratePruningStepsContext));
749 : 16128 : context->rel = rel;
750 : 16128 : context->target = target;
751 : :
752 : : /*
753 : : * If this partitioned table is in turn a partition, and it shares any
754 : : * partition keys with its parent, then it's possible that the hierarchy
755 : : * allows the parent a narrower range of values than some of its
756 : : * partitions (particularly the default one). This is normally not
757 : : * useful, but it can be to prune the default partition.
758 : : */
2457 alvherre@alvh.no-ip. 759 [ + + + + ]: 16128 : if (partition_bound_has_default(rel->boundinfo) && rel->partition_qual)
760 : : {
761 : : /* Make a copy to avoid modifying the passed-in List */
762 : 615 : clauses = list_concat_copy(clauses, rel->partition_qual);
763 : : }
764 : :
765 : : /* Down into the rabbit-hole. */
2545 tgl@sss.pgh.pa.us 766 : 16128 : (void) gen_partprune_steps_internal(context, clauses);
2951 alvherre@alvh.no-ip. 767 : 16128 : }
768 : :
769 : : /*
770 : : * prune_append_rel_partitions
771 : : * Process rel's baserestrictinfo and make use of quals which can be
772 : : * evaluated during query planning in order to determine the minimum set
773 : : * of partitions which must be scanned to satisfy these quals. Returns
774 : : * the matching partitions in the form of a Bitmapset containing the
775 : : * partitions' indexes in the rel's part_rels array.
776 : : *
777 : : * Callers must ensure that 'rel' is a partitioned table.
778 : : */
779 : : Bitmapset *
780 : 14088 : prune_append_rel_partitions(RelOptInfo *rel)
781 : : {
782 : 14088 : List *clauses = rel->baserestrictinfo;
783 : : List *pruning_steps;
784 : : GeneratePruningStepsContext gcontext;
785 : : PartitionPruneContext context;
786 : :
787 [ - + ]: 14088 : Assert(rel->part_scheme != NULL);
788 : :
789 : : /* If there are no partitions, return the empty set */
790 [ - + ]: 14088 : if (rel->nparts == 0)
2951 alvherre@alvh.no-ip. 791 :UBC 0 : return NULL;
792 : :
793 : : /*
794 : : * If pruning is disabled or if there are no clauses to prune with, return
795 : : * all partitions.
796 : : */
2593 tgl@sss.pgh.pa.us 797 [ + + + + ]:CBC 14088 : if (!enable_partition_pruning || clauses == NIL)
798 : 5632 : return bms_add_range(NULL, 0, rel->nparts - 1);
799 : :
800 : : /*
801 : : * Process clauses to extract pruning steps that are usable at plan time.
802 : : * If the clauses are found to be contradictory, we can return the empty
803 : : * set.
804 : : */
2545 805 : 8456 : gen_partprune_steps(rel, clauses, PARTTARGET_PLANNER,
806 : : &gcontext);
807 [ + + ]: 8456 : if (gcontext.contradictory)
2951 alvherre@alvh.no-ip. 808 : 118 : return NULL;
2545 tgl@sss.pgh.pa.us 809 : 8338 : pruning_steps = gcontext.steps;
810 : :
811 : : /* If there's nothing usable, return all partitions */
812 [ + + ]: 8338 : if (pruning_steps == NIL)
813 : 2542 : return bms_add_range(NULL, 0, rel->nparts - 1);
814 : :
815 : : /* Set up PartitionPruneContext */
2951 alvherre@alvh.no-ip. 816 : 5796 : context.strategy = rel->part_scheme->strategy;
817 : 5796 : context.partnatts = rel->part_scheme->partnatts;
818 : 5796 : context.nparts = rel->nparts;
819 : 5796 : context.boundinfo = rel->boundinfo;
2883 tgl@sss.pgh.pa.us 820 : 5796 : context.partcollation = rel->part_scheme->partcollation;
821 : 5796 : context.partsupfunc = rel->part_scheme->partsupfunc;
145 michael@paquier.xyz 822 :GNC 5796 : context.stepcmpfuncs = palloc0_array(FmgrInfo,
823 : : context.partnatts * list_length(pruning_steps));
2883 tgl@sss.pgh.pa.us 824 :CBC 5796 : context.ppccontext = CurrentMemoryContext;
825 : :
826 : : /* These are not valid when being called from the planner */
2950 alvherre@alvh.no-ip. 827 : 5796 : context.planstate = NULL;
1491 828 : 5796 : context.exprcontext = NULL;
2933 829 : 5796 : context.exprstates = NULL;
830 : :
831 : : /* Actual pruning happens here. */
2593 tgl@sss.pgh.pa.us 832 : 5796 : return get_matching_partitions(&context, pruning_steps);
833 : : }
834 : :
835 : : /*
836 : : * get_matching_partitions
837 : : * Determine partitions that survive partition pruning
838 : : *
839 : : * Note: context->exprcontext must be valid when the pruning_steps were
840 : : * generated with a target other than PARTTARGET_PLANNER.
841 : : *
842 : : * Returns a Bitmapset of the RelOptInfo->part_rels indexes of the surviving
843 : : * partitions.
844 : : */
845 : : Bitmapset *
2951 alvherre@alvh.no-ip. 846 : 8466 : get_matching_partitions(PartitionPruneContext *context, List *pruning_steps)
847 : : {
848 : : Bitmapset *result;
849 : 8466 : int num_steps = list_length(pruning_steps),
850 : : i;
851 : : PruneStepResult **results,
852 : : *final_result;
853 : : ListCell *lc;
854 : : bool scan_default;
855 : :
856 : : /* If there are no pruning steps then all partitions match. */
857 [ - + ]: 8466 : if (num_steps == 0)
858 : : {
2836 alvherre@alvh.no-ip. 859 [ # # ]:UBC 0 : Assert(context->nparts > 0);
2951 860 : 0 : return bms_add_range(NULL, 0, context->nparts - 1);
861 : : }
862 : :
863 : : /*
864 : : * Allocate space for individual pruning steps to store its result. Each
865 : : * slot will hold a PruneStepResult after performing a given pruning step.
866 : : * Later steps may use the result of one or more earlier steps. The
867 : : * result of applying all pruning steps is the value contained in the slot
868 : : * of the last pruning step.
869 : : */
870 : : results = (PruneStepResult **)
2951 alvherre@alvh.no-ip. 871 :CBC 8466 : palloc0(num_steps * sizeof(PruneStepResult *));
872 [ + - + + : 20973 : foreach(lc, pruning_steps)
+ + ]
873 : : {
874 : 12507 : PartitionPruneStep *step = lfirst(lc);
875 : :
876 [ + + - ]: 12507 : switch (nodeTag(step))
877 : : {
878 : 10418 : case T_PartitionPruneStepOp:
879 : 20836 : results[step->step_id] =
880 : 10418 : perform_pruning_base_step(context,
881 : : (PartitionPruneStepOp *) step);
882 : 10418 : break;
883 : :
884 : 2089 : case T_PartitionPruneStepCombine:
885 : 4178 : results[step->step_id] =
886 : 2089 : perform_pruning_combine_step(context,
887 : : (PartitionPruneStepCombine *) step,
888 : : results);
889 : 2089 : break;
890 : :
2951 alvherre@alvh.no-ip. 891 :UBC 0 : default:
892 [ # # ]: 0 : elog(ERROR, "invalid pruning step type: %d",
893 : : (int) nodeTag(step));
894 : : }
895 : : }
896 : :
897 : : /*
898 : : * At this point we know the offsets of all the datums whose corresponding
899 : : * partitions need to be in the result, including special null-accepting
900 : : * and default partitions. Collect the actual partition indexes now.
901 : : */
2951 alvherre@alvh.no-ip. 902 :CBC 8466 : final_result = results[num_steps - 1];
903 [ - + ]: 8466 : Assert(final_result != NULL);
904 : 8466 : i = -1;
905 : 8466 : result = NULL;
2466 906 : 8466 : scan_default = final_result->scan_default;
2951 907 [ + + ]: 17419 : while ((i = bms_next_member(final_result->bound_offsets, i)) >= 0)
908 : : {
909 : : int partindex;
910 : :
1923 tgl@sss.pgh.pa.us 911 [ - + ]: 8953 : Assert(i < context->boundinfo->nindexes);
912 : 8953 : partindex = context->boundinfo->indexes[i];
913 : :
2466 alvherre@alvh.no-ip. 914 [ + + ]: 8953 : if (partindex < 0)
915 : : {
916 : : /*
917 : : * In range partitioning cases, if a partition index is -1 it
918 : : * means that the bound at the offset is the upper bound for a
919 : : * range not covered by any partition (other than a possible
920 : : * default partition). In hash partitioning, the same means no
921 : : * partition has been defined for the corresponding remainder
922 : : * value.
923 : : *
924 : : * In either case, the value is still part of the queried range of
925 : : * values, so mark to scan the default partition if one exists.
926 : : */
927 : 1057 : scan_default |= partition_bound_has_default(context->boundinfo);
928 : 1057 : continue;
929 : : }
930 : :
931 : 7896 : result = bms_add_member(result, partindex);
932 : : }
933 : :
934 : : /* Add the null and/or default partition if needed and present. */
2951 935 [ + + ]: 8466 : if (final_result->scan_null)
936 : : {
937 [ - + ]: 140 : Assert(context->strategy == PARTITION_STRATEGY_LIST);
938 [ - + ]: 140 : Assert(partition_bound_accepts_nulls(context->boundinfo));
939 : 140 : result = bms_add_member(result, context->boundinfo->null_index);
940 : : }
2466 941 [ + + ]: 8466 : if (scan_default)
942 : : {
2951 943 [ + + - + ]: 611 : Assert(context->strategy == PARTITION_STRATEGY_LIST ||
944 : : context->strategy == PARTITION_STRATEGY_RANGE);
945 [ - + ]: 611 : Assert(partition_bound_has_default(context->boundinfo));
946 : 611 : result = bms_add_member(result, context->boundinfo->default_index);
947 : : }
948 : :
949 : 8466 : return result;
950 : : }
951 : :
952 : : /*
953 : : * gen_partprune_steps_internal
954 : : * Processes 'clauses' to generate a List of partition pruning steps. We
955 : : * return NIL when no steps were generated.
956 : : *
957 : : * These partition pruning steps come in 2 forms; operator steps and combine
958 : : * steps.
959 : : *
960 : : * Operator steps (PartitionPruneStepOp) contain details of clauses that we
961 : : * determined that we can use for partition pruning. These contain details of
962 : : * the expression which is being compared to the partition key and the
963 : : * comparison function.
964 : : *
965 : : * Combine steps (PartitionPruneStepCombine) instruct the partition pruning
966 : : * code how it should produce a single set of partitions from multiple input
967 : : * operator and other combine steps. A PARTPRUNE_COMBINE_INTERSECT type
968 : : * combine step will merge its input steps to produce a result which only
969 : : * contains the partitions which are present in all of the input operator
970 : : * steps. A PARTPRUNE_COMBINE_UNION combine step will produce a result that
971 : : * has all of the partitions from each of the input operator steps.
972 : : *
973 : : * For BoolExpr clauses, each argument is processed recursively. Steps
974 : : * generated from processing an OR BoolExpr will be combined using
975 : : * PARTPRUNE_COMBINE_UNION. AND BoolExprs get combined using
976 : : * PARTPRUNE_COMBINE_INTERSECT.
977 : : *
978 : : * Otherwise, the list of clauses we receive we assume to be mutually ANDed.
979 : : * We generate all of the pruning steps we can based on these clauses and then
980 : : * at the end, if we have more than 1 step, we combine each step with a
981 : : * PARTPRUNE_COMBINE_INTERSECT combine step. Single steps are returned as-is.
982 : : *
983 : : * If we find clauses that are mutually contradictory, or contradictory with
984 : : * the partitioning constraint, or a pseudoconstant clause that contains
985 : : * false, we set context->contradictory to true and return NIL (that is, no
986 : : * pruning steps). Caller should consider all partitions as pruned in that
987 : : * case.
988 : : */
989 : : static List *
990 : 20716 : gen_partprune_steps_internal(GeneratePruningStepsContext *context,
991 : : List *clauses)
992 : : {
2545 tgl@sss.pgh.pa.us 993 : 20716 : PartitionScheme part_scheme = context->rel->part_scheme;
994 : : List *keyclauses[PARTITION_MAX_KEYS];
2951 alvherre@alvh.no-ip. 995 : 20716 : Bitmapset *nullkeys = NULL,
996 : 20716 : *notnullkeys = NULL;
997 : 20716 : bool generate_opsteps = false;
998 : 20716 : List *result = NIL;
999 : : ListCell *lc;
1000 : :
1001 : : /*
1002 : : * If this partitioned relation has a default partition and is itself a
1003 : : * partition (as evidenced by partition_qual being not NIL), we first
1004 : : * check if the clauses contradict the partition constraint. If they do,
1005 : : * there's no need to generate any steps as it'd already be proven that no
1006 : : * partitions need to be scanned.
1007 : : *
1008 : : * This is a measure of last resort only to be used because the default
1009 : : * partition cannot be pruned using the steps generated from clauses that
1010 : : * contradict the parent's partition constraint; regular pruning, which is
1011 : : * cheaper, is sufficient when no default partition exists.
1012 : : */
2457 1013 [ + + + + ]: 26620 : if (partition_bound_has_default(context->rel->boundinfo) &&
1014 : 5904 : predicate_refuted_by(context->rel->partition_qual, clauses, false))
1015 : : {
1016 : 235 : context->contradictory = true;
1017 : 235 : return NIL;
1018 : : }
1019 : :
2951 1020 : 20481 : memset(keyclauses, 0, sizeof(keyclauses));
1021 [ + - + + : 49013 : foreach(lc, clauses)
+ + ]
1022 : : {
1023 : 28650 : Expr *clause = (Expr *) lfirst(lc);
1024 : : int i;
1025 : :
1026 : : /* Look through RestrictInfo, if any */
1027 [ + + ]: 28650 : if (IsA(clause, RestrictInfo))
2883 tgl@sss.pgh.pa.us 1028 : 11282 : clause = ((RestrictInfo *) clause)->clause;
1029 : :
1030 : : /* Constant-false-or-null is contradictory */
1031 [ + + ]: 28650 : if (IsA(clause, Const) &&
1032 [ + - ]: 68 : (((Const *) clause)->constisnull ||
1033 [ + - ]: 68 : !DatumGetBool(((Const *) clause)->constvalue)))
1034 : : {
2545 1035 : 68 : context->contradictory = true;
2883 1036 : 118 : return NIL;
1037 : : }
1038 : :
1039 : : /* Get the BoolExpr's out of the way. */
2951 alvherre@alvh.no-ip. 1040 [ + + ]: 28582 : if (IsA(clause, BoolExpr))
1041 : : {
1042 : : /*
1043 : : * Generate steps for arguments.
1044 : : *
1045 : : * While steps generated for the arguments themselves will be
1046 : : * added to context->steps during recursion and will be evaluated
1047 : : * independently, collect their step IDs to be stored in the
1048 : : * combine step we'll be creating.
1049 : : */
2653 tgl@sss.pgh.pa.us 1050 [ + + ]: 2454 : if (is_orclause(clause))
2951 alvherre@alvh.no-ip. 1051 : 1604 : {
1052 : 1604 : List *arg_stepids = NIL;
1053 : 1604 : bool all_args_contradictory = true;
1054 : : ListCell *lc1;
1055 : :
1056 : : /*
1057 : : * We can share the outer context area with the recursive
1058 : : * call, but contradictory had better not be true yet.
1059 : : */
2545 tgl@sss.pgh.pa.us 1060 [ - + ]: 1604 : Assert(!context->contradictory);
1061 : :
1062 : : /*
1063 : : * Get pruning step for each arg. If we get contradictory for
1064 : : * all args, it means the OR expression is false as a whole.
1065 : : */
2951 alvherre@alvh.no-ip. 1066 [ + - + + : 5057 : foreach(lc1, ((BoolExpr *) clause)->args)
+ + ]
1067 : : {
1068 : 3453 : Expr *arg = lfirst(lc1);
1069 : : bool arg_contradictory;
1070 : : List *argsteps;
1071 : :
2545 tgl@sss.pgh.pa.us 1072 : 3453 : argsteps = gen_partprune_steps_internal(context,
2545 tgl@sss.pgh.pa.us 1073 :ECB (2066) : list_make1(arg));
2545 tgl@sss.pgh.pa.us 1074 :CBC 3453 : arg_contradictory = context->contradictory;
1075 : : /* Keep context->contradictory clear till we're done */
1076 : 3453 : context->contradictory = false;
1077 : :
1078 [ + + ]: 3453 : if (arg_contradictory)
1079 : : {
1080 : : /* Just ignore self-contradictory arguments. */
1081 : 230 : continue;
1082 : : }
1083 : : else
2951 alvherre@alvh.no-ip. 1084 : 3223 : all_args_contradictory = false;
1085 : :
1086 [ + + ]: 3223 : if (argsteps != NIL)
1087 : : {
1088 : : /*
1089 : : * gen_partprune_steps_internal() always adds a single
1090 : : * combine step when it generates multiple steps, so
1091 : : * here we can just pay attention to the last one in
1092 : : * the list. If it just generated one, then the last
1093 : : * one in the list is still the one we want.
1094 : : */
1853 drowley@postgresql.o 1095 : 2763 : PartitionPruneStep *last = llast(argsteps);
1096 : :
1097 : 2763 : arg_stepids = lappend_int(arg_stepids, last->step_id);
1098 : : }
1099 : : else
1100 : : {
1101 : : PartitionPruneStep *orstep;
1102 : :
1103 : : /*
1104 : : * The arg didn't contain a clause matching this
1105 : : * partition key. We cannot prune using such an arg.
1106 : : * To indicate that to the pruning code, we must
1107 : : * construct a dummy PartitionPruneStepCombine whose
1108 : : * source_stepids is set to an empty List.
1109 : : */
2951 alvherre@alvh.no-ip. 1110 : 460 : orstep = gen_prune_step_combine(context, NIL,
1111 : : PARTPRUNE_COMBINE_UNION);
1112 : 460 : arg_stepids = lappend_int(arg_stepids, orstep->step_id);
1113 : : }
1114 : : }
1115 : :
1116 : : /* If all the OR arms are contradictory, we can stop */
2545 tgl@sss.pgh.pa.us 1117 [ - + ]: 1604 : if (all_args_contradictory)
1118 : : {
2545 tgl@sss.pgh.pa.us 1119 :UBC 0 : context->contradictory = true;
2951 alvherre@alvh.no-ip. 1120 : 0 : return NIL;
1121 : : }
1122 : :
2951 alvherre@alvh.no-ip. 1123 [ + - ]:CBC 1604 : if (arg_stepids != NIL)
1124 : : {
1125 : : PartitionPruneStep *step;
1126 : :
1127 : 1604 : step = gen_prune_step_combine(context, arg_stepids,
1128 : : PARTPRUNE_COMBINE_UNION);
1129 : 1604 : result = lappend(result, step);
1130 : : }
1131 : 1604 : continue;
1132 : : }
2653 tgl@sss.pgh.pa.us 1133 [ + + ]: 850 : else if (is_andclause(clause))
2951 alvherre@alvh.no-ip. 1134 : 650 : {
1135 : 650 : List *args = ((BoolExpr *) clause)->args;
1136 : : List *argsteps;
1137 : :
1138 : : /*
1139 : : * args may itself contain clauses of arbitrary type, so just
1140 : : * recurse and later combine the component partitions sets
1141 : : * using a combine step.
1142 : : */
2545 tgl@sss.pgh.pa.us 1143 : 650 : argsteps = gen_partprune_steps_internal(context, args);
1144 : :
1145 : : /* If any AND arm is contradictory, we can stop immediately */
1146 [ - + ]: 650 : if (context->contradictory)
2951 alvherre@alvh.no-ip. 1147 :UBC 0 : return NIL;
1148 : :
1149 : : /*
1150 : : * gen_partprune_steps_internal() always adds a single combine
1151 : : * step when it generates multiple steps, so here we can just
1152 : : * pay attention to the last one in the list. If it just
1153 : : * generated one, then the last one in the list is still the
1154 : : * one we want.
1155 : : */
1853 drowley@postgresql.o 1156 [ + + ]:CBC 650 : if (argsteps != NIL)
1157 : 480 : result = lappend(result, llast(argsteps));
1158 : :
2951 alvherre@alvh.no-ip. 1159 : 650 : continue;
1160 : : }
1161 : :
1162 : : /*
1163 : : * Fall-through for a NOT clause, which if it's a Boolean clause,
1164 : : * will be handled in match_clause_to_partition_key(). We
1165 : : * currently don't perform any pruning for more complex NOT
1166 : : * clauses.
1167 : : */
1168 : : }
1169 : :
1170 : : /*
1171 : : * See if we can match this clause to any of the partition keys.
1172 : : */
1173 [ + + ]: 36704 : for (i = 0; i < part_scheme->partnatts; i++)
1174 : : {
2545 tgl@sss.pgh.pa.us 1175 : 29748 : Expr *partkey = linitial(context->rel->partexprs[i]);
2951 alvherre@alvh.no-ip. 1176 : 29748 : bool clause_is_not_null = false;
1177 : 29748 : PartClauseInfo *pc = NULL;
1178 : 29748 : List *clause_steps = NIL;
1179 : :
2545 tgl@sss.pgh.pa.us 1180 [ + + + + : 29748 : switch (match_clause_to_partition_key(context,
+ + - ]
1181 : : clause, partkey, i,
1182 : : &clause_is_not_null,
1183 : : &pc, &clause_steps))
1184 : : {
2951 alvherre@alvh.no-ip. 1185 : 15338 : case PARTCLAUSE_MATCH_CLAUSE:
1186 [ - + ]: 15338 : Assert(pc != NULL);
1187 : :
1188 : : /*
1189 : : * Since we only allow strict operators, check for any
1190 : : * contradicting IS NULL.
1191 : : */
1192 [ + + ]: 15338 : if (bms_is_member(i, nullkeys))
1193 : : {
2545 tgl@sss.pgh.pa.us 1194 : 5 : context->contradictory = true;
2951 alvherre@alvh.no-ip. 1195 : 50 : return NIL;
1196 : : }
1197 : 15333 : generate_opsteps = true;
1198 : 15333 : keyclauses[i] = lappend(keyclauses[i], pc);
1199 : 15333 : break;
1200 : :
1201 : 1870 : case PARTCLAUSE_MATCH_NULLNESS:
1202 [ + + ]: 1870 : if (!clause_is_not_null)
1203 : : {
1204 : : /*
1205 : : * check for conflicting IS NOT NULL as well as
1206 : : * contradicting strict clauses
1207 : : */
2107 efujita@postgresql.o 1208 [ + + ]: 1385 : if (bms_is_member(i, notnullkeys) ||
1209 [ + + ]: 1380 : keyclauses[i] != NIL)
1210 : : {
2545 tgl@sss.pgh.pa.us 1211 : 25 : context->contradictory = true;
2951 alvherre@alvh.no-ip. 1212 : 25 : return NIL;
1213 : : }
1214 : 1360 : nullkeys = bms_add_member(nullkeys, i);
1215 : : }
1216 : : else
1217 : : {
1218 : : /* check for conflicting IS NULL */
1219 [ - + ]: 485 : if (bms_is_member(i, nullkeys))
1220 : : {
2545 tgl@sss.pgh.pa.us 1221 :UBC 0 : context->contradictory = true;
2951 alvherre@alvh.no-ip. 1222 : 0 : return NIL;
1223 : : }
2951 alvherre@alvh.no-ip. 1224 :CBC 485 : notnullkeys = bms_add_member(notnullkeys, i);
1225 : : }
1226 : 1845 : break;
1227 : :
1228 : 485 : case PARTCLAUSE_MATCH_STEPS:
1229 [ - + ]: 485 : Assert(clause_steps != NIL);
1230 : 485 : result = list_concat(result, clause_steps);
1231 : 485 : break;
1232 : :
1233 : 20 : case PARTCLAUSE_MATCH_CONTRADICT:
1234 : : /* We've nothing more to do if a contradiction was found. */
2545 tgl@sss.pgh.pa.us 1235 : 20 : context->contradictory = true;
2951 alvherre@alvh.no-ip. 1236 : 20 : return NIL;
1237 : :
1238 : 10376 : case PARTCLAUSE_NOMATCH:
1239 : :
1240 : : /*
1241 : : * Clause didn't match this key, but it might match the
1242 : : * next one.
1243 : : */
1244 : 10376 : continue;
1245 : :
1246 : 1659 : case PARTCLAUSE_UNSUPPORTED:
1247 : : /* This clause cannot be used for pruning. */
1248 : 1659 : break;
1249 : : }
1250 : :
1251 : : /* done; go check the next clause. */
1252 : 19322 : break;
1253 : : }
1254 : : }
1255 : :
1256 : : /*-----------
1257 : : * Now generate some (more) pruning steps. We have three strategies:
1258 : : *
1259 : : * 1) Generate pruning steps based on IS NULL clauses:
1260 : : * a) For list partitioning, null partition keys can only be found in
1261 : : * the designated null-accepting partition, so if there are IS NULL
1262 : : * clauses containing partition keys we should generate a pruning
1263 : : * step that gets rid of all partitions but that one. We can
1264 : : * disregard any OpExpr we may have found.
1265 : : * b) For range partitioning, only the default partition can contain
1266 : : * NULL values, so the same rationale applies.
1267 : : * c) For hash partitioning, we only apply this strategy if we have
1268 : : * IS NULL clauses for all the keys. Strategy 2 below will take
1269 : : * care of the case where some keys have OpExprs and others have
1270 : : * IS NULL clauses.
1271 : : *
1272 : : * 2) If not, generate steps based on OpExprs we have (if any).
1273 : : *
1274 : : * 3) If this doesn't work either, we may be able to generate steps to
1275 : : * prune just the null-accepting partition (if one exists), if we have
1276 : : * IS NOT NULL clauses for all partition keys.
1277 : : */
2850 1278 [ + + ]: 20363 : if (!bms_is_empty(nullkeys) &&
1279 [ + + ]: 985 : (part_scheme->strategy == PARTITION_STRATEGY_LIST ||
1280 [ + + ]: 475 : part_scheme->strategy == PARTITION_STRATEGY_RANGE ||
1281 [ + - ]: 370 : (part_scheme->strategy == PARTITION_STRATEGY_HASH &&
1282 [ + + ]: 370 : bms_num_members(nullkeys) == part_scheme->partnatts)))
2951 1283 : 655 : {
1284 : : PartitionPruneStep *step;
1285 : :
1286 : : /* Strategy 1 */
2850 1287 : 655 : step = gen_prune_step_op(context, InvalidStrategy,
1288 : : false, NIL, NIL, nullkeys);
1289 : 655 : result = lappend(result, step);
1290 : : }
1291 [ + + ]: 19708 : else if (generate_opsteps)
1292 : : {
1293 : : List *opsteps;
1294 : :
1295 : : /* Strategy 2 */
1853 drowley@postgresql.o 1296 : 13198 : opsteps = gen_prune_steps_from_opexps(context, keyclauses, nullkeys);
1297 : 13198 : result = list_concat(result, opsteps);
1298 : : }
2850 alvherre@alvh.no-ip. 1299 [ + + ]: 6510 : else if (bms_num_members(notnullkeys) == part_scheme->partnatts)
1300 : : {
1301 : : PartitionPruneStep *step;
1302 : :
1303 : : /* Strategy 3 */
1304 : 150 : step = gen_prune_step_op(context, InvalidStrategy,
1305 : : false, NIL, NIL, NULL);
1306 : 150 : result = lappend(result, step);
1307 : : }
1308 : :
1309 : : /*
1310 : : * Finally, if there are multiple steps, since the 'clauses' are mutually
1311 : : * ANDed, add an INTERSECT step to combine the partition sets resulting
1312 : : * from them and append it to the result list.
1313 : : */
2951 1314 [ + + ]: 20363 : if (list_length(result) > 1)
1315 : : {
1316 : 1470 : List *step_ids = NIL;
1317 : : PartitionPruneStep *final;
1318 : :
1319 [ + - + + : 5325 : foreach(lc, result)
+ + ]
1320 : : {
1321 : 3855 : PartitionPruneStep *step = lfirst(lc);
1322 : :
1323 : 3855 : step_ids = lappend_int(step_ids, step->step_id);
1324 : : }
1325 : :
1853 drowley@postgresql.o 1326 : 1470 : final = gen_prune_step_combine(context, step_ids,
1327 : : PARTPRUNE_COMBINE_INTERSECT);
1328 : 1470 : result = lappend(result, final);
1329 : : }
1330 : :
2951 alvherre@alvh.no-ip. 1331 : 20363 : return result;
1332 : : }
1333 : :
1334 : : /*
1335 : : * gen_prune_step_op
1336 : : * Generate a pruning step for a specific operator
1337 : : *
1338 : : * The step is assigned a unique step identifier and added to context's 'steps'
1339 : : * list.
1340 : : */
1341 : : static PartitionPruneStep *
1342 : 15393 : gen_prune_step_op(GeneratePruningStepsContext *context,
1343 : : StrategyNumber opstrategy, bool op_is_ne,
1344 : : List *exprs, List *cmpfns,
1345 : : Bitmapset *nullkeys)
1346 : : {
1347 : 15393 : PartitionPruneStepOp *opstep = makeNode(PartitionPruneStepOp);
1348 : :
1349 : 15393 : opstep->step.step_id = context->next_step_id++;
1350 : :
1351 : : /*
1352 : : * For clauses that contain an <> operator, set opstrategy to
1353 : : * InvalidStrategy to signal get_matching_list_bounds to do the right
1354 : : * thing.
1355 : : */
2938 1356 [ + + ]: 15393 : opstep->opstrategy = op_is_ne ? InvalidStrategy : opstrategy;
2951 1357 [ - + ]: 15393 : Assert(list_length(exprs) == list_length(cmpfns));
1358 : 15393 : opstep->exprs = exprs;
1359 : 15393 : opstep->cmpfns = cmpfns;
1360 : 15393 : opstep->nullkeys = nullkeys;
1361 : :
1362 : 15393 : context->steps = lappend(context->steps, opstep);
1363 : :
1364 : 15393 : return (PartitionPruneStep *) opstep;
1365 : : }
1366 : :
1367 : : /*
1368 : : * gen_prune_step_combine
1369 : : * Generate a pruning step for a combination of several other steps
1370 : : *
1371 : : * The step is assigned a unique step identifier and added to context's
1372 : : * 'steps' list.
1373 : : */
1374 : : static PartitionPruneStep *
1375 : 3534 : gen_prune_step_combine(GeneratePruningStepsContext *context,
1376 : : List *source_stepids,
1377 : : PartitionPruneCombineOp combineOp)
1378 : : {
1379 : 3534 : PartitionPruneStepCombine *cstep = makeNode(PartitionPruneStepCombine);
1380 : :
1381 : 3534 : cstep->step.step_id = context->next_step_id++;
1382 : 3534 : cstep->combineOp = combineOp;
1383 : 3534 : cstep->source_stepids = source_stepids;
1384 : :
1385 : 3534 : context->steps = lappend(context->steps, cstep);
1386 : :
1387 : 3534 : return (PartitionPruneStep *) cstep;
1388 : : }
1389 : :
1390 : : /*
1391 : : * gen_prune_steps_from_opexps
1392 : : * Generate and return a list of PartitionPruneStepOp that are based on
1393 : : * OpExpr and BooleanTest clauses that have been matched to the partition
1394 : : * key.
1395 : : *
1396 : : * 'keyclauses' is an array of List pointers, indexed by the partition key's
1397 : : * index. Each List element in the array can contain clauses that match to
1398 : : * the corresponding partition key column. Partition key columns without any
1399 : : * matched clauses will have an empty List.
1400 : : *
1401 : : * Some partitioning strategies allow pruning to still occur when we only have
1402 : : * clauses for a prefix of the partition key columns, for example, RANGE
1403 : : * partitioning. Other strategies, such as HASH partitioning, require clauses
1404 : : * for all partition key columns.
1405 : : *
1406 : : * When we return multiple pruning steps here, it's up to the caller to add a
1407 : : * relevant "combine" step to combine the returned steps. This is not done
1408 : : * here as callers may wish to include additional pruning steps before
1409 : : * combining them all.
1410 : : */
1411 : : static List *
2545 tgl@sss.pgh.pa.us 1412 : 13198 : gen_prune_steps_from_opexps(GeneratePruningStepsContext *context,
1413 : : List **keyclauses, Bitmapset *nullkeys)
1414 : : {
1415 : 13198 : PartitionScheme part_scheme = context->rel->part_scheme;
2951 alvherre@alvh.no-ip. 1416 : 13198 : List *opsteps = NIL;
1417 : : List *btree_clauses[BTMaxStrategyNumber + 1],
1418 : : *hash_clauses[HTMaxStrategyNumber + 1];
1419 : : int i;
1420 : : ListCell *lc;
1421 : :
1422 : 13198 : memset(btree_clauses, 0, sizeof(btree_clauses));
1423 : 13198 : memset(hash_clauses, 0, sizeof(hash_clauses));
1424 [ + + ]: 25835 : for (i = 0; i < part_scheme->partnatts; i++)
1425 : : {
1426 : 15188 : List *clauselist = keyclauses[i];
1427 : 15188 : bool consider_next_key = true;
1428 : :
1429 : : /*
1430 : : * For range partitioning, if we have no clauses for the current key,
1431 : : * we can't consider any later keys either, so we can stop here.
1432 : : */
1433 [ + + + + ]: 15188 : if (part_scheme->strategy == PARTITION_STRATEGY_RANGE &&
1434 : : clauselist == NIL)
1435 : 530 : break;
1436 : :
1437 : : /*
1438 : : * For hash partitioning, if a column doesn't have the necessary
1439 : : * equality clause, there should be an IS NULL clause, otherwise
1440 : : * pruning is not possible.
1441 : : */
1442 [ + + + + ]: 14658 : if (part_scheme->strategy == PARTITION_STRATEGY_HASH &&
1443 [ + + ]: 645 : clauselist == NIL && !bms_is_member(i, nullkeys))
1853 drowley@postgresql.o 1444 : 60 : return NIL;
1445 : :
2951 alvherre@alvh.no-ip. 1446 [ + + + + : 29631 : foreach(lc, clauselist)
+ + ]
1447 : : {
1448 : 15033 : PartClauseInfo *pc = (PartClauseInfo *) lfirst(lc);
1449 : : Oid lefttype,
1450 : : righttype;
1451 : :
1452 : : /* Look up the operator's btree/hash strategy number. */
1453 [ + + ]: 15033 : if (pc->op_strategy == InvalidStrategy)
1454 : 525 : get_op_opfamily_properties(pc->opno,
1455 : 525 : part_scheme->partopfamily[i],
1456 : : false,
1457 : : &pc->op_strategy,
1458 : : &lefttype,
1459 : : &righttype);
1460 : :
1461 [ + + - ]: 15033 : switch (part_scheme->strategy)
1462 : : {
1463 : 14134 : case PARTITION_STRATEGY_LIST:
1464 : : case PARTITION_STRATEGY_RANGE:
2107 efujita@postgresql.o 1465 : 28268 : btree_clauses[pc->op_strategy] =
1466 : 14134 : lappend(btree_clauses[pc->op_strategy], pc);
1467 : :
1468 : : /*
1469 : : * We can't consider subsequent partition keys if the
1470 : : * clause for the current key contains a non-inclusive
1471 : : * operator.
1472 : : */
1473 [ + + ]: 14134 : if (pc->op_strategy == BTLessStrategyNumber ||
1474 [ + + ]: 12622 : pc->op_strategy == BTGreaterStrategyNumber)
1475 : 2096 : consider_next_key = false;
1476 : 14134 : break;
1477 : :
2951 alvherre@alvh.no-ip. 1478 : 899 : case PARTITION_STRATEGY_HASH:
1479 [ - + ]: 899 : if (pc->op_strategy != HTEqualStrategyNumber)
2951 alvherre@alvh.no-ip. 1480 [ # # ]:UBC 0 : elog(ERROR, "invalid clause for hash partitioning");
2951 alvherre@alvh.no-ip. 1481 :CBC 1798 : hash_clauses[pc->op_strategy] =
1482 : 899 : lappend(hash_clauses[pc->op_strategy], pc);
1483 : 899 : break;
1484 : :
2951 alvherre@alvh.no-ip. 1485 :UBC 0 : default:
1486 [ # # ]: 0 : elog(ERROR, "invalid partition strategy: %c",
1487 : : part_scheme->strategy);
1488 : : break;
1489 : : }
1490 : : }
1491 : :
1492 : : /*
1493 : : * If we've decided that clauses for subsequent partition keys
1494 : : * wouldn't be useful for pruning, don't search any further.
1495 : : */
2951 alvherre@alvh.no-ip. 1496 [ + + ]:CBC 14598 : if (!consider_next_key)
1497 : 1961 : break;
1498 : : }
1499 : :
1500 : : /*
1501 : : * Now, we have divided clauses according to their operator strategies.
1502 : : * Check for each strategy if we can generate pruning step(s) by
1503 : : * collecting a list of expressions whose values will constitute a vector
1504 : : * that can be used as a lookup key by a partition bound searching
1505 : : * function.
1506 : : */
1507 [ + + - ]: 13138 : switch (part_scheme->strategy)
1508 : : {
1509 : 12664 : case PARTITION_STRATEGY_LIST:
1510 : : case PARTITION_STRATEGY_RANGE:
1511 : : {
1512 : 12664 : List *eq_clauses = btree_clauses[BTEqualStrategyNumber];
1513 : 12664 : List *le_clauses = btree_clauses[BTLessEqualStrategyNumber];
1514 : 12664 : List *ge_clauses = btree_clauses[BTGreaterEqualStrategyNumber];
1515 : : int strat;
1516 : :
1517 : : /*
1518 : : * For each clause under consideration for a given strategy,
1519 : : * we collect expressions from clauses for earlier keys, whose
1520 : : * operator strategy is inclusive, into a list called
1521 : : * 'prefix'. By appending the clause's own expression to the
1522 : : * 'prefix', we'll generate one step using the so generated
1523 : : * vector and assign the current strategy to it. Actually,
1524 : : * 'prefix' might contain multiple clauses for the same key,
1525 : : * in which case, we must generate steps for various
1526 : : * combinations of expressions of different keys, which
1527 : : * get_steps_using_prefix takes care of for us.
1528 : : */
1529 [ + + ]: 75984 : for (strat = 1; strat <= BTMaxStrategyNumber; strat++)
1530 : : {
1531 [ + + + + : 77404 : foreach(lc, btree_clauses[strat])
+ + ]
1532 : : {
1533 : 14124 : PartClauseInfo *pc = lfirst(lc);
1534 : : ListCell *eq_start;
1535 : : ListCell *le_start;
1536 : : ListCell *ge_start;
1537 : : ListCell *lc1;
1538 : 14124 : List *prefix = NIL;
1539 : : List *pc_steps;
2107 efujita@postgresql.o 1540 : 14124 : bool prefix_valid = true;
1541 : : bool pk_has_clauses;
1542 : : int keyno;
1543 : :
1544 : : /*
1545 : : * If this is a clause for the first partition key,
1546 : : * there are no preceding expressions; generate a
1547 : : * pruning step without a prefix.
1548 : : *
1549 : : * Note that we pass NULL for step_nullkeys, because
1550 : : * we don't search list/range partition bounds where
1551 : : * some keys are NULL.
1552 : : */
1553 [ + + ]: 14124 : if (pc->keyno == 0)
1554 : : {
1555 [ - + ]: 13534 : Assert(pc->op_strategy == strat);
1556 : 13534 : pc_steps = get_steps_using_prefix(context, strat,
1557 : 13534 : pc->op_is_ne,
1558 : : pc->expr,
1559 : : pc->cmpfn,
1560 : : NULL,
1561 : : NIL);
1562 : 13534 : opsteps = list_concat(opsteps, pc_steps);
1563 : 13534 : continue;
1564 : : }
1565 : :
2097 1566 : 590 : eq_start = list_head(eq_clauses);
1567 : 590 : le_start = list_head(le_clauses);
1568 : 590 : ge_start = list_head(ge_clauses);
1569 : :
1570 : : /*
1571 : : * We arrange clauses into prefix in ascending order
1572 : : * of their partition key numbers.
1573 : : */
1574 [ + + ]: 1310 : for (keyno = 0; keyno < pc->keyno; keyno++)
1575 : : {
1576 : 760 : pk_has_clauses = false;
1577 : :
1578 : : /*
1579 : : * Expressions from = clauses can always be in the
1580 : : * prefix, provided they're from an earlier key.
1581 : : */
1582 [ + + + + : 1375 : for_each_cell(lc1, eq_clauses, eq_start)
+ + ]
1583 : : {
1584 : 1145 : PartClauseInfo *eqpc = lfirst(lc1);
1585 : :
1586 [ + + ]: 1145 : if (eqpc->keyno == keyno)
1587 : : {
1588 : 615 : prefix = lappend(prefix, eqpc);
1589 : 615 : pk_has_clauses = true;
1590 : : }
1591 : : else
1592 : : {
1593 [ - + ]: 530 : Assert(eqpc->keyno > keyno);
2951 alvherre@alvh.no-ip. 1594 : 530 : break;
1595 : : }
1596 : : }
2097 efujita@postgresql.o 1597 : 760 : eq_start = lc1;
1598 : :
1599 : : /*
1600 : : * If we're generating steps for </<= strategy, we
1601 : : * can add other <= clauses to the prefix,
1602 : : * provided they're from an earlier key.
1603 : : */
1604 [ + + + + ]: 760 : if (strat == BTLessStrategyNumber ||
1605 : : strat == BTLessEqualStrategyNumber)
1606 : : {
1607 [ + + + + : 95 : for_each_cell(lc1, le_clauses, le_start)
+ + ]
1608 : : {
1609 : 25 : PartClauseInfo *lepc = lfirst(lc1);
1610 : :
1611 [ + + ]: 25 : if (lepc->keyno == keyno)
1612 : : {
1613 : 15 : prefix = lappend(prefix, lepc);
1614 : 15 : pk_has_clauses = true;
1615 : : }
1616 : : else
1617 : : {
1618 [ - + ]: 10 : Assert(lepc->keyno > keyno);
1619 : 10 : break;
1620 : : }
1621 : : }
1622 : 80 : le_start = lc1;
1623 : : }
1624 : :
1625 : : /*
1626 : : * If we're generating steps for >/>= strategy, we
1627 : : * can add other >= clauses to the prefix,
1628 : : * provided they're from an earlier key.
1629 : : */
1630 [ + + + + ]: 760 : if (strat == BTGreaterStrategyNumber ||
1631 : : strat == BTGreaterEqualStrategyNumber)
1632 : : {
1633 [ + + + - : 330 : for_each_cell(lc1, ge_clauses, ge_start)
+ + ]
1634 : : {
1635 : 250 : PartClauseInfo *gepc = lfirst(lc1);
1636 : :
1637 [ + + ]: 250 : if (gepc->keyno == keyno)
1638 : : {
1639 : 120 : prefix = lappend(prefix, gepc);
1640 : 120 : pk_has_clauses = true;
1641 : : }
1642 : : else
1643 : : {
1644 [ - + ]: 130 : Assert(gepc->keyno > keyno);
1645 : 130 : break;
1646 : : }
1647 : : }
1648 : 210 : ge_start = lc1;
1649 : : }
1650 : :
1651 : : /*
1652 : : * If this key has no clauses, prefix is not valid
1653 : : * anymore.
1654 : : */
1655 [ + + ]: 760 : if (!pk_has_clauses)
1656 : : {
2107 1657 : 40 : prefix_valid = false;
1658 : 40 : break;
1659 : : }
1660 : : }
1661 : :
1662 : : /*
1663 : : * If prefix_valid, generate PartitionPruneStepOps.
1664 : : * Otherwise, we would not find clauses for a valid
1665 : : * subset of the partition keys anymore for the
1666 : : * strategy; give up on generating partition pruning
1667 : : * steps further for the strategy.
1668 : : *
1669 : : * As mentioned above, if 'prefix' contains multiple
1670 : : * expressions for the same key, the following will
1671 : : * generate multiple steps, one for each combination
1672 : : * of the expressions for different keys.
1673 : : *
1674 : : * Note that we pass NULL for step_nullkeys, because
1675 : : * we don't search list/range partition bounds where
1676 : : * some keys are NULL.
1677 : : */
1678 [ + + ]: 590 : if (prefix_valid)
1679 : : {
1680 [ - + ]: 550 : Assert(pc->op_strategy == strat);
1681 : 550 : pc_steps = get_steps_using_prefix(context, strat,
1682 : 550 : pc->op_is_ne,
1683 : : pc->expr,
1684 : : pc->cmpfn,
1685 : : NULL,
1686 : : prefix);
1687 : 550 : opsteps = list_concat(opsteps, pc_steps);
1688 : : }
1689 : : else
1690 : 40 : break;
1691 : : }
1692 : : }
2951 alvherre@alvh.no-ip. 1693 : 12664 : break;
1694 : : }
1695 : :
1696 : 474 : case PARTITION_STRATEGY_HASH:
1697 : : {
1698 : 474 : List *eq_clauses = hash_clauses[HTEqualStrategyNumber];
1699 : :
1700 : : /* For hash partitioning, we have just the = strategy. */
1701 [ + - ]: 474 : if (eq_clauses != NIL)
1702 : : {
1703 : : PartClauseInfo *pc;
1704 : : List *pc_steps;
1705 : 474 : List *prefix = NIL;
1706 : : int last_keyno;
1707 : : ListCell *lc1;
1708 : :
1709 : : /*
1710 : : * Locate the clause for the greatest column. This may
1711 : : * not belong to the last partition key, but it is the
1712 : : * clause belonging to the last partition key we found a
1713 : : * clause for above.
1714 : : */
1715 : 474 : pc = llast(eq_clauses);
1716 : :
1717 : : /*
1718 : : * There might be multiple clauses which matched to that
1719 : : * partition key; find the first such clause. While at
1720 : : * it, add all the clauses before that one to 'prefix'.
1721 : : */
1722 : 474 : last_keyno = pc->keyno;
1723 [ + - + - : 889 : foreach(lc, eq_clauses)
+ - ]
1724 : : {
1725 : 889 : pc = lfirst(lc);
1726 [ + + ]: 889 : if (pc->keyno == last_keyno)
1727 : 474 : break;
1728 : 415 : prefix = lappend(prefix, pc);
1729 : : }
1730 : :
1731 : : /*
1732 : : * For each clause for the "last" column, after appending
1733 : : * the clause's own expression to the 'prefix', we'll
1734 : : * generate one step using the so generated vector and
1735 : : * assign = as its strategy. Actually, 'prefix' might
1736 : : * contain multiple clauses for the same key, in which
1737 : : * case, we must generate steps for various combinations
1738 : : * of expressions of different keys, which
1739 : : * get_steps_using_prefix will take care of for us.
1740 : : */
2486 tgl@sss.pgh.pa.us 1741 [ + - + + : 948 : for_each_cell(lc1, eq_clauses, lc)
+ + ]
1742 : : {
2951 alvherre@alvh.no-ip. 1743 : 474 : pc = lfirst(lc1);
1744 : :
1745 : : /*
1746 : : * Note that we pass nullkeys for step_nullkeys,
1747 : : * because we need to tell hash partition bound search
1748 : : * function which of the keys we found IS NULL clauses
1749 : : * for.
1750 : : */
1751 [ - + ]: 474 : Assert(pc->op_strategy == HTEqualStrategyNumber);
1752 : : pc_steps =
1753 : 474 : get_steps_using_prefix(context,
1754 : : HTEqualStrategyNumber,
1755 : : false,
1756 : : pc->expr,
1757 : : pc->cmpfn,
1758 : : nullkeys,
1759 : : prefix);
2458 tgl@sss.pgh.pa.us 1760 : 474 : opsteps = list_concat(opsteps, pc_steps);
1761 : : }
1762 : : }
2951 alvherre@alvh.no-ip. 1763 : 474 : break;
1764 : : }
1765 : :
2951 alvherre@alvh.no-ip. 1766 :UBC 0 : default:
1767 [ # # ]: 0 : elog(ERROR, "invalid partition strategy: %c",
1768 : : part_scheme->strategy);
1769 : : break;
1770 : : }
1771 : :
1853 drowley@postgresql.o 1772 :CBC 13138 : return opsteps;
1773 : : }
1774 : :
1775 : : /*
1776 : : * If the partition key has a collation, then the clause must have the same
1777 : : * input collation. If the partition key is non-collatable, we assume the
1778 : : * collation doesn't matter, because while collation wasn't considered when
1779 : : * performing partitioning, the clause still may have a collation assigned
1780 : : * due to the other input being of a collatable type.
1781 : : *
1782 : : * See also IndexCollMatchesExprColl.
1783 : : */
1784 : : #define PartCollMatchesExprColl(partcoll, exprcoll) \
1785 : : ((partcoll) == InvalidOid || (partcoll) == (exprcoll))
1786 : :
1787 : : /*
1788 : : * match_clause_to_partition_key
1789 : : * Attempt to match the given 'clause' with the specified partition key.
1790 : : *
1791 : : * Return value is:
1792 : : * * PARTCLAUSE_NOMATCH if the clause doesn't match this partition key (but
1793 : : * caller should keep trying, because it might match a subsequent key).
1794 : : * Output arguments: none set.
1795 : : *
1796 : : * * PARTCLAUSE_MATCH_CLAUSE if there is a match.
1797 : : * Output arguments: *pc is set to a PartClauseInfo constructed for the
1798 : : * matched clause.
1799 : : *
1800 : : * * PARTCLAUSE_MATCH_NULLNESS if there is a match, and the matched clause was
1801 : : * either a "a IS NULL" or "a IS NOT NULL" clause.
1802 : : * Output arguments: *clause_is_not_null is set to false in the former case
1803 : : * true otherwise.
1804 : : *
1805 : : * * PARTCLAUSE_MATCH_STEPS if there is a match.
1806 : : * Output arguments: *clause_steps is set to the list of recursively
1807 : : * generated steps for the clause.
1808 : : *
1809 : : * * PARTCLAUSE_MATCH_CONTRADICT if the clause is self-contradictory, ie
1810 : : * it provably returns FALSE or NULL.
1811 : : * Output arguments: none set.
1812 : : *
1813 : : * * PARTCLAUSE_UNSUPPORTED if the clause doesn't match this partition key
1814 : : * and couldn't possibly match any other one either, due to its form or
1815 : : * properties (such as containing a volatile function).
1816 : : * Output arguments: none set.
1817 : : *
1818 : : * Note that when pulling up a subquery, the clause operands may get wrapped
1819 : : * in PlaceHolderVars to enforce separate identity or as a result of outer
1820 : : * joins. We must strip such no-op PlaceHolderVars before comparing operands
1821 : : * to the partition key, otherwise the equal() checks will fail to recognize
1822 : : * valid matches. This is safe because the clauses here are always
1823 : : * relation-scan-level expressions, where a PlaceHolderVar with empty
1824 : : * phnullingrels is effectively a no-op. Stripping may also bring separate
1825 : : * RelabelType nodes into adjacency, so we must loop when peeling those.
1826 : : */
1827 : : static PartClauseMatchStatus
2545 tgl@sss.pgh.pa.us 1828 : 29748 : match_clause_to_partition_key(GeneratePruningStepsContext *context,
1829 : : Expr *clause, const Expr *partkey, int partkeyidx,
1830 : : bool *clause_is_not_null, PartClauseInfo **pc,
1831 : : List **clause_steps)
1832 : : {
1833 : : PartClauseMatchStatus boolmatchstatus;
1834 : 29748 : PartitionScheme part_scheme = context->rel->part_scheme;
2951 alvherre@alvh.no-ip. 1835 : 29748 : Oid partopfamily = part_scheme->partopfamily[partkeyidx],
1836 : 29748 : partcoll = part_scheme->partcollation[partkeyidx];
1837 : : Expr *expr;
1838 : : bool notclause;
1839 : :
1840 : : /*
1841 : : * Recognize specially shaped clauses that match a Boolean partition key.
1842 : : */
2489 drowley@postgresql.o 1843 : 29748 : boolmatchstatus = match_boolean_partition_clause(partopfamily, clause,
1844 : : partkey, &expr,
1845 : : ¬clause);
1846 : :
1847 [ + + ]: 29748 : if (boolmatchstatus == PARTCLAUSE_MATCH_CLAUSE)
1848 : : {
1849 : : PartClauseInfo *partclause;
1850 : :
1851 : : /*
1852 : : * For bool tests in the form of partkey IS NOT true and IS NOT false,
1853 : : * we invert these clauses. Effectively, "partkey IS NOT true"
1854 : : * becomes "partkey IS false OR partkey IS NULL". We do this by
1855 : : * building an OR BoolExpr and forming a clause just like that and
1856 : : * punt it off to gen_partprune_steps_internal() to generate pruning
1857 : : * steps.
1858 : : */
792 1859 [ + + ]: 570 : if (notclause)
1860 : : {
1861 : : List *new_clauses;
1862 : : List *or_clause;
805 1863 : 180 : BooleanTest *new_booltest = (BooleanTest *) copyObject(clause);
1864 : : NullTest *nulltest;
1865 : :
1866 : : /* We expect 'notclause' to only be set to true for BooleanTests */
1867 [ - + ]: 180 : Assert(IsA(clause, BooleanTest));
1868 : :
1869 : : /* reverse the bool test */
1870 [ + + ]: 180 : if (new_booltest->booltesttype == IS_NOT_TRUE)
1871 : 110 : new_booltest->booltesttype = IS_FALSE;
1872 [ + - ]: 70 : else if (new_booltest->booltesttype == IS_NOT_FALSE)
1873 : 70 : new_booltest->booltesttype = IS_TRUE;
1874 : : else
1875 : : {
1876 : : /*
1877 : : * We only expect match_boolean_partition_clause to return
1878 : : * PARTCLAUSE_MATCH_CLAUSE for IS_NOT_TRUE and IS_NOT_FALSE.
1879 : : */
805 drowley@postgresql.o 1880 :UBC 0 : Assert(false);
1881 : : }
1882 : :
805 drowley@postgresql.o 1883 :CBC 180 : nulltest = makeNode(NullTest);
1884 : 180 : nulltest->arg = copyObject(partkey);
1885 : 180 : nulltest->nulltesttype = IS_NULL;
1886 : 180 : nulltest->argisrow = false;
1887 : 180 : nulltest->location = -1;
1888 : :
1889 : 180 : new_clauses = list_make2(new_booltest, nulltest);
1890 : 180 : or_clause = list_make1(makeBoolExpr(OR_EXPR, new_clauses, -1));
1891 : :
1892 : : /* Finally, generate steps */
1893 : 180 : *clause_steps = gen_partprune_steps_internal(context, or_clause);
1894 : :
1895 [ - + ]: 180 : if (context->contradictory)
805 drowley@postgresql.o 1896 :UBC 0 : return PARTCLAUSE_MATCH_CONTRADICT; /* shouldn't happen */
805 drowley@postgresql.o 1897 [ - + ]:CBC 180 : else if (*clause_steps == NIL)
805 drowley@postgresql.o 1898 :UBC 0 : return PARTCLAUSE_UNSUPPORTED; /* step generation failed */
805 drowley@postgresql.o 1899 :CBC 180 : return PARTCLAUSE_MATCH_STEPS;
1900 : : }
1901 : :
145 michael@paquier.xyz 1902 :GNC 390 : partclause = palloc_object(PartClauseInfo);
2951 alvherre@alvh.no-ip. 1903 :CBC 390 : partclause->keyno = partkeyidx;
1904 : : /* Do pruning with the Boolean equality operator. */
1905 : 390 : partclause->opno = BooleanEqualOperator;
805 drowley@postgresql.o 1906 : 390 : partclause->op_is_ne = false;
2951 alvherre@alvh.no-ip. 1907 : 390 : partclause->expr = expr;
1908 : : /* We know that expr is of Boolean type. */
2545 tgl@sss.pgh.pa.us 1909 : 390 : partclause->cmpfn = part_scheme->partsupfunc[partkeyidx].fn_oid;
2951 alvherre@alvh.no-ip. 1910 : 390 : partclause->op_strategy = InvalidStrategy;
1911 : :
1912 : 390 : *pc = partclause;
1913 : :
1914 : 390 : return PARTCLAUSE_MATCH_CLAUSE;
1915 : : }
792 drowley@postgresql.o 1916 [ + + ]: 29178 : else if (boolmatchstatus == PARTCLAUSE_MATCH_NULLNESS)
1917 : : {
1918 : : /*
1919 : : * Handle IS UNKNOWN and IS NOT UNKNOWN. These just logically
1920 : : * translate to IS NULL and IS NOT NULL.
1921 : : */
1922 : 80 : *clause_is_not_null = notclause;
1923 : 80 : return PARTCLAUSE_MATCH_NULLNESS;
1924 : : }
2951 alvherre@alvh.no-ip. 1925 [ + + + - ]: 53638 : else if (IsA(clause, OpExpr) &&
1926 : 24540 : list_length(((OpExpr *) clause)->args) == 2)
1927 : : {
1928 : 24540 : OpExpr *opclause = (OpExpr *) clause;
1929 : : Expr *leftop,
1930 : : *rightop;
1931 : : Oid opno,
1932 : : op_lefttype,
1933 : : op_righttype,
1934 : 24540 : negator = InvalidOid;
1935 : : Oid cmpfn;
1936 : : int op_strategy;
1937 : 24540 : bool is_opne_listp = false;
1938 : : PartClauseInfo *partclause;
1939 : :
1940 : 24540 : leftop = (Expr *) get_leftop(clause);
26 rguo@postgresql.org 1941 : 24540 : leftop = (Expr *) strip_noop_phvs((Node *) leftop);
1942 [ + + ]: 24960 : while (IsA(leftop, RelabelType))
2951 alvherre@alvh.no-ip. 1943 : 420 : leftop = ((RelabelType *) leftop)->arg;
1944 : 24540 : rightop = (Expr *) get_rightop(clause);
26 rguo@postgresql.org 1945 : 24540 : rightop = (Expr *) strip_noop_phvs((Node *) rightop);
1946 [ - + ]: 24540 : while (IsA(rightop, RelabelType))
2951 alvherre@alvh.no-ip. 1947 :UBC 0 : rightop = ((RelabelType *) rightop)->arg;
2883 tgl@sss.pgh.pa.us 1948 :CBC 24540 : opno = opclause->opno;
1949 : :
1950 : : /* check if the clause matches this partition key */
2951 alvherre@alvh.no-ip. 1951 [ + + ]: 24540 : if (equal(leftop, partkey))
1952 : 15033 : expr = rightop;
1953 [ + + ]: 9507 : else if (equal(rightop, partkey))
1954 : : {
1955 : : /*
1956 : : * It's only useful if we can commute the operator to put the
1957 : : * partkey on the left. If we can't, the clause can be deemed
1958 : : * UNSUPPORTED. Even if its leftop matches some later partkey, we
1959 : : * now know it has Vars on the right, so it's no use.
1960 : : */
2883 tgl@sss.pgh.pa.us 1961 : 1071 : opno = get_commutator(opno);
1962 [ - + ]: 1071 : if (!OidIsValid(opno))
2951 alvherre@alvh.no-ip. 1963 :UBC 0 : return PARTCLAUSE_UNSUPPORTED;
2883 tgl@sss.pgh.pa.us 1964 :CBC 1071 : expr = leftop;
1965 : : }
1966 : : else
1967 : : /* clause does not match this partition key, but perhaps next. */
2951 alvherre@alvh.no-ip. 1968 : 8436 : return PARTCLAUSE_NOMATCH;
1969 : :
1970 : : /*
1971 : : * Partition key match also requires collation match. There may be
1972 : : * multiple partkeys with the same expression but different
1973 : : * collations, so failure is NOMATCH.
1974 : : */
1975 [ + + + + ]: 16104 : if (!PartCollMatchesExprColl(partcoll, opclause->inputcollid))
1976 : 50 : return PARTCLAUSE_NOMATCH;
1977 : :
1978 : : /*
1979 : : * See if the operator is relevant to the partitioning opfamily.
1980 : : *
1981 : : * Normally we only care about operators that are listed as being part
1982 : : * of the partitioning operator family. But there is one exception:
1983 : : * the not-equals operators are not listed in any operator family
1984 : : * whatsoever, but their negators (equality) are. We can use one of
1985 : : * those if we find it, but only for list partitioning.
1986 : : *
1987 : : * Note: we report NOMATCH on failure if the negator isn't the
1988 : : * equality operator for the partkey's opfamily as other partkeys may
1989 : : * have the same expression but different opfamily. That's unlikely,
1990 : : * but not much more so than duplicate expressions with different
1991 : : * collations.
1992 : : */
2883 tgl@sss.pgh.pa.us 1993 [ + + ]: 16054 : if (op_in_opfamily(opno, partopfamily))
1994 : : {
1995 : 15764 : get_op_opfamily_properties(opno, partopfamily, false,
1996 : : &op_strategy, &op_lefttype,
1997 : : &op_righttype);
1998 : : }
1999 : : else
2000 : : {
2001 : : /* not supported for anything apart from LIST partitioned tables */
2951 alvherre@alvh.no-ip. 2002 [ + + ]: 290 : if (part_scheme->strategy != PARTITION_STRATEGY_LIST)
805 drowley@postgresql.o 2003 : 80 : return PARTCLAUSE_UNSUPPORTED;
2004 : :
2005 : : /* See if the negator is equality */
2883 tgl@sss.pgh.pa.us 2006 : 210 : negator = get_negator(opno);
2951 alvherre@alvh.no-ip. 2007 [ + - + + ]: 210 : if (OidIsValid(negator) && op_in_opfamily(negator, partopfamily))
2008 : : {
2009 : 200 : get_op_opfamily_properties(negator, partopfamily, false,
2010 : : &op_strategy, &op_lefttype,
2011 : : &op_righttype);
2938 2012 [ + - ]: 200 : if (op_strategy == BTEqualStrategyNumber)
2013 : 200 : is_opne_listp = true; /* bingo */
2014 : : }
2015 : :
2016 : : /* Nope, it's not <> either. */
2951 2017 [ + + ]: 210 : if (!is_opne_listp)
2883 tgl@sss.pgh.pa.us 2018 : 10 : return PARTCLAUSE_NOMATCH;
2019 : : }
2020 : :
2021 : : /*
2022 : : * Only allow strict operators. This will guarantee nulls are
2023 : : * filtered. (This test is likely useless, since btree and hash
2024 : : * comparison operators are generally strict.)
2025 : : */
2545 2026 [ - + ]: 15964 : if (!op_strict(opno))
2545 tgl@sss.pgh.pa.us 2027 :UBC 0 : return PARTCLAUSE_UNSUPPORTED;
2028 : :
2029 : : /*
2030 : : * OK, we have a match to the partition key and a suitable operator.
2031 : : * Examine the other argument to see if it's usable for pruning.
2032 : : *
2033 : : * In most of these cases, we can return UNSUPPORTED because the same
2034 : : * failure would occur no matter which partkey it's matched to. (In
2035 : : * particular, now that we've successfully matched one side of the
2036 : : * opclause to a partkey, there is no chance that matching the other
2037 : : * side to another partkey will produce a usable result, since that'd
2038 : : * mean there are Vars on both sides.)
2039 : : *
2040 : : * Also, if we reject an argument for a target-dependent reason, set
2041 : : * appropriate fields of *context to report that. We postpone these
2042 : : * tests until after matching the partkey and the operator, so as to
2043 : : * reduce the odds of setting the context fields for clauses that do
2044 : : * not end up contributing to pruning steps.
2045 : : *
2046 : : * First, check for non-Const argument. (We assume that any immutable
2047 : : * subexpression will have been folded to a Const already.)
2048 : : */
2545 tgl@sss.pgh.pa.us 2049 [ + + ]:CBC 15964 : if (!IsA(expr, Const))
2050 : : {
2051 : : Bitmapset *paramids;
2052 : :
2053 : : /*
2054 : : * When pruning in the planner, we only support pruning using
2055 : : * comparisons to constants. We cannot prune on the basis of
2056 : : * anything that's not immutable. (Note that has_mutable_arg and
2057 : : * has_exec_param do not get set for this target value.)
2058 : : */
2059 [ + + ]: 1772 : if (context->target == PARTTARGET_PLANNER)
2060 : 631 : return PARTCLAUSE_UNSUPPORTED;
2061 : :
2062 : : /*
2063 : : * We can never prune using an expression that contains Vars.
2064 : : */
2065 [ + + ]: 1141 : if (contain_var_clause((Node *) expr))
2066 : 34 : return PARTCLAUSE_UNSUPPORTED;
2067 : :
2068 : : /*
2069 : : * And we must reject anything containing a volatile function.
2070 : : * Stable functions are OK though.
2071 : : */
2072 [ - + ]: 1107 : if (contain_volatile_functions((Node *) expr))
2545 tgl@sss.pgh.pa.us 2073 :UBC 0 : return PARTCLAUSE_UNSUPPORTED;
2074 : :
2075 : : /*
2076 : : * See if there are any exec Params. If so, we can only use this
2077 : : * expression during per-scan pruning.
2078 : : */
2545 tgl@sss.pgh.pa.us 2079 :CBC 1107 : paramids = pull_exec_paramids(expr);
2080 [ + + ]: 1107 : if (!bms_is_empty(paramids))
2081 : : {
2082 : 702 : context->has_exec_param = true;
2083 [ + + ]: 702 : if (context->target != PARTTARGET_EXEC)
2084 : 346 : return PARTCLAUSE_UNSUPPORTED;
2085 : : }
2086 : : else
2087 : : {
2088 : : /* It's potentially usable, but mutable */
2089 : 405 : context->has_mutable_arg = true;
2090 : : }
2091 : : }
2092 : :
2093 : : /*
2094 : : * Check whether the comparison operator itself is immutable. (We
2095 : : * assume anything that's in a btree or hash opclass is at least
2096 : : * stable, but we need to check for immutability.)
2097 : : */
2098 [ + + ]: 14953 : if (op_volatile(opno) != PROVOLATILE_IMMUTABLE)
2099 : : {
2100 : 30 : context->has_mutable_op = true;
2101 : :
2102 : : /*
2103 : : * When pruning in the planner, we cannot prune with mutable
2104 : : * operators.
2105 : : */
2106 [ + + ]: 30 : if (context->target == PARTTARGET_PLANNER)
2107 : 5 : return PARTCLAUSE_UNSUPPORTED;
2108 : : }
2109 : :
2110 : : /*
2111 : : * Now find the procedure to use, based on the types. If the clause's
2112 : : * other argument is of the same type as the partitioning opclass's
2113 : : * declared input type, we can use the procedure cached in
2114 : : * PartitionKey. If not, search for a cross-type one in the same
2115 : : * opfamily; if one doesn't exist, report no match.
2116 : : */
2938 alvherre@alvh.no-ip. 2117 [ + + ]: 14948 : if (op_righttype == part_scheme->partopcintype[partkeyidx])
2118 : 14753 : cmpfn = part_scheme->partsupfunc[partkeyidx].fn_oid;
2119 : : else
2120 : : {
2951 2121 [ + - - ]: 195 : switch (part_scheme->strategy)
2122 : : {
2123 : : /*
2124 : : * For range and list partitioning, we need the ordering
2125 : : * procedure with lefttype being the partition key's type,
2126 : : * and righttype the clause's operator's right type.
2127 : : */
2128 : 195 : case PARTITION_STRATEGY_LIST:
2129 : : case PARTITION_STRATEGY_RANGE:
2130 : : cmpfn =
2131 : 195 : get_opfamily_proc(part_scheme->partopfamily[partkeyidx],
2132 : 195 : part_scheme->partopcintype[partkeyidx],
2133 : : op_righttype, BTORDER_PROC);
2134 : 195 : break;
2135 : :
2136 : : /*
2137 : : * For hash partitioning, we need the hashing procedure
2138 : : * for the clause's type.
2139 : : */
2951 alvherre@alvh.no-ip. 2140 :UBC 0 : case PARTITION_STRATEGY_HASH:
2141 : : cmpfn =
2142 : 0 : get_opfamily_proc(part_scheme->partopfamily[partkeyidx],
2143 : : op_righttype, op_righttype,
2144 : : HASHEXTENDED_PROC);
2145 : 0 : break;
2146 : :
2147 : 0 : default:
2148 [ # # ]: 0 : elog(ERROR, "invalid partition strategy: %c",
2149 : : part_scheme->strategy);
2150 : : cmpfn = InvalidOid; /* keep compiler quiet */
2151 : : break;
2152 : : }
2153 : :
2951 alvherre@alvh.no-ip. 2154 [ - + ]:CBC 195 : if (!OidIsValid(cmpfn))
2883 tgl@sss.pgh.pa.us 2155 :UBC 0 : return PARTCLAUSE_NOMATCH;
2156 : : }
2157 : :
2158 : : /*
2159 : : * Build the clause, passing the negator if applicable.
2160 : : */
145 michael@paquier.xyz 2161 :GNC 14948 : partclause = palloc_object(PartClauseInfo);
2951 alvherre@alvh.no-ip. 2162 :CBC 14948 : partclause->keyno = partkeyidx;
2163 [ + + ]: 14948 : if (is_opne_listp)
2164 : : {
2165 [ - + ]: 170 : Assert(OidIsValid(negator));
2166 : 170 : partclause->opno = negator;
2167 : 170 : partclause->op_is_ne = true;
2938 2168 : 170 : partclause->op_strategy = InvalidStrategy;
2169 : : }
2170 : : else
2171 : : {
2883 tgl@sss.pgh.pa.us 2172 : 14778 : partclause->opno = opno;
2938 alvherre@alvh.no-ip. 2173 : 14778 : partclause->op_is_ne = false;
2174 : 14778 : partclause->op_strategy = op_strategy;
2175 : : }
2951 2176 : 14948 : partclause->expr = expr;
2177 : 14948 : partclause->cmpfn = cmpfn;
2178 : :
2179 : 14948 : *pc = partclause;
2180 : :
2181 : 14948 : return PARTCLAUSE_MATCH_CLAUSE;
2182 : : }
2183 [ + + ]: 4558 : else if (IsA(clause, ScalarArrayOpExpr))
2184 : : {
2185 : 636 : ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
2186 : 636 : Oid saop_op = saop->opno;
2187 : 636 : Oid saop_coll = saop->inputcollid;
2188 : 636 : Expr *leftop = (Expr *) linitial(saop->args),
2189 : 636 : *rightop = (Expr *) lsecond(saop->args);
2190 : : List *elem_exprs,
2191 : : *elem_clauses;
2192 : : ListCell *lc1;
2193 : :
26 rguo@postgresql.org 2194 : 636 : leftop = (Expr *) strip_noop_phvs((Node *) leftop);
2195 [ + + ]: 776 : while (IsA(leftop, RelabelType))
2951 alvherre@alvh.no-ip. 2196 : 140 : leftop = ((RelabelType *) leftop)->arg;
2197 : :
2198 : : /* check if the LHS matches this partition key */
2199 [ + + + + ]: 636 : if (!equal(leftop, partkey) ||
2200 [ - + ]: 180 : !PartCollMatchesExprColl(partcoll, saop->inputcollid))
2201 : 188 : return PARTCLAUSE_NOMATCH;
2202 : :
2203 : : /*
2204 : : * See if the operator is relevant to the partitioning opfamily.
2205 : : *
2206 : : * In case of NOT IN (..), we get a '<>', which we handle if list
2207 : : * partitioning is in use and we're able to confirm that it's negator
2208 : : * is a btree equality operator belonging to the partitioning operator
2209 : : * family. As above, report NOMATCH for non-matching operator.
2210 : : */
2211 [ + + ]: 448 : if (!op_in_opfamily(saop_op, partopfamily))
2212 : : {
2213 : : Oid negator;
2214 : :
2215 [ + + ]: 60 : if (part_scheme->strategy != PARTITION_STRATEGY_LIST)
2883 tgl@sss.pgh.pa.us 2216 : 10 : return PARTCLAUSE_NOMATCH;
2217 : :
2951 alvherre@alvh.no-ip. 2218 : 50 : negator = get_negator(saop_op);
2219 [ + - + + ]: 50 : if (OidIsValid(negator) && op_in_opfamily(negator, partopfamily))
2220 : 10 : {
2221 : : int strategy;
2222 : : Oid lefttype,
2223 : : righttype;
2224 : :
2225 : 10 : get_op_opfamily_properties(negator, partopfamily,
2226 : : false, &strategy,
2227 : : &lefttype, &righttype);
2228 [ - + ]: 10 : if (strategy != BTEqualStrategyNumber)
2883 tgl@sss.pgh.pa.us 2229 :UBC 0 : return PARTCLAUSE_NOMATCH;
2230 : : }
2231 : : else
2883 tgl@sss.pgh.pa.us 2232 :CBC 40 : return PARTCLAUSE_NOMATCH; /* no useful negator */
2233 : : }
2234 : :
2235 : : /*
2236 : : * Only allow strict operators. This will guarantee nulls are
2237 : : * filtered. (This test is likely useless, since btree and hash
2238 : : * comparison operators are generally strict.)
2239 : : */
2545 2240 [ - + ]: 398 : if (!op_strict(saop_op))
2545 tgl@sss.pgh.pa.us 2241 :UBC 0 : return PARTCLAUSE_UNSUPPORTED;
2242 : :
2243 : : /*
2244 : : * OK, we have a match to the partition key and a suitable operator.
2245 : : * Examine the array argument to see if it's usable for pruning. This
2246 : : * is identical to the logic for a plain OpExpr.
2247 : : */
2545 tgl@sss.pgh.pa.us 2248 [ + + ]:CBC 398 : if (!IsA(rightop, Const))
2249 : : {
2250 : : Bitmapset *paramids;
2251 : :
2252 : : /*
2253 : : * When pruning in the planner, we only support pruning using
2254 : : * comparisons to constants. We cannot prune on the basis of
2255 : : * anything that's not immutable. (Note that has_mutable_arg and
2256 : : * has_exec_param do not get set for this target value.)
2257 : : */
2258 [ + + ]: 101 : if (context->target == PARTTARGET_PLANNER)
2259 : 48 : return PARTCLAUSE_UNSUPPORTED;
2260 : :
2261 : : /*
2262 : : * We can never prune using an expression that contains Vars.
2263 : : */
2264 [ - + ]: 53 : if (contain_var_clause((Node *) rightop))
2545 tgl@sss.pgh.pa.us 2265 :UBC 0 : return PARTCLAUSE_UNSUPPORTED;
2266 : :
2267 : : /*
2268 : : * And we must reject anything containing a volatile function.
2269 : : * Stable functions are OK though.
2270 : : */
2545 tgl@sss.pgh.pa.us 2271 [ - + ]:CBC 53 : if (contain_volatile_functions((Node *) rightop))
2545 tgl@sss.pgh.pa.us 2272 :UBC 0 : return PARTCLAUSE_UNSUPPORTED;
2273 : :
2274 : : /*
2275 : : * See if there are any exec Params. If so, we can only use this
2276 : : * expression during per-scan pruning.
2277 : : */
2545 tgl@sss.pgh.pa.us 2278 :CBC 53 : paramids = pull_exec_paramids(rightop);
2279 [ + + ]: 53 : if (!bms_is_empty(paramids))
2280 : : {
2281 : 10 : context->has_exec_param = true;
2282 [ + + ]: 10 : if (context->target != PARTTARGET_EXEC)
2283 : 5 : return PARTCLAUSE_UNSUPPORTED;
2284 : : }
2285 : : else
2286 : : {
2287 : : /* It's potentially usable, but mutable */
2288 : 43 : context->has_mutable_arg = true;
2289 : : }
2290 : : }
2291 : :
2292 : : /*
2293 : : * Check whether the comparison operator itself is immutable. (We
2294 : : * assume anything that's in a btree or hash opclass is at least
2295 : : * stable, but we need to check for immutability.)
2296 : : */
2297 [ + + ]: 345 : if (op_volatile(saop_op) != PROVOLATILE_IMMUTABLE)
2298 : : {
2299 : 30 : context->has_mutable_op = true;
2300 : :
2301 : : /*
2302 : : * When pruning in the planner, we cannot prune with mutable
2303 : : * operators.
2304 : : */
2305 [ + + ]: 30 : if (context->target == PARTTARGET_PLANNER)
2306 : 15 : return PARTCLAUSE_UNSUPPORTED;
2307 : : }
2308 : :
2309 : : /*
2310 : : * Examine the contents of the array argument.
2311 : : */
2951 alvherre@alvh.no-ip. 2312 : 330 : elem_exprs = NIL;
2313 [ + + ]: 330 : if (IsA(rightop, Const))
2314 : : {
2315 : : /*
2316 : : * For a constant array, convert the elements to a list of Const
2317 : : * nodes, one for each array element (excepting nulls).
2318 : : */
2883 tgl@sss.pgh.pa.us 2319 : 282 : Const *arr = (Const *) rightop;
2320 : : ArrayType *arrval;
2321 : : int16 elemlen;
2322 : : bool elembyval;
2323 : : char elemalign;
2324 : : Datum *elem_values;
2325 : : bool *elem_nulls;
2326 : : int num_elems,
2327 : : i;
2328 : :
2329 : : /* If the array itself is null, the saop returns null */
2461 2330 [ + + ]: 282 : if (arr->constisnull)
2331 : 20 : return PARTCLAUSE_MATCH_CONTRADICT;
2332 : :
2333 : 267 : arrval = DatumGetArrayTypeP(arr->constvalue);
2951 alvherre@alvh.no-ip. 2334 : 267 : get_typlenbyvalalign(ARR_ELEMTYPE(arrval),
2335 : : &elemlen, &elembyval, &elemalign);
2336 : 267 : deconstruct_array(arrval,
2337 : : ARR_ELEMTYPE(arrval),
2338 : : elemlen, elembyval, elemalign,
2339 : : &elem_values, &elem_nulls,
2340 : : &num_elems);
2341 [ + + ]: 938 : for (i = 0; i < num_elems; i++)
2342 : : {
2343 : : Const *elem_expr;
2344 : :
2345 : : /*
2346 : : * A null array element must lead to a null comparison result,
2347 : : * since saop_op is known strict. We can ignore it in the
2348 : : * useOr case, but otherwise it implies self-contradiction.
2349 : : */
2350 [ + + ]: 676 : if (elem_nulls[i])
2351 : : {
2883 tgl@sss.pgh.pa.us 2352 [ + + ]: 45 : if (saop->useOr)
2353 : 40 : continue;
2354 : 5 : return PARTCLAUSE_MATCH_CONTRADICT;
2355 : : }
2356 : :
2951 alvherre@alvh.no-ip. 2357 : 631 : elem_expr = makeConst(ARR_ELEMTYPE(arrval), -1,
2358 : : arr->constcollid, elemlen,
2359 : 631 : elem_values[i], false, elembyval);
2360 : 631 : elem_exprs = lappend(elem_exprs, elem_expr);
2361 : : }
2362 : : }
2918 2363 [ + + ]: 48 : else if (IsA(rightop, ArrayExpr))
2364 : : {
2951 2365 : 43 : ArrayExpr *arrexpr = castNode(ArrayExpr, rightop);
2366 : :
2367 : : /*
2368 : : * For a nested ArrayExpr, we don't know how to get the actual
2369 : : * scalar values out into a flat list, so we give up doing
2370 : : * anything with this ScalarArrayOpExpr.
2371 : : */
2372 [ - + ]: 43 : if (arrexpr->multidims)
2951 alvherre@alvh.no-ip. 2373 :UBC 0 : return PARTCLAUSE_UNSUPPORTED;
2374 : :
2375 : : /*
2376 : : * Otherwise, we can just use the list of element values.
2377 : : */
2951 alvherre@alvh.no-ip. 2378 :CBC 43 : elem_exprs = arrexpr->elements;
2379 : : }
2380 : : else
2381 : : {
2382 : : /* Give up on any other clause types. */
2918 2383 : 5 : return PARTCLAUSE_UNSUPPORTED;
2384 : : }
2385 : :
2386 : : /*
2387 : : * Now generate a list of clauses, one for each array element, of the
2388 : : * form leftop saop_op elem_expr
2389 : : */
2951 2390 : 305 : elem_clauses = NIL;
2391 [ + - + + : 1030 : foreach(lc1, elem_exprs)
+ + ]
2392 : : {
2393 : : Expr *elem_clause;
2394 : :
2395 : 725 : elem_clause = make_opclause(saop_op, BOOLOID, false,
1308 drowley@postgresql.o 2396 : 725 : leftop, lfirst(lc1),
2397 : : InvalidOid, saop_coll);
2951 alvherre@alvh.no-ip. 2398 : 725 : elem_clauses = lappend(elem_clauses, elem_clause);
2399 : : }
2400 : :
2401 : : /*
2402 : : * If we have an ANY clause and multiple elements, now turn the list
2403 : : * of clauses into an OR expression.
2404 : : */
2405 [ + + + + ]: 305 : if (saop->useOr && list_length(elem_clauses) > 1)
2918 2406 : 240 : elem_clauses = list_make1(makeBoolExpr(OR_EXPR, elem_clauses, -1));
2407 : :
2408 : : /* Finally, generate steps */
2545 tgl@sss.pgh.pa.us 2409 : 305 : *clause_steps = gen_partprune_steps_internal(context, elem_clauses);
2410 [ - + ]: 305 : if (context->contradictory)
2918 alvherre@alvh.no-ip. 2411 :UBC 0 : return PARTCLAUSE_MATCH_CONTRADICT;
2918 alvherre@alvh.no-ip. 2412 [ - + ]:CBC 305 : else if (*clause_steps == NIL)
2918 alvherre@alvh.no-ip. 2413 :UBC 0 : return PARTCLAUSE_UNSUPPORTED; /* step generation failed */
2918 alvherre@alvh.no-ip. 2414 :CBC 305 : return PARTCLAUSE_MATCH_STEPS;
2415 : : }
2951 2416 [ + + ]: 3922 : else if (IsA(clause, NullTest))
2417 : : {
2418 : 3222 : NullTest *nulltest = (NullTest *) clause;
2419 : 3222 : Expr *arg = nulltest->arg;
2420 : :
26 rguo@postgresql.org 2421 : 3222 : arg = (Expr *) strip_noop_phvs((Node *) arg);
2422 [ - + ]: 3222 : while (IsA(arg, RelabelType))
2951 alvherre@alvh.no-ip. 2423 :UBC 0 : arg = ((RelabelType *) arg)->arg;
2424 : :
2425 : : /* Does arg match with this partition key column? */
2951 alvherre@alvh.no-ip. 2426 [ + + ]:CBC 3222 : if (!equal(arg, partkey))
2427 : 1432 : return PARTCLAUSE_NOMATCH;
2428 : :
2883 tgl@sss.pgh.pa.us 2429 : 1790 : *clause_is_not_null = (nulltest->nulltesttype == IS_NOT_NULL);
2430 : :
2951 alvherre@alvh.no-ip. 2431 : 1790 : return PARTCLAUSE_MATCH_NULLNESS;
2432 : : }
2433 : :
2434 : : /*
2435 : : * If we get here then the return value depends on the result of the
2436 : : * match_boolean_partition_clause call above. If the call returned
2437 : : * PARTCLAUSE_UNSUPPORTED then we're either not dealing with a bool qual
2438 : : * or the bool qual is not suitable for pruning. Since the qual didn't
2439 : : * match up to any of the other qual types supported here, then trying to
2440 : : * match it against any other partition key is a waste of time, so just
2441 : : * return PARTCLAUSE_UNSUPPORTED. If the qual just couldn't be matched to
2442 : : * this partition key, then it may match another, so return
2443 : : * PARTCLAUSE_NOMATCH. The only other value that
2444 : : * match_boolean_partition_clause can return is PARTCLAUSE_MATCH_CLAUSE,
2445 : : * and since that value was already dealt with above, then we can just
2446 : : * return boolmatchstatus.
2447 : : */
2489 drowley@postgresql.o 2448 : 700 : return boolmatchstatus;
2449 : : }
2450 : :
2451 : : /*
2452 : : * get_steps_using_prefix
2453 : : * Generate a list of PartitionPruneStepOps based on the given input.
2454 : : *
2455 : : * 'step_lastexpr' and 'step_lastcmpfn' are the Expr and comparison function
2456 : : * belonging to the final partition key that we have a clause for. 'prefix'
2457 : : * is a list of PartClauseInfos for partition key numbers prior to the given
2458 : : * 'step_lastexpr' and 'step_lastcmpfn'. 'prefix' may contain multiple
2459 : : * PartClauseInfos belonging to a single partition key. We will generate a
2460 : : * PartitionPruneStepOp for each combination of the given PartClauseInfos
2461 : : * using, at most, one PartClauseInfo per partition key.
2462 : : *
2463 : : * For LIST and RANGE partitioned tables, callers must ensure that
2464 : : * step_nullkeys is NULL, and that prefix contains at least one clause for
2465 : : * each of the partition keys prior to the key that 'step_lastexpr' and
2466 : : * 'step_lastcmpfn' belong to.
2467 : : *
2468 : : * For HASH partitioned tables, callers must ensure that 'prefix' contains at
2469 : : * least one clause for each of the partition keys apart from the final key
2470 : : * (the expr and comparison function for the final key are in 'step_lastexpr'
2471 : : * and 'step_lastcmpfn'). A bit set in step_nullkeys can substitute clauses
2472 : : * in the 'prefix' list for any given key. If a bit is set in 'step_nullkeys'
2473 : : * for a given key, then there must be no PartClauseInfo for that key in the
2474 : : * 'prefix' list.
2475 : : *
2476 : : * For each of the above cases, callers must ensure that PartClauseInfos in
2477 : : * 'prefix' are sorted in ascending order of keyno.
2478 : : */
2479 : : static List *
2951 alvherre@alvh.no-ip. 2480 : 14558 : get_steps_using_prefix(GeneratePruningStepsContext *context,
2481 : : StrategyNumber step_opstrategy,
2482 : : bool step_op_is_ne,
2483 : : Expr *step_lastexpr,
2484 : : Oid step_lastcmpfn,
2485 : : Bitmapset *step_nullkeys,
2486 : : List *prefix)
2487 : : {
2488 : : /* step_nullkeys must be empty for RANGE and LIST partitioned tables */
2107 efujita@postgresql.o 2489 [ + + - + ]: 14558 : Assert(step_nullkeys == NULL ||
2490 : : context->rel->part_scheme->strategy == PARTITION_STRATEGY_HASH);
2491 : :
2492 : : /*
2493 : : * No recursive processing is required when 'prefix' is an empty list.
2494 : : * This occurs when there is only 1 partition key column.
2495 : : */
1357 tgl@sss.pgh.pa.us 2496 [ + + ]: 14558 : if (prefix == NIL)
2497 : : {
2498 : : PartitionPruneStep *step;
2499 : :
2951 alvherre@alvh.no-ip. 2500 : 13713 : step = gen_prune_step_op(context,
2501 : : step_opstrategy,
2502 : : step_op_is_ne,
2951 alvherre@alvh.no-ip. 2503 :ECB (9169) : list_make1(step_lastexpr),
2504 : (9169) : list_make1_oid(step_lastcmpfn),
2505 : : step_nullkeys);
2951 alvherre@alvh.no-ip. 2506 :CBC 13713 : return list_make1(step);
2507 : : }
2508 : :
2509 : : /* Recurse to generate steps for every combination of clauses. */
2510 : 845 : return get_steps_using_prefix_recurse(context,
2511 : : step_opstrategy,
2512 : : step_op_is_ne,
2513 : : step_lastexpr,
2514 : : step_lastcmpfn,
2515 : : step_nullkeys,
2516 : : prefix,
2517 : : list_head(prefix),
2518 : : NIL, NIL);
2519 : : }
2520 : :
2521 : : /*
2522 : : * get_steps_using_prefix_recurse
2523 : : * Generate and return a list of PartitionPruneStepOps using the 'prefix'
2524 : : * list of PartClauseInfos starting at the 'start' cell.
2525 : : *
2526 : : * When 'prefix' contains multiple PartClauseInfos for a single partition key
2527 : : * we create a PartitionPruneStepOp for each combination of duplicated
2528 : : * PartClauseInfos. The returned list will contain a PartitionPruneStepOp
2529 : : * for each unique combination of input PartClauseInfos containing at most one
2530 : : * PartClauseInfo per partition key.
2531 : : *
2532 : : * 'prefix' is the input list of PartClauseInfos sorted by keyno.
2533 : : * 'start' marks the cell that searching the 'prefix' list should start from.
2534 : : * 'step_exprs' and 'step_cmpfns' each contains the expressions and cmpfns
2535 : : * we've generated so far from the clauses for the previous part keys.
2536 : : */
2537 : : static List *
2538 : 1155 : get_steps_using_prefix_recurse(GeneratePruningStepsContext *context,
2539 : : StrategyNumber step_opstrategy,
2540 : : bool step_op_is_ne,
2541 : : Expr *step_lastexpr,
2542 : : Oid step_lastcmpfn,
2543 : : Bitmapset *step_nullkeys,
2544 : : List *prefix,
2545 : : ListCell *start,
2546 : : List *step_exprs,
2547 : : List *step_cmpfns)
2548 : : {
2549 : 1155 : List *result = NIL;
2550 : : ListCell *lc;
2551 : : int cur_keyno;
2552 : : int final_keyno;
2553 : :
2554 : : /* Actually, recursion would be limited by PARTITION_MAX_KEYS. */
2555 : 1155 : check_stack_depth();
2556 : :
2557 [ - + ]: 1155 : Assert(start != NULL);
2558 : 1155 : cur_keyno = ((PartClauseInfo *) lfirst(start))->keyno;
936 drowley@postgresql.o 2559 : 1155 : final_keyno = ((PartClauseInfo *) llast(prefix))->keyno;
2560 : :
2561 : : /* Check if we need to recurse. */
2562 [ + + ]: 1155 : if (cur_keyno < final_keyno)
2563 : : {
2564 : : PartClauseInfo *pc;
2565 : : ListCell *next_start;
2566 : :
2567 : : /*
2568 : : * Find the first PartClauseInfo belonging to the next partition key,
2569 : : * the next recursive call must start iteration of the prefix list
2570 : : * from that point.
2571 : : */
2486 tgl@sss.pgh.pa.us 2572 [ + - + - : 600 : for_each_cell(lc, prefix, start)
+ - ]
2573 : : {
2951 alvherre@alvh.no-ip. 2574 : 600 : pc = lfirst(lc);
2575 : :
2576 [ + + ]: 600 : if (pc->keyno > cur_keyno)
2577 : 290 : break;
2578 : : }
2579 : :
2580 : : /* record where to start iterating in the next recursive call */
2581 : 290 : next_start = lc;
2582 : :
2583 : : /*
2584 : : * For each PartClauseInfo with keyno set to cur_keyno, add its expr
2585 : : * and cmpfn to step_exprs and step_cmpfns, respectively, and recurse
2586 : : * using 'next_start' as the starting point in the 'prefix' list.
2587 : : */
2486 tgl@sss.pgh.pa.us 2588 [ + - + - : 600 : for_each_cell(lc, prefix, start)
+ - ]
2589 : : {
2590 : : List *moresteps;
2591 : : List *step_exprs1,
2592 : : *step_cmpfns1;
2593 : :
2951 alvherre@alvh.no-ip. 2594 : 600 : pc = lfirst(lc);
2595 [ + + ]: 600 : if (pc->keyno == cur_keyno)
2596 : : {
2597 : : /* Leave the original step_exprs unmodified. */
2107 efujita@postgresql.o 2598 : 310 : step_exprs1 = list_copy(step_exprs);
2599 : 310 : step_exprs1 = lappend(step_exprs1, pc->expr);
2600 : :
2601 : : /* Leave the original step_cmpfns unmodified. */
2602 : 310 : step_cmpfns1 = list_copy(step_cmpfns);
2603 : 310 : step_cmpfns1 = lappend_oid(step_cmpfns1, pc->cmpfn);
2604 : : }
2605 : : else
2606 : : {
2607 : : /* check the 'prefix' list is sorted correctly */
2951 alvherre@alvh.no-ip. 2608 [ - + ]: 290 : Assert(pc->keyno > cur_keyno);
2609 : 290 : break;
2610 : : }
2611 : :
2612 : 310 : moresteps = get_steps_using_prefix_recurse(context,
2613 : : step_opstrategy,
2614 : : step_op_is_ne,
2615 : : step_lastexpr,
2616 : : step_lastcmpfn,
2617 : : step_nullkeys,
2618 : : prefix,
2619 : : next_start,
2620 : : step_exprs1,
2621 : : step_cmpfns1);
2622 : 310 : result = list_concat(result, moresteps);
2623 : :
2107 efujita@postgresql.o 2624 : 310 : list_free(step_exprs1);
2625 : 310 : list_free(step_cmpfns1);
2626 : : }
2627 : : }
2628 : : else
2629 : : {
2630 : : /*
2631 : : * End the current recursion cycle and start generating steps, one for
2632 : : * each clause with cur_keyno, which is all clauses from here onward
2633 : : * till the end of the list. Note that for hash partitioning,
2634 : : * step_nullkeys is allowed to be non-empty, in which case step_exprs
2635 : : * would only contain expressions for the partition keys that are not
2636 : : * specified in step_nullkeys.
2637 : : */
2638 [ + + - + ]: 865 : Assert(list_length(step_exprs) == cur_keyno ||
2639 : : !bms_is_empty(step_nullkeys));
2640 : :
2641 : : /*
2642 : : * Note also that for hash partitioning, each partition key should
2643 : : * have either equality clauses or an IS NULL clause, so if a
2644 : : * partition key doesn't have an expression, it would be specified in
2645 : : * step_nullkeys.
2646 : : */
2647 [ + + - + ]: 865 : Assert(context->rel->part_scheme->strategy
2648 : : != PARTITION_STRATEGY_HASH ||
2649 : : list_length(step_exprs) + 2 + bms_num_members(step_nullkeys) ==
2650 : : context->rel->part_scheme->partnatts);
2486 tgl@sss.pgh.pa.us 2651 [ + - + + : 1740 : for_each_cell(lc, prefix, start)
+ + ]
2652 : : {
2951 alvherre@alvh.no-ip. 2653 : 875 : PartClauseInfo *pc = lfirst(lc);
2654 : : PartitionPruneStep *step;
2655 : : List *step_exprs1,
2656 : : *step_cmpfns1;
2657 : :
2658 [ - + ]: 875 : Assert(pc->keyno == cur_keyno);
2659 : :
2660 : : /* Leave the original step_exprs unmodified. */
2661 : 875 : step_exprs1 = list_copy(step_exprs);
2662 : 875 : step_exprs1 = lappend(step_exprs1, pc->expr);
2663 : 875 : step_exprs1 = lappend(step_exprs1, step_lastexpr);
2664 : :
2665 : : /* Leave the original step_cmpfns unmodified. */
2666 : 875 : step_cmpfns1 = list_copy(step_cmpfns);
2667 : 875 : step_cmpfns1 = lappend_oid(step_cmpfns1, pc->cmpfn);
2668 : 875 : step_cmpfns1 = lappend_oid(step_cmpfns1, step_lastcmpfn);
2669 : :
2670 : 875 : step = gen_prune_step_op(context,
2671 : : step_opstrategy, step_op_is_ne,
2672 : : step_exprs1, step_cmpfns1,
2673 : : step_nullkeys);
2674 : 875 : result = lappend(result, step);
2675 : : }
2676 : : }
2677 : :
2678 : 1155 : return result;
2679 : : }
2680 : :
2681 : : /*
2682 : : * get_matching_hash_bounds
2683 : : * Determine offset of the hash bound matching the specified values,
2684 : : * considering that all the non-null values come from clauses containing
2685 : : * a compatible hash equality operator and any keys that are null come
2686 : : * from an IS NULL clause.
2687 : : *
2688 : : * Generally this function will return a single matching bound offset,
2689 : : * although if a partition has not been setup for a given modulus then we may
2690 : : * return no matches. If the number of clauses found don't cover the entire
2691 : : * partition key, then we'll need to return all offsets.
2692 : : *
2693 : : * 'opstrategy' if non-zero must be HTEqualStrategyNumber.
2694 : : *
2695 : : * 'values' contains Datums indexed by the partition key to use for pruning.
2696 : : *
2697 : : * 'nvalues', the number of Datums in the 'values' array.
2698 : : *
2699 : : * 'partsupfunc' contains partition hashing functions that can produce correct
2700 : : * hash for the type of the values contained in 'values'.
2701 : : *
2702 : : * 'nullkeys' is the set of partition keys that are null.
2703 : : */
2704 : : static PruneStepResult *
2705 : 261 : get_matching_hash_bounds(PartitionPruneContext *context,
2706 : : StrategyNumber opstrategy, const Datum *values, int nvalues,
2707 : : FmgrInfo *partsupfunc, Bitmapset *nullkeys)
2708 : : {
145 michael@paquier.xyz 2709 :GNC 261 : PruneStepResult *result = palloc0_object(PruneStepResult);
2951 alvherre@alvh.no-ip. 2710 :CBC 261 : PartitionBoundInfo boundinfo = context->boundinfo;
2711 : 261 : int *partindices = boundinfo->indexes;
2712 : 261 : int partnatts = context->partnatts;
2713 : : bool isnull[PARTITION_MAX_KEYS];
2714 : : int i;
2715 : : uint64 rowHash;
2716 : : int greatest_modulus;
2601 peter@eisentraut.org 2717 : 261 : Oid *partcollation = context->partcollation;
2718 : :
2951 alvherre@alvh.no-ip. 2719 [ - + ]: 261 : Assert(context->strategy == PARTITION_STRATEGY_HASH);
2720 : :
2721 : : /*
2722 : : * For hash partitioning we can only perform pruning based on equality
2723 : : * clauses to the partition key or IS NULL clauses. We also can only
2724 : : * prune if we got values for all keys.
2725 : : */
2726 [ + - ]: 261 : if (nvalues + bms_num_members(nullkeys) == partnatts)
2727 : : {
2728 : : /*
2729 : : * If there are any values, they must have come from clauses
2730 : : * containing an equality operator compatible with hash partitioning.
2731 : : */
2732 [ + + - + ]: 261 : Assert(opstrategy == HTEqualStrategyNumber || nvalues == 0);
2733 : :
2734 [ + + ]: 1066 : for (i = 0; i < partnatts; i++)
2735 : 805 : isnull[i] = bms_is_member(i, nullkeys);
2736 : :
2601 peter@eisentraut.org 2737 : 261 : rowHash = compute_partition_hash_value(partnatts, partsupfunc, partcollation,
2738 : : values, isnull);
2739 : :
1923 tgl@sss.pgh.pa.us 2740 : 261 : greatest_modulus = boundinfo->nindexes;
2951 alvherre@alvh.no-ip. 2741 [ + + ]: 261 : if (partindices[rowHash % greatest_modulus] >= 0)
2742 : 256 : result->bound_offsets =
2743 : 256 : bms_make_singleton(rowHash % greatest_modulus);
2744 : : }
2745 : : else
2746 : : {
2747 : : /* Report all valid offsets into the boundinfo->indexes array. */
2951 alvherre@alvh.no-ip. 2748 :UBC 0 : result->bound_offsets = bms_add_range(NULL, 0,
1923 tgl@sss.pgh.pa.us 2749 : 0 : boundinfo->nindexes - 1);
2750 : : }
2751 : :
2752 : : /*
2753 : : * There is neither a special hash null partition or the default hash
2754 : : * partition.
2755 : : */
2951 alvherre@alvh.no-ip. 2756 :CBC 261 : result->scan_null = result->scan_default = false;
2757 : :
2758 : 261 : return result;
2759 : : }
2760 : :
2761 : : /*
2762 : : * get_matching_list_bounds
2763 : : * Determine the offsets of list bounds matching the specified value,
2764 : : * according to the semantics of the given operator strategy
2765 : : *
2766 : : * scan_default will be set in the returned struct, if the default partition
2767 : : * needs to be scanned, provided one exists at all. scan_null will be set if
2768 : : * the special null-accepting partition needs to be scanned.
2769 : : *
2770 : : * 'opstrategy' if non-zero must be a btree strategy number.
2771 : : *
2772 : : * 'value' contains the value to use for pruning.
2773 : : *
2774 : : * 'nvalues', if non-zero, should be exactly 1, because of list partitioning.
2775 : : *
2776 : : * 'partsupfunc' contains the list partitioning comparison function to be used
2777 : : * to perform partition_list_bsearch
2778 : : *
2779 : : * 'nullkeys' is the set of partition keys that are null.
2780 : : */
2781 : : static PruneStepResult *
2782 : 5420 : get_matching_list_bounds(PartitionPruneContext *context,
2783 : : StrategyNumber opstrategy, Datum value, int nvalues,
2784 : : FmgrInfo *partsupfunc, Bitmapset *nullkeys)
2785 : : {
145 michael@paquier.xyz 2786 :GNC 5420 : PruneStepResult *result = palloc0_object(PruneStepResult);
2951 alvherre@alvh.no-ip. 2787 :CBC 5420 : PartitionBoundInfo boundinfo = context->boundinfo;
2788 : : int off,
2789 : : minoff,
2790 : : maxoff;
2791 : : bool is_equal;
2792 : 5420 : bool inclusive = false;
2793 : 5420 : Oid *partcollation = context->partcollation;
2794 : :
2795 [ - + ]: 5420 : Assert(context->strategy == PARTITION_STRATEGY_LIST);
2796 [ - + ]: 5420 : Assert(context->partnatts == 1);
2797 : :
2798 : 5420 : result->scan_null = result->scan_default = false;
2799 : :
2800 [ + + ]: 5420 : if (!bms_is_empty(nullkeys))
2801 : : {
2802 : : /*
2803 : : * Nulls may exist in only one partition - the partition whose
2804 : : * accepted set of values includes null or the default partition if
2805 : : * the former doesn't exist.
2806 : : */
2807 [ + + ]: 275 : if (partition_bound_accepts_nulls(boundinfo))
2808 : 195 : result->scan_null = true;
2809 : : else
2810 : 80 : result->scan_default = partition_bound_has_default(boundinfo);
2811 : 275 : return result;
2812 : : }
2813 : :
2814 : : /*
2815 : : * If there are no datums to compare keys with, but there are partitions,
2816 : : * just return the default partition if one exists.
2817 : : */
2818 [ - + ]: 5145 : if (boundinfo->ndatums == 0)
2819 : : {
2951 alvherre@alvh.no-ip. 2820 :UBC 0 : result->scan_default = partition_bound_has_default(boundinfo);
2821 : 0 : return result;
2822 : : }
2823 : :
2951 alvherre@alvh.no-ip. 2824 :CBC 5145 : minoff = 0;
2825 : 5145 : maxoff = boundinfo->ndatums - 1;
2826 : :
2827 : : /*
2828 : : * If there are no values to compare with the datums in boundinfo, it
2829 : : * means the caller asked for partitions for all non-null datums. Add
2830 : : * indexes of *all* partitions, including the default if any.
2831 : : */
2832 [ + + ]: 5145 : if (nvalues == 0)
2833 : : {
2836 2834 [ - + ]: 50 : Assert(boundinfo->ndatums > 0);
2951 2835 : 100 : result->bound_offsets = bms_add_range(NULL, 0,
2836 : 50 : boundinfo->ndatums - 1);
2837 : 50 : result->scan_default = partition_bound_has_default(boundinfo);
2838 : 50 : return result;
2839 : : }
2840 : :
2841 : : /* Special case handling of values coming from a <> operator clause. */
2842 [ + + ]: 5095 : if (opstrategy == InvalidStrategy)
2843 : : {
2844 : : /*
2845 : : * First match to all bounds. We'll remove any matching datums below.
2846 : : */
2836 2847 [ - + ]: 112 : Assert(boundinfo->ndatums > 0);
2951 2848 : 224 : result->bound_offsets = bms_add_range(NULL, 0,
2849 : 112 : boundinfo->ndatums - 1);
2850 : :
2851 : 112 : off = partition_list_bsearch(partsupfunc, partcollation, boundinfo,
2852 : : value, &is_equal);
2853 [ + + + + ]: 112 : if (off >= 0 && is_equal)
2854 : : {
2855 : :
2856 : : /* We have a match. Remove from the result. */
2857 [ - + ]: 87 : Assert(boundinfo->indexes[off] >= 0);
2858 : 87 : result->bound_offsets = bms_del_member(result->bound_offsets,
2859 : : off);
2860 : : }
2861 : :
2862 : : /* Always include the default partition if any. */
2863 : 112 : result->scan_default = partition_bound_has_default(boundinfo);
2864 : :
2865 : 112 : return result;
2866 : : }
2867 : :
2868 : : /*
2869 : : * With range queries, always include the default list partition, because
2870 : : * list partitions divide the key space in a discontinuous manner, not all
2871 : : * values in the given range will have a partition assigned. This may not
2872 : : * technically be true for some data types (e.g. integer types), however,
2873 : : * we currently lack any sort of infrastructure to provide us with proofs
2874 : : * that would allow us to do anything smarter here.
2875 : : */
2876 [ + + ]: 4983 : if (opstrategy != BTEqualStrategyNumber)
2877 : 803 : result->scan_default = partition_bound_has_default(boundinfo);
2878 : :
2879 [ + + + + : 4983 : switch (opstrategy)
+ - ]
2880 : : {
2881 : 4180 : case BTEqualStrategyNumber:
2882 : 4180 : off = partition_list_bsearch(partsupfunc,
2883 : : partcollation,
2884 : : boundinfo, value,
2885 : : &is_equal);
2886 [ + + + + ]: 4180 : if (off >= 0 && is_equal)
2887 : : {
2888 [ - + ]: 1946 : Assert(boundinfo->indexes[off] >= 0);
2889 : 1946 : result->bound_offsets = bms_make_singleton(off);
2890 : : }
2891 : : else
2892 : 2234 : result->scan_default = partition_bound_has_default(boundinfo);
2893 : 4180 : return result;
2894 : :
2895 : 362 : case BTGreaterEqualStrategyNumber:
2896 : 362 : inclusive = true;
2897 : : pg_fallthrough;
2898 : 401 : case BTGreaterStrategyNumber:
2899 : 401 : off = partition_list_bsearch(partsupfunc,
2900 : : partcollation,
2901 : : boundinfo, value,
2902 : : &is_equal);
2903 [ + + ]: 401 : if (off >= 0)
2904 : : {
2905 : : /* We don't want the matched datum to be in the result. */
2906 [ + + + + ]: 316 : if (!is_equal || !inclusive)
2907 : 114 : off++;
2908 : : }
2909 : : else
2910 : : {
2911 : : /*
2912 : : * This case means all partition bounds are greater, which in
2913 : : * turn means that all partitions satisfy this key.
2914 : : */
2915 : 85 : off = 0;
2916 : : }
2917 : :
2918 : : /*
2919 : : * off is greater than the numbers of datums we have partitions
2920 : : * for. The only possible partition that could contain a match is
2921 : : * the default partition, but we must've set context->scan_default
2922 : : * above anyway if one exists.
2923 : : */
2924 [ + + ]: 401 : if (off > boundinfo->ndatums - 1)
2925 : 5 : return result;
2926 : :
2927 : 396 : minoff = off;
2928 : 396 : break;
2929 : :
2930 : 81 : case BTLessEqualStrategyNumber:
2931 : 81 : inclusive = true;
2932 : : pg_fallthrough;
2933 : 402 : case BTLessStrategyNumber:
2934 : 402 : off = partition_list_bsearch(partsupfunc,
2935 : : partcollation,
2936 : : boundinfo, value,
2937 : : &is_equal);
2938 [ + - + + : 402 : if (off >= 0 && is_equal && !inclusive)
+ + ]
2939 : 37 : off--;
2940 : :
2941 : : /*
2942 : : * off is smaller than the datums of all non-default partitions.
2943 : : * The only possible partition that could contain a match is the
2944 : : * default partition, but we must've set context->scan_default
2945 : : * above anyway if one exists.
2946 : : */
2947 [ + + ]: 402 : if (off < 0)
2948 : 5 : return result;
2949 : :
2950 : 397 : maxoff = off;
2951 : 397 : break;
2952 : :
2951 alvherre@alvh.no-ip. 2953 :UBC 0 : default:
2954 [ # # ]: 0 : elog(ERROR, "invalid strategy number %d", opstrategy);
2955 : : break;
2956 : : }
2957 : :
2836 alvherre@alvh.no-ip. 2958 [ + - - + ]:CBC 793 : Assert(minoff >= 0 && maxoff >= 0);
2951 2959 : 793 : result->bound_offsets = bms_add_range(NULL, minoff, maxoff);
2960 : 793 : return result;
2961 : : }
2962 : :
2963 : :
2964 : : /*
2965 : : * get_matching_range_bounds
2966 : : * Determine the offsets of range bounds matching the specified values,
2967 : : * according to the semantics of the given operator strategy
2968 : : *
2969 : : * Each datum whose offset is in result is to be treated as the upper bound of
2970 : : * the partition that will contain the desired values.
2971 : : *
2972 : : * scan_default is set in the returned struct if a default partition exists
2973 : : * and we're absolutely certain that it needs to be scanned. We do *not* set
2974 : : * it just because values match portions of the key space uncovered by
2975 : : * partitions other than default (space which we normally assume to belong to
2976 : : * the default partition): the final set of bounds obtained after combining
2977 : : * multiple pruning steps might exclude it, so we infer its inclusion
2978 : : * elsewhere.
2979 : : *
2980 : : * 'opstrategy' must be a btree strategy number.
2981 : : *
2982 : : * 'values' contains Datums indexed by the partition key to use for pruning.
2983 : : *
2984 : : * 'nvalues', number of Datums in 'values' array. Must be <= context->partnatts.
2985 : : *
2986 : : * 'partsupfunc' contains the range partitioning comparison functions to be
2987 : : * used to perform partition_range_datum_bsearch or partition_rbound_datum_cmp
2988 : : * using.
2989 : : *
2990 : : * 'nullkeys' is the set of partition keys that are null.
2991 : : */
2992 : : static PruneStepResult *
2993 : 4733 : get_matching_range_bounds(PartitionPruneContext *context,
2994 : : StrategyNumber opstrategy, const Datum *values, int nvalues,
2995 : : FmgrInfo *partsupfunc, Bitmapset *nullkeys)
2996 : : {
145 michael@paquier.xyz 2997 :GNC 4733 : PruneStepResult *result = palloc0_object(PruneStepResult);
2951 alvherre@alvh.no-ip. 2998 :CBC 4733 : PartitionBoundInfo boundinfo = context->boundinfo;
2999 : 4733 : Oid *partcollation = context->partcollation;
3000 : 4733 : int partnatts = context->partnatts;
3001 : 4733 : int *partindices = boundinfo->indexes;
3002 : : int off,
3003 : : minoff,
3004 : : maxoff;
3005 : : bool is_equal;
3006 : 4733 : bool inclusive = false;
3007 : :
3008 [ - + ]: 4733 : Assert(context->strategy == PARTITION_STRATEGY_RANGE);
3009 [ - + ]: 4733 : Assert(nvalues <= partnatts);
3010 : :
3011 : 4733 : result->scan_null = result->scan_default = false;
3012 : :
3013 : : /*
3014 : : * If there are no datums to compare keys with, or if we got an IS NULL
3015 : : * clause just return the default partition, if it exists.
3016 : : */
3017 [ + + + + ]: 4733 : if (boundinfo->ndatums == 0 || !bms_is_empty(nullkeys))
3018 : : {
3019 : 55 : result->scan_default = partition_bound_has_default(boundinfo);
3020 : 55 : return result;
3021 : : }
3022 : :
3023 : 4678 : minoff = 0;
3024 : 4678 : maxoff = boundinfo->ndatums;
3025 : :
3026 : : /*
3027 : : * If there are no values to compare with the datums in boundinfo, it
3028 : : * means the caller asked for partitions for all non-null datums. Add
3029 : : * indexes of *all* partitions, including the default partition if one
3030 : : * exists.
3031 : : */
3032 [ + + ]: 4678 : if (nvalues == 0)
3033 : : {
3034 : : /* ignore key space not covered by any partitions */
3035 [ + - ]: 25 : if (partindices[minoff] < 0)
3036 : 25 : minoff++;
3037 [ + - ]: 25 : if (partindices[maxoff] < 0)
3038 : 25 : maxoff--;
3039 : :
3040 : 25 : result->scan_default = partition_bound_has_default(boundinfo);
2466 3041 [ + - - + ]: 25 : Assert(partindices[minoff] >= 0 &&
3042 : : partindices[maxoff] >= 0);
2951 3043 : 25 : result->bound_offsets = bms_add_range(NULL, minoff, maxoff);
3044 : :
3045 : 25 : return result;
3046 : : }
3047 : :
3048 : : /*
3049 : : * If the query does not constrain all key columns, we'll need to scan the
3050 : : * default partition, if any.
3051 : : */
3052 [ + + ]: 4653 : if (nvalues < partnatts)
3053 : 599 : result->scan_default = partition_bound_has_default(boundinfo);
3054 : :
3055 [ + + + + : 4653 : switch (opstrategy)
+ - ]
3056 : : {
3057 : 3364 : case BTEqualStrategyNumber:
3058 : : /* Look for the smallest bound that is = lookup value. */
3059 : 3364 : off = partition_range_datum_bsearch(partsupfunc,
3060 : : partcollation,
3061 : : boundinfo,
3062 : : nvalues, values,
3063 : : &is_equal);
3064 : :
3065 [ + - + + ]: 3364 : if (off >= 0 && is_equal)
3066 : : {
3067 [ + + ]: 948 : if (nvalues == partnatts)
3068 : : {
3069 : : /* There can only be zero or one matching partition. */
2466 3070 : 585 : result->bound_offsets = bms_make_singleton(off + 1);
2951 3071 : 585 : return result;
3072 : : }
3073 : : else
3074 : : {
3075 : 363 : int saved_off = off;
3076 : :
3077 : : /*
3078 : : * Since the lookup value contains only a prefix of keys,
3079 : : * we must find other bounds that may also match the
3080 : : * prefix. partition_range_datum_bsearch() returns the
3081 : : * offset of one of them, find others by checking adjacent
3082 : : * bounds.
3083 : : */
3084 : :
3085 : : /*
3086 : : * First find greatest bound that's smaller than the
3087 : : * lookup value.
3088 : : */
3089 [ + + ]: 568 : while (off >= 1)
3090 : : {
3091 : : int32 cmpval;
3092 : :
3093 : : cmpval =
3094 : 493 : partition_rbound_datum_cmp(partsupfunc,
3095 : : partcollation,
3096 : 493 : boundinfo->datums[off - 1],
3097 : 493 : boundinfo->kind[off - 1],
3098 : : values, nvalues);
3099 [ + + ]: 493 : if (cmpval != 0)
3100 : 288 : break;
3101 : 205 : off--;
3102 : : }
3103 : :
3104 [ - + ]: 363 : Assert(0 ==
3105 : : partition_rbound_datum_cmp(partsupfunc,
3106 : : partcollation,
3107 : : boundinfo->datums[off],
3108 : : boundinfo->kind[off],
3109 : : values, nvalues));
3110 : :
3111 : : /*
3112 : : * We can treat 'off' as the offset of the smallest bound
3113 : : * to be included in the result, if we know it is the
3114 : : * upper bound of the partition in which the lookup value
3115 : : * could possibly exist. One case it couldn't is if the
3116 : : * bound, or precisely the matched portion of its prefix,
3117 : : * is not inclusive.
3118 : : */
3119 [ + + ]: 363 : if (boundinfo->kind[off][nvalues] ==
3120 : : PARTITION_RANGE_DATUM_MINVALUE)
3121 : 25 : off++;
3122 : :
3123 : 363 : minoff = off;
3124 : :
3125 : : /*
3126 : : * Now find smallest bound that's greater than the lookup
3127 : : * value.
3128 : : */
3129 : 363 : off = saved_off;
3130 [ + + ]: 598 : while (off < boundinfo->ndatums - 1)
3131 : : {
3132 : : int32 cmpval;
3133 : :
3134 : 553 : cmpval = partition_rbound_datum_cmp(partsupfunc,
3135 : : partcollation,
3136 : 553 : boundinfo->datums[off + 1],
3137 : 553 : boundinfo->kind[off + 1],
3138 : : values, nvalues);
3139 [ + + ]: 553 : if (cmpval != 0)
3140 : 318 : break;
3141 : 235 : off++;
3142 : : }
3143 : :
3144 [ - + ]: 363 : Assert(0 ==
3145 : : partition_rbound_datum_cmp(partsupfunc,
3146 : : partcollation,
3147 : : boundinfo->datums[off],
3148 : : boundinfo->kind[off],
3149 : : values, nvalues));
3150 : :
3151 : : /*
3152 : : * off + 1, then would be the offset of the greatest bound
3153 : : * to be included in the result.
3154 : : */
3155 : 363 : maxoff = off + 1;
3156 : : }
3157 : :
3158 [ + - - + ]: 363 : Assert(minoff >= 0 && maxoff >= 0);
3159 : 363 : result->bound_offsets = bms_add_range(NULL, minoff, maxoff);
3160 : : }
3161 : : else
3162 : : {
3163 : : /*
3164 : : * The lookup value falls in the range between some bounds in
3165 : : * boundinfo. 'off' would be the offset of the greatest bound
3166 : : * that is <= lookup value, so add off + 1 to the result
3167 : : * instead as the offset of the upper bound of the only
3168 : : * partition that may contain the lookup value. If 'off' is
3169 : : * -1 indicating that all bounds are greater, then we simply
3170 : : * end up adding the first bound's offset, that is, 0.
3171 : : */
2466 3172 : 2416 : result->bound_offsets = bms_make_singleton(off + 1);
3173 : : }
3174 : :
2951 3175 : 2779 : return result;
3176 : :
3177 : 421 : case BTGreaterEqualStrategyNumber:
3178 : 421 : inclusive = true;
3179 : : pg_fallthrough;
3180 : 681 : case BTGreaterStrategyNumber:
3181 : :
3182 : : /*
3183 : : * Look for the smallest bound that is > or >= lookup value and
3184 : : * set minoff to its offset.
3185 : : */
3186 : 681 : off = partition_range_datum_bsearch(partsupfunc,
3187 : : partcollation,
3188 : : boundinfo,
3189 : : nvalues, values,
3190 : : &is_equal);
3191 [ + + ]: 681 : if (off < 0)
3192 : : {
3193 : : /*
3194 : : * All bounds are greater than the lookup value, so include
3195 : : * all of them in the result.
3196 : : */
3197 : 50 : minoff = 0;
3198 : : }
3199 : : else
3200 : : {
3201 [ + + + + ]: 631 : if (is_equal && nvalues < partnatts)
3202 : : {
3203 : : /*
3204 : : * Since the lookup value contains only a prefix of keys,
3205 : : * we must find other bounds that may also match the
3206 : : * prefix. partition_range_datum_bsearch() returns the
3207 : : * offset of one of them, find others by checking adjacent
3208 : : * bounds.
3209 : : *
3210 : : * Based on whether the lookup values are inclusive or
3211 : : * not, we must either include the indexes of all such
3212 : : * bounds in the result (that is, set minoff to the index
3213 : : * of smallest such bound) or find the smallest one that's
3214 : : * greater than the lookup values and set minoff to that.
3215 : : */
3216 [ + + + - ]: 110 : while (off >= 1 && off < boundinfo->ndatums - 1)
3217 : : {
3218 : : int32 cmpval;
3219 : : int nextoff;
3220 : :
3221 [ + + ]: 90 : nextoff = inclusive ? off - 1 : off + 1;
3222 : : cmpval =
3223 : 90 : partition_rbound_datum_cmp(partsupfunc,
3224 : : partcollation,
3225 : 90 : boundinfo->datums[nextoff],
3226 : 90 : boundinfo->kind[nextoff],
3227 : : values, nvalues);
3228 [ + + ]: 90 : if (cmpval != 0)
3229 : 45 : break;
3230 : :
3231 : 45 : off = nextoff;
3232 : : }
3233 : :
3234 [ - + ]: 65 : Assert(0 ==
3235 : : partition_rbound_datum_cmp(partsupfunc,
3236 : : partcollation,
3237 : : boundinfo->datums[off],
3238 : : boundinfo->kind[off],
3239 : : values, nvalues));
3240 : :
3241 [ + + ]: 65 : minoff = inclusive ? off : off + 1;
3242 : : }
3243 : : else
3244 : : {
3245 : :
3246 : : /*
3247 : : * lookup value falls in the range between some bounds in
3248 : : * boundinfo. off would be the offset of the greatest
3249 : : * bound that is <= lookup value, so add off + 1 to the
3250 : : * result instead as the offset of the upper bound of the
3251 : : * smallest partition that may contain the lookup value.
3252 : : */
3253 : 566 : minoff = off + 1;
3254 : : }
3255 : : }
3256 : 681 : break;
3257 : :
3258 : 67 : case BTLessEqualStrategyNumber:
3259 : 67 : inclusive = true;
3260 : : pg_fallthrough;
3261 : 608 : case BTLessStrategyNumber:
3262 : :
3263 : : /*
3264 : : * Look for the greatest bound that is < or <= lookup value and
3265 : : * set maxoff to its offset.
3266 : : */
3267 : 608 : off = partition_range_datum_bsearch(partsupfunc,
3268 : : partcollation,
3269 : : boundinfo,
3270 : : nvalues, values,
3271 : : &is_equal);
2466 3272 [ + - ]: 608 : if (off >= 0)
3273 : : {
3274 : : /*
3275 : : * See the comment above.
3276 : : */
2951 3277 [ + + + + ]: 608 : if (is_equal && nvalues < partnatts)
3278 : : {
3279 [ + - + + ]: 108 : while (off >= 1 && off < boundinfo->ndatums - 1)
3280 : : {
3281 : : int32 cmpval;
3282 : : int nextoff;
3283 : :
3284 [ + + ]: 103 : nextoff = inclusive ? off + 1 : off - 1;
3285 : 103 : cmpval = partition_rbound_datum_cmp(partsupfunc,
3286 : : partcollation,
3287 : 103 : boundinfo->datums[nextoff],
3288 : 103 : boundinfo->kind[nextoff],
3289 : : values, nvalues);
3290 [ + + ]: 103 : if (cmpval != 0)
3291 : 83 : break;
3292 : :
3293 : 20 : off = nextoff;
3294 : : }
3295 : :
3296 [ - + ]: 88 : Assert(0 ==
3297 : : partition_rbound_datum_cmp(partsupfunc,
3298 : : partcollation,
3299 : : boundinfo->datums[off],
3300 : : boundinfo->kind[off],
3301 : : values, nvalues));
3302 : :
3303 [ + + ]: 88 : maxoff = inclusive ? off + 1 : off;
3304 : : }
3305 : :
3306 : : /*
3307 : : * The lookup value falls in the range between some bounds in
3308 : : * boundinfo. 'off' would be the offset of the greatest bound
3309 : : * that is <= lookup value, so add off + 1 to the result
3310 : : * instead as the offset of the upper bound of the greatest
3311 : : * partition that may contain lookup value. If the lookup
3312 : : * value had exactly matched the bound, but it isn't
3313 : : * inclusive, no need add the adjacent partition.
3314 : : */
3315 [ + + + + ]: 520 : else if (!is_equal || inclusive)
3316 : 375 : maxoff = off + 1;
3317 : : else
3318 : 145 : maxoff = off;
3319 : : }
3320 : : else
3321 : : {
3322 : : /*
3323 : : * 'off' is -1 indicating that all bounds are greater, so just
3324 : : * set the first bound's offset as maxoff.
3325 : : */
2466 alvherre@alvh.no-ip. 3326 :UBC 0 : maxoff = off + 1;
3327 : : }
2951 alvherre@alvh.no-ip. 3328 :CBC 608 : break;
3329 : :
2951 alvherre@alvh.no-ip. 3330 :UBC 0 : default:
3331 [ # # ]: 0 : elog(ERROR, "invalid strategy number %d", opstrategy);
3332 : : break;
3333 : : }
3334 : :
2466 alvherre@alvh.no-ip. 3335 [ + - - + ]:CBC 1289 : Assert(minoff >= 0 && minoff <= boundinfo->ndatums);
3336 [ + - - + ]: 1289 : Assert(maxoff >= 0 && maxoff <= boundinfo->ndatums);
3337 : :
3338 : : /*
3339 : : * If the smallest partition to return has MINVALUE (negative infinity) as
3340 : : * its lower bound, increment it to point to the next finite bound
3341 : : * (supposedly its upper bound), so that we don't inadvertently end up
3342 : : * scanning the default partition.
3343 : : */
3344 [ + + + + ]: 1289 : if (minoff < boundinfo->ndatums && partindices[minoff] < 0)
3345 : : {
2951 3346 : 733 : int lastkey = nvalues - 1;
3347 : :
2466 3348 [ + + ]: 733 : if (boundinfo->kind[minoff][lastkey] ==
3349 : : PARTITION_RANGE_DATUM_MINVALUE)
3350 : : {
3351 : 135 : minoff++;
3352 [ - + ]: 135 : Assert(boundinfo->indexes[minoff] >= 0);
3353 : : }
3354 : : }
3355 : :
3356 : : /*
3357 : : * If the previous greatest partition has MAXVALUE (positive infinity) as
3358 : : * its upper bound (something only possible to do with multi-column range
3359 : : * partitioning), we scan switch to it as the greatest partition to
3360 : : * return. Again, so that we don't inadvertently end up scanning the
3361 : : * default partition.
3362 : : */
2951 3363 [ + - + + ]: 1289 : if (maxoff >= 1 && partindices[maxoff] < 0)
3364 : : {
3365 : 845 : int lastkey = nvalues - 1;
3366 : :
2466 3367 [ + + ]: 845 : if (boundinfo->kind[maxoff - 1][lastkey] ==
3368 : : PARTITION_RANGE_DATUM_MAXVALUE)
3369 : : {
3370 : 129 : maxoff--;
3371 [ - + ]: 129 : Assert(boundinfo->indexes[maxoff] >= 0);
3372 : : }
3373 : : }
3374 : :
2951 3375 [ + - - + ]: 1289 : Assert(minoff >= 0 && maxoff >= 0);
3376 [ + - ]: 1289 : if (minoff <= maxoff)
3377 : 1289 : result->bound_offsets = bms_add_range(NULL, minoff, maxoff);
3378 : :
3379 : 1289 : return result;
3380 : : }
3381 : :
3382 : : /*
3383 : : * pull_exec_paramids
3384 : : * Returns a Bitmapset containing the paramids of all Params with
3385 : : * paramkind = PARAM_EXEC in 'expr'.
3386 : : */
3387 : : static Bitmapset *
2886 tgl@sss.pgh.pa.us 3388 : 1546 : pull_exec_paramids(Expr *expr)
3389 : : {
3390 : 1546 : Bitmapset *result = NULL;
3391 : :
3392 : 1546 : (void) pull_exec_paramids_walker((Node *) expr, &result);
3393 : :
3394 : 1546 : return result;
3395 : : }
3396 : :
3397 : : static bool
3398 : 1983 : pull_exec_paramids_walker(Node *node, Bitmapset **context)
3399 : : {
3400 [ + + ]: 1983 : if (node == NULL)
3401 : 15 : return false;
3402 [ + + ]: 1968 : if (IsA(node, Param))
3403 : : {
3404 : 1547 : Param *param = (Param *) node;
3405 : :
3406 [ + + ]: 1547 : if (param->paramkind == PARAM_EXEC)
3407 : 1118 : *context = bms_add_member(*context, param->paramid);
3408 : 1547 : return false;
3409 : : }
523 peter@eisentraut.org 3410 : 421 : return expression_tree_walker(node, pull_exec_paramids_walker, context);
3411 : : }
3412 : :
3413 : : /*
3414 : : * get_partkey_exec_paramids
3415 : : * Loop through given pruning steps and find out which exec Params
3416 : : * are used.
3417 : : *
3418 : : * Returns a Bitmapset of Param IDs.
3419 : : */
3420 : : static Bitmapset *
2545 tgl@sss.pgh.pa.us 3421 : 341 : get_partkey_exec_paramids(List *steps)
3422 : : {
3423 : 341 : Bitmapset *execparamids = NULL;
3424 : : ListCell *lc;
3425 : :
2950 alvherre@alvh.no-ip. 3426 [ + - + + : 772 : foreach(lc, steps)
+ + ]
3427 : : {
2886 tgl@sss.pgh.pa.us 3428 : 431 : PartitionPruneStepOp *step = (PartitionPruneStepOp *) lfirst(lc);
3429 : : ListCell *lc2;
3430 : :
3431 [ + + ]: 431 : if (!IsA(step, PartitionPruneStepOp))
2950 alvherre@alvh.no-ip. 3432 : 40 : continue;
3433 : :
2545 tgl@sss.pgh.pa.us 3434 [ + - + + : 822 : foreach(lc2, step->exprs)
+ + ]
3435 : : {
2950 alvherre@alvh.no-ip. 3436 : 431 : Expr *expr = lfirst(lc2);
3437 : :
3438 : : /* We can be quick for plain Consts */
2886 tgl@sss.pgh.pa.us 3439 [ + + ]: 431 : if (!IsA(expr, Const))
2545 3440 : 386 : execparamids = bms_join(execparamids,
3441 : : pull_exec_paramids(expr));
3442 : : }
3443 : : }
3444 : :
3445 : 341 : return execparamids;
3446 : : }
3447 : :
3448 : : /*
3449 : : * perform_pruning_base_step
3450 : : * Determines the indexes of datums that satisfy conditions specified in
3451 : : * 'opstep'.
3452 : : *
3453 : : * Result also contains whether special null-accepting and/or default
3454 : : * partition need to be scanned.
3455 : : */
3456 : : static PruneStepResult *
2951 alvherre@alvh.no-ip. 3457 : 10418 : perform_pruning_base_step(PartitionPruneContext *context,
3458 : : PartitionPruneStepOp *opstep)
3459 : : {
3460 : : ListCell *lc1,
3461 : : *lc2;
3462 : : int keyno,
3463 : : nvalues;
3464 : : Datum values[PARTITION_MAX_KEYS];
3465 : : FmgrInfo *partsupfunc;
3466 : : int stateidx;
3467 : :
3468 : : /*
3469 : : * There better be the same number of expressions and compare functions.
3470 : : */
3471 [ - + ]: 10418 : Assert(list_length(opstep->exprs) == list_length(opstep->cmpfns));
3472 : :
3473 : 10418 : nvalues = 0;
3474 : 10418 : lc1 = list_head(opstep->exprs);
3475 : 10418 : lc2 = list_head(opstep->cmpfns);
3476 : :
3477 : : /*
3478 : : * Generate the partition lookup key that will be used by one of the
3479 : : * get_matching_*_bounds functions called below.
3480 : : */
3481 [ + + ]: 22378 : for (keyno = 0; keyno < context->partnatts; keyno++)
3482 : : {
3483 : : /*
3484 : : * For hash partitioning, it is possible that values of some keys are
3485 : : * not provided in operator clauses, but instead the planner found
3486 : : * that they appeared in a IS NULL clause.
3487 : : */
3488 [ + + ]: 12270 : if (bms_is_member(keyno, opstep->nullkeys))
3489 : 689 : continue;
3490 : :
3491 : : /*
3492 : : * For range partitioning, we must only perform pruning with values
3493 : : * for either all partition keys or a prefix thereof.
3494 : : */
3495 [ + + + + ]: 11581 : if (keyno > nvalues && context->strategy == PARTITION_STRATEGY_RANGE)
3496 : 306 : break;
3497 : :
3498 [ + + ]: 11275 : if (lc1 != NULL)
3499 : : {
3500 : : Expr *expr;
3501 : : Datum datum;
3502 : : bool isnull;
3503 : : Oid cmpfn;
3504 : :
3505 : 10591 : expr = lfirst(lc1);
2933 3506 : 10591 : stateidx = PruneCxtStateIdx(context->partnatts,
3507 : : opstep->step.step_id, keyno);
2545 tgl@sss.pgh.pa.us 3508 : 10591 : partkey_datum_from_expr(context, expr, stateidx,
3509 : : &datum, &isnull);
3510 : :
3511 : : /*
3512 : : * Since we only allow strict operators in pruning steps, any
3513 : : * null-valued comparison value must cause the comparison to fail,
3514 : : * so that no partitions could match.
3515 : : */
3516 [ + + ]: 10591 : if (isnull)
3517 : : {
3518 : : PruneStepResult *result;
3519 : :
145 michael@paquier.xyz 3520 :GNC 4 : result = palloc_object(PruneStepResult);
2545 tgl@sss.pgh.pa.us 3521 :CBC 4 : result->bound_offsets = NULL;
3522 : 4 : result->scan_default = false;
3523 : 4 : result->scan_null = false;
3524 : :
3525 : 4 : return result;
3526 : : }
3527 : :
3528 : : /* Set up the stepcmpfuncs entry, unless we already did */
3529 : 10587 : cmpfn = lfirst_oid(lc2);
3530 [ - + ]: 10587 : Assert(OidIsValid(cmpfn));
3531 [ + + ]: 10587 : if (cmpfn != context->stepcmpfuncs[stateidx].fn_oid)
3532 : : {
3533 : : /*
3534 : : * If the needed support function is the same one cached in
3535 : : * the relation's partition key, copy the cached FmgrInfo.
3536 : : * Otherwise (i.e., when we have a cross-type comparison), an
3537 : : * actual lookup is required.
3538 : : */
3539 [ + + ]: 8483 : if (cmpfn == context->partsupfunc[keyno].fn_oid)
3540 : 8398 : fmgr_info_copy(&context->stepcmpfuncs[stateidx],
3541 : 8398 : &context->partsupfunc[keyno],
3542 : : context->ppccontext);
3543 : : else
3544 : 85 : fmgr_info_cxt(cmpfn, &context->stepcmpfuncs[stateidx],
3545 : : context->ppccontext);
3546 : : }
3547 : :
3548 : 10587 : values[keyno] = datum;
3549 : 10587 : nvalues++;
3550 : :
2486 3551 : 10587 : lc1 = lnext(opstep->exprs, lc1);
3552 : 10587 : lc2 = lnext(opstep->cmpfns, lc2);
3553 : : }
3554 : : }
3555 : :
3556 : : /*
3557 : : * Point partsupfunc to the entry for the 0th key of this step; the
3558 : : * additional support functions, if any, follow consecutively.
3559 : : */
2883 3560 : 10414 : stateidx = PruneCxtStateIdx(context->partnatts, opstep->step.step_id, 0);
3561 : 10414 : partsupfunc = &context->stepcmpfuncs[stateidx];
3562 : :
2951 alvherre@alvh.no-ip. 3563 [ + + + - ]: 10414 : switch (context->strategy)
3564 : : {
3565 : 261 : case PARTITION_STRATEGY_HASH:
3566 : 261 : return get_matching_hash_bounds(context,
3567 : 261 : opstep->opstrategy,
3568 : : values, nvalues,
3569 : : partsupfunc,
3570 : : opstep->nullkeys);
3571 : :
3572 : 5420 : case PARTITION_STRATEGY_LIST:
3573 : 5420 : return get_matching_list_bounds(context,
3574 : 5420 : opstep->opstrategy,
3575 : : values[0], nvalues,
3576 : : &partsupfunc[0],
3577 : : opstep->nullkeys);
3578 : :
3579 : 4733 : case PARTITION_STRATEGY_RANGE:
3580 : 4733 : return get_matching_range_bounds(context,
3581 : 4733 : opstep->opstrategy,
3582 : : values, nvalues,
3583 : : partsupfunc,
3584 : : opstep->nullkeys);
3585 : :
2951 alvherre@alvh.no-ip. 3586 :UBC 0 : default:
3587 [ # # ]: 0 : elog(ERROR, "unexpected partition strategy: %d",
3588 : : (int) context->strategy);
3589 : : break;
3590 : : }
3591 : :
3592 : : return NULL;
3593 : : }
3594 : :
3595 : : /*
3596 : : * perform_pruning_combine_step
3597 : : * Determines the indexes of datums obtained by combining those given
3598 : : * by the steps identified by cstep->source_stepids using the specified
3599 : : * combination method
3600 : : *
3601 : : * Since cstep may refer to the result of earlier steps, we also receive
3602 : : * step_results here.
3603 : : */
3604 : : static PruneStepResult *
2951 alvherre@alvh.no-ip. 3605 :CBC 2089 : perform_pruning_combine_step(PartitionPruneContext *context,
3606 : : PartitionPruneStepCombine *cstep,
3607 : : PruneStepResult **step_results)
3608 : : {
145 michael@paquier.xyz 3609 :GNC 2089 : PruneStepResult *result = palloc0_object(PruneStepResult);
3610 : : bool firststep;
3611 : : ListCell *lc1;
3612 : :
3613 : : /*
3614 : : * A combine step without any source steps is an indication to not perform
3615 : : * any partition pruning. Return all datum indexes in that case.
3616 : : */
1923 tgl@sss.pgh.pa.us 3617 [ + + ]:CBC 2089 : if (cstep->source_stepids == NIL)
3618 : : {
2951 alvherre@alvh.no-ip. 3619 : 315 : PartitionBoundInfo boundinfo = context->boundinfo;
3620 : :
2466 3621 : 315 : result->bound_offsets =
1923 tgl@sss.pgh.pa.us 3622 : 315 : bms_add_range(NULL, 0, boundinfo->nindexes - 1);
2951 alvherre@alvh.no-ip. 3623 : 315 : result->scan_default = partition_bound_has_default(boundinfo);
3624 : 315 : result->scan_null = partition_bound_accepts_nulls(boundinfo);
3625 : 315 : return result;
3626 : : }
3627 : :
3628 [ + + - ]: 1774 : switch (cstep->combineOp)
3629 : : {
3630 : 952 : case PARTPRUNE_COMBINE_UNION:
3631 [ + - + + : 2903 : foreach(lc1, cstep->source_stepids)
+ + ]
3632 : : {
3633 : 1951 : int step_id = lfirst_int(lc1);
3634 : : PruneStepResult *step_result;
3635 : :
3636 : : /*
3637 : : * step_results[step_id] must contain a valid result, which is
3638 : : * confirmed by the fact that cstep's step_id is greater than
3639 : : * step_id and the fact that results of the individual steps
3640 : : * are evaluated in sequence of their step_ids.
3641 : : */
3642 [ - + ]: 1951 : if (step_id >= cstep->step.step_id)
2951 alvherre@alvh.no-ip. 3643 [ # # ]:UBC 0 : elog(ERROR, "invalid pruning combine step argument");
2951 alvherre@alvh.no-ip. 3644 :CBC 1951 : step_result = step_results[step_id];
3645 [ - + ]: 1951 : Assert(step_result != NULL);
3646 : :
3647 : : /* Record any additional datum indexes from this step */
3648 : 3902 : result->bound_offsets = bms_add_members(result->bound_offsets,
3649 : 1951 : step_result->bound_offsets);
3650 : :
3651 : : /* Update whether to scan null and default partitions. */
3652 [ + + ]: 1951 : if (!result->scan_null)
3653 : 1866 : result->scan_null = step_result->scan_null;
3654 [ + + ]: 1951 : if (!result->scan_default)
3655 : 1716 : result->scan_default = step_result->scan_default;
3656 : : }
3657 : 952 : break;
3658 : :
3659 : 822 : case PARTPRUNE_COMBINE_INTERSECT:
3660 : 822 : firststep = true;
3661 [ + - + + : 2932 : foreach(lc1, cstep->source_stepids)
+ + ]
3662 : : {
3663 : 2110 : int step_id = lfirst_int(lc1);
3664 : : PruneStepResult *step_result;
3665 : :
3666 [ - + ]: 2110 : if (step_id >= cstep->step.step_id)
2951 alvherre@alvh.no-ip. 3667 [ # # ]:UBC 0 : elog(ERROR, "invalid pruning combine step argument");
2951 alvherre@alvh.no-ip. 3668 :CBC 2110 : step_result = step_results[step_id];
3669 [ - + ]: 2110 : Assert(step_result != NULL);
3670 : :
3671 [ + + ]: 2110 : if (firststep)
3672 : : {
3673 : : /* Copy step's result the first time. */
2948 3674 : 822 : result->bound_offsets =
3675 : 822 : bms_copy(step_result->bound_offsets);
2951 3676 : 822 : result->scan_null = step_result->scan_null;
3677 : 822 : result->scan_default = step_result->scan_default;
3678 : 822 : firststep = false;
3679 : : }
3680 : : else
3681 : : {
3682 : : /* Record datum indexes common to both steps */
3683 : 1288 : result->bound_offsets =
3684 : 1288 : bms_int_members(result->bound_offsets,
3685 : 1288 : step_result->bound_offsets);
3686 : :
3687 : : /* Update whether to scan null and default partitions. */
3688 [ + + ]: 1288 : if (result->scan_null)
3689 : 80 : result->scan_null = step_result->scan_null;
3690 [ + + ]: 1288 : if (result->scan_default)
3691 : 630 : result->scan_default = step_result->scan_default;
3692 : : }
3693 : : }
3694 : 822 : break;
3695 : : }
3696 : :
3697 : 1774 : return result;
3698 : : }
3699 : :
3700 : : /*
3701 : : * match_boolean_partition_clause
3702 : : *
3703 : : * If we're able to match the clause to the partition key as specially-shaped
3704 : : * boolean clause, set *outconst to a Const containing a true, false or NULL
3705 : : * value, set *notclause according to if the clause was in the "not" form,
3706 : : * i.e. "IS NOT TRUE", "IS NOT FALSE" or "IS NOT UNKNOWN" and return
3707 : : * PARTCLAUSE_MATCH_CLAUSE for "IS [NOT] (TRUE|FALSE)" clauses and
3708 : : * PARTCLAUSE_MATCH_NULLNESS for "IS [NOT] UNKNOWN" clauses. Otherwise,
3709 : : * return PARTCLAUSE_UNSUPPORTED if the clause cannot be used for partition
3710 : : * pruning, and PARTCLAUSE_NOMATCH for supported clauses that do not match this
3711 : : * 'partkey'.
3712 : : */
3713 : : static PartClauseMatchStatus
40 peter@eisentraut.org 3714 :GNC 29748 : match_boolean_partition_clause(Oid partopfamily, Expr *clause, const Expr *partkey,
3715 : : Expr **outconst, bool *notclause)
3716 : : {
3717 : : Expr *leftop;
3718 : :
2951 alvherre@alvh.no-ip. 3719 :CBC 29748 : *outconst = NULL;
792 drowley@postgresql.o 3720 : 29748 : *notclause = false;
3721 : :
3722 : : /*
3723 : : * Partitioning currently can only use built-in AMs, so checking for
3724 : : * built-in boolean opfamilies is good enough.
3725 : : */
1341 tgl@sss.pgh.pa.us 3726 [ + + + - ]: 29748 : if (!IsBuiltinBooleanOpfamily(partopfamily))
2489 drowley@postgresql.o 3727 : 28518 : return PARTCLAUSE_UNSUPPORTED;
3728 : :
2951 alvherre@alvh.no-ip. 3729 [ + + ]: 1230 : if (IsA(clause, BooleanTest))
3730 : : {
3731 : 670 : BooleanTest *btest = (BooleanTest *) clause;
3732 : :
3733 : 670 : leftop = btest->arg;
26 rguo@postgresql.org 3734 : 670 : leftop = (Expr *) strip_noop_phvs((Node *) leftop);
3735 [ - + ]: 670 : while (IsA(leftop, RelabelType))
2951 alvherre@alvh.no-ip. 3736 :UBC 0 : leftop = ((RelabelType *) leftop)->arg;
3737 : :
2951 alvherre@alvh.no-ip. 3738 [ + + ]:CBC 670 : if (equal(leftop, partkey))
3739 : : {
1117 drowley@postgresql.o 3740 [ + + + + : 490 : switch (btest->booltesttype)
+ + - ]
3741 : : {
3742 : 110 : case IS_NOT_TRUE:
792 3743 : 110 : *notclause = true;
3744 : : pg_fallthrough;
1117 3745 : 225 : case IS_TRUE:
3746 : 225 : *outconst = (Expr *) makeBoolConst(true, false);
792 3747 : 225 : return PARTCLAUSE_MATCH_CLAUSE;
1117 3748 : 70 : case IS_NOT_FALSE:
792 3749 : 70 : *notclause = true;
3750 : : pg_fallthrough;
1117 3751 : 185 : case IS_FALSE:
3752 : 185 : *outconst = (Expr *) makeBoolConst(false, false);
792 3753 : 185 : return PARTCLAUSE_MATCH_CLAUSE;
3754 : 45 : case IS_NOT_UNKNOWN:
3755 : 45 : *notclause = true;
3756 : : pg_fallthrough;
3757 : 80 : case IS_UNKNOWN:
3758 : 80 : return PARTCLAUSE_MATCH_NULLNESS;
1117 drowley@postgresql.o 3759 :UBC 0 : default:
3760 : 0 : return PARTCLAUSE_UNSUPPORTED;
3761 : : }
3762 : : }
3763 : : /* does not match partition key */
792 drowley@postgresql.o 3764 :CBC 180 : return PARTCLAUSE_NOMATCH;
3765 : : }
3766 : : else
3767 : : {
2653 tgl@sss.pgh.pa.us 3768 : 560 : bool is_not_clause = is_notclause(clause);
3769 : :
2951 alvherre@alvh.no-ip. 3770 [ + + ]: 560 : leftop = is_not_clause ? get_notclausearg(clause) : clause;
3771 : :
26 rguo@postgresql.org 3772 : 560 : leftop = (Expr *) strip_noop_phvs((Node *) leftop);
3773 [ - + ]: 560 : while (IsA(leftop, RelabelType))
2951 alvherre@alvh.no-ip. 3774 :UBC 0 : leftop = ((RelabelType *) leftop)->arg;
3775 : :
3776 : : /* Compare to the partition key, and make up a clause ... */
2951 alvherre@alvh.no-ip. 3777 [ + + ]:CBC 560 : if (equal(leftop, partkey))
1117 drowley@postgresql.o 3778 : 120 : *outconst = (Expr *) makeBoolConst(!is_not_clause, false);
2951 alvherre@alvh.no-ip. 3779 [ + + ]: 440 : else if (equal(negate_clause((Node *) leftop), partkey))
1117 drowley@postgresql.o 3780 : 40 : *outconst = (Expr *) makeBoolConst(is_not_clause, false);
3781 : : else
792 3782 : 400 : return PARTCLAUSE_NOMATCH;
3783 : :
3784 : 160 : return PARTCLAUSE_MATCH_CLAUSE;
3785 : : }
3786 : : }
3787 : :
3788 : : /*
3789 : : * partkey_datum_from_expr
3790 : : * Evaluate expression for potential partition pruning
3791 : : *
3792 : : * Evaluate 'expr'; set *value and *isnull to the resulting Datum and nullflag.
3793 : : *
3794 : : * If expr isn't a Const, its ExprState is in stateidx of the context
3795 : : * exprstate array.
3796 : : *
3797 : : * Note that the evaluated result may be in the per-tuple memory context of
3798 : : * context->exprcontext, and we may have leaked other memory there too.
3799 : : * This memory must be recovered by resetting that ExprContext after
3800 : : * we're done with the pruning operation (see execPartition.c).
3801 : : */
3802 : : static void
2951 alvherre@alvh.no-ip. 3803 : 10591 : partkey_datum_from_expr(PartitionPruneContext *context,
3804 : : Expr *expr, int stateidx,
3805 : : Datum *value, bool *isnull)
3806 : : {
2886 tgl@sss.pgh.pa.us 3807 [ + + ]: 10591 : if (IsA(expr, Const))
3808 : : {
3809 : : /* We can always determine the value of a constant */
2885 3810 : 7709 : Const *con = (Const *) expr;
3811 : :
3812 : 7709 : *value = con->constvalue;
3813 : 7709 : *isnull = con->constisnull;
3814 : : }
3815 : : else
3816 : : {
3817 : : ExprState *exprstate;
3818 : : ExprContext *ectx;
3819 : :
3820 : : /*
3821 : : * We should never see a non-Const in a step unless the caller has
3822 : : * passed a valid ExprContext.
3823 : : */
459 amitlan@postgresql.o 3824 [ - + ]: 2882 : Assert(context->exprcontext != NULL);
3825 : :
2545 tgl@sss.pgh.pa.us 3826 : 2882 : exprstate = context->exprstates[stateidx];
1491 alvherre@alvh.no-ip. 3827 : 2882 : ectx = context->exprcontext;
2545 tgl@sss.pgh.pa.us 3828 : 2882 : *value = ExecEvalExprSwitchContext(exprstate, ectx, isnull);
3829 : : }
2951 alvherre@alvh.no-ip. 3830 : 10591 : }
|