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