Skip to content

Commit 10c5cc4

Browse files
committed
Fix partition pruning to treat stable comparison operators properly.
Cross-type comparison operators in a btree or hash opclass might be only stable not immutable (this is true of timestamp vs. timestamptz for example). partprune.c ignored this possibility and would perform plan-time pruning with them anyway, possibly leading to wrong answers if the environment changed between planning and execution. To fix, teach gen_partprune_steps() to do things differently when creating plan-time pruning steps vs. run-time pruning steps. analyze_partkey_exprs() also needs an extra check, which is rather annoying but now is not the time to restructure things enough to avoid that. While at it, simplify the logic for the plan-time case a little by insisting that the comparison value be a Const and nothing else. This relies on the assumption that eval_const_expressions will have reduced any immutable expression to a Const; which is not quite 100% true, but certainly any case that comes up often enough to be interesting should have simplification logic there. Also improve a bunch of inadequate/obsolete/wrong comments. Per discussion of a report from Alan Jackson (though this fixes only one aspect of that problem). Back-patch to v11 where this code came in. David Rowley, with some further hacking by me Discussion: https://postgr.es/m/FAD28A83-AC73-489E-A058-2681FA31D648@tvsquared.com
1 parent 05cf419 commit 10c5cc4

File tree

3 files changed

+272
-45
lines changed

3 files changed

+272
-45
lines changed

src/backend/partitioning/partprune.c

Lines changed: 146 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include "access/nbtree.h"
4040
#include "catalog/pg_operator.h"
4141
#include "catalog/pg_opfamily.h"
42+
#include "catalog/pg_proc.h"
4243
#include "catalog/pg_type.h"
4344
#include "executor/executor.h"
4445
#include "miscadmin.h"
@@ -94,8 +95,12 @@ typedef enum PartClauseMatchStatus
9495
*/
9596
typedef struct GeneratePruningStepsContext
9697
{
98+
/* Input data: */
99+
bool forplanner; /* true when generating steps to be used
100+
* during query planning */
101+
/* Working state and result data: */
97102
int next_step_id;
98-
List *steps;
103+
List *steps; /* output, list of PartitionPruneSteps */
99104
} GeneratePruningStepsContext;
100105

101106
/* The result of performing one PartitionPruneStep */
@@ -118,7 +123,7 @@ static List *make_partitionedrel_pruneinfo(PlannerInfo *root,
118123
List *partitioned_rels, List *prunequal,
119124
Bitmapset **matchedsubplans);
120125
static List *gen_partprune_steps(RelOptInfo *rel, List *clauses,
121-
bool *contradictory);
126+
bool forplanner, bool *contradictory);
122127
static List *gen_partprune_steps_internal(GeneratePruningStepsContext *context,
123128
RelOptInfo *rel, List *clauses,
124129
bool *contradictory);
@@ -413,7 +418,13 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
413418
targetpart->relids);
414419
}
415420

416-
pruning_steps = gen_partprune_steps(subpart, partprunequal,
421+
/*
422+
* Convert pruning qual to pruning steps. Since these steps will be
423+
* used in the executor, we can pass 'forplanner' as false to allow
424+
* steps to be generated that are unsafe for evaluation during
425+
* planning, e.g. evaluation of stable functions.
426+
*/
427+
pruning_steps = gen_partprune_steps(subpart, partprunequal, false,
417428
&contradictory);
418429

419430
if (contradictory)
@@ -492,22 +503,37 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
492503
/*
493504
* gen_partprune_steps
494505
* Process 'clauses' (a rel's baserestrictinfo list of clauses) and return
495-
* a list of "partition pruning steps"
506+
* a list of "partition pruning steps".
507+
*
508+
* 'forplanner' must be true when generating steps to be evaluated during
509+
* query planning, false when generating steps to be used at run-time.
510+
*
511+
* The result generated with forplanner=false includes all clauses that
512+
* are selected with forplanner=true, because in some cases we need a
513+
* combination of clauses to prune successfully. For example, if we
514+
* are partitioning on a hash of columns A and B, and we have clauses
515+
* "WHERE A=constant AND B=nonconstant", we can't do anything at plan
516+
* time even though the first clause would be evaluable then. And we
517+
* must include the first clause when called with forplanner=false,
518+
* or we'll fail to prune at run-time either. This does mean that when
519+
* called with forplanner=false, we may return steps that don't actually
520+
* need to be executed at runtime; it's left to analyze_partkey_exprs()
521+
* to (re)discover that.
496522
*
497523
* If the clauses in the input list are contradictory or there is a
498-
* pseudo-constant "false", *contradictory is set to true upon return.
524+
* pseudo-constant "false", *contradictory is set to true upon return,
525+
* else it's set false.
499526
*/
500527
static List *
501-
gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory)
528+
gen_partprune_steps(RelOptInfo *rel, List *clauses, bool forplanner,
529+
bool *contradictory)
502530
{
503531
GeneratePruningStepsContext context;
504532

533+
context.forplanner = forplanner;
505534
context.next_step_id = 0;
506535
context.steps = NIL;
507536

508-
/* The clauses list may be modified below, so better make a copy. */
509-
clauses = list_copy(clauses);
510-
511537
/*
512538
* For sub-partitioned tables there's a corner case where if the
513539
* sub-partitioned table shares any partition keys with its parent, then
@@ -532,19 +558,23 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory)
532558
if (rel->relid != 1)
533559
ChangeVarNodes((Node *) partqual, 1, rel->relid, 0);
534560

535-
clauses = list_concat(clauses, partqual);
561+
/* Use list_copy to avoid modifying the passed-in List */
562+
clauses = list_concat(list_copy(clauses), partqual);
536563
}
537564

538565
/* Down into the rabbit-hole. */
539-
gen_partprune_steps_internal(&context, rel, clauses, contradictory);
566+
(void) gen_partprune_steps_internal(&context, rel, clauses, contradictory);
540567

541568
return context.steps;
542569
}
543570

544571
/*
545572
* prune_append_rel_partitions
546-
* Returns RT indexes of the minimum set of child partitions which must
547-
* be scanned to satisfy rel's baserestrictinfo quals.
573+
* Process rel's baserestrictinfo and make use of quals which can be
574+
* evaluated during query planning in order to determine the minimum set
575+
* of partitions which must be scanned to satisfy these quals. Returns
576+
* the matching partitions in the form of a Relids set containing the
577+
* partitions' RT indexes.
548578
*
549579
* Callers must ensure that 'rel' is a partitioned table.
550580
*/
@@ -568,9 +598,12 @@ prune_append_rel_partitions(RelOptInfo *rel)
568598

569599
/*
570600
* Process clauses. If the clauses are found to be contradictory, we can
571-
* return the empty set.
601+
* return the empty set. Pass 'forplanner' as true to indicate to the
602+
* pruning code that we only want pruning steps that can be evaluated
603+
* during planning.
572604
*/
573-
pruning_steps = gen_partprune_steps(rel, clauses, &contradictory);
605+
pruning_steps = gen_partprune_steps(rel, clauses, true,
606+
&contradictory);
574607
if (contradictory)
575608
return NULL;
576609

@@ -609,6 +642,9 @@ prune_append_rel_partitions(RelOptInfo *rel)
609642
* get_matching_partitions
610643
* Determine partitions that survive partition pruning
611644
*
645+
* Note: context->planstate must be set to a valid PlanState when the
646+
* pruning_steps were generated with 'forplanner' = false.
647+
*
612648
* Returns a Bitmapset of the RelOptInfo->part_rels indexes of the surviving
613649
* partitions.
614650
*/
@@ -729,9 +765,6 @@ get_matching_partitions(PartitionPruneContext *context, List *pruning_steps)
729765
* clause that contains false, we set *contradictory to true and return NIL
730766
* (that is, no pruning steps). Caller should consider all partitions as
731767
* pruned in that case. Otherwise, *contradictory is set to false.
732-
*
733-
* Note: the 'clauses' List may be modified inside this function. Callers may
734-
* like to make a copy of it before passing them to this function.
735768
*/
736769
static List *
737770
gen_partprune_steps_internal(GeneratePruningStepsContext *context,
@@ -1546,8 +1579,7 @@ match_clause_to_partition_key(RelOptInfo *rel,
15461579
Expr *expr;
15471580

15481581
/*
1549-
* Recognize specially shaped clauses that match with the Boolean
1550-
* partition key.
1582+
* Recognize specially shaped clauses that match a Boolean partition key.
15511583
*/
15521584
if (match_boolean_partition_clause(partopfamily, clause, partkey, &expr))
15531585
{
@@ -1622,16 +1654,47 @@ match_clause_to_partition_key(RelOptInfo *rel,
16221654
* Matched with this key. Now check various properties of the clause
16231655
* to see if it's sane to use it for pruning. In most of these cases,
16241656
* we can return UNSUPPORTED because the same failure would occur no
1625-
* matter which partkey it's matched to.
1657+
* matter which partkey it's matched to. (In particular, now that
1658+
* we've successfully matched one side of the opclause to a partkey,
1659+
* there is no chance that matching the other side to another partkey
1660+
* will produce a usable result, since that'd mean there are Vars on
1661+
* both sides.)
16261662
*/
1663+
if (context->forplanner)
1664+
{
1665+
/*
1666+
* When pruning in the planner, we only support pruning using
1667+
* comparisons to constants. Immutable subexpressions will have
1668+
* been folded to constants already, and we cannot prune on the
1669+
* basis of anything that's not immutable.
1670+
*/
1671+
if (!IsA(expr, Const))
1672+
return PARTCLAUSE_UNSUPPORTED;
16271673

1628-
/*
1629-
* We can't prune using an expression with Vars. (Report failure as
1630-
* UNSUPPORTED, not NOMATCH: as in the no-commutator case above, we
1631-
* now know there are Vars on both sides, so it's no good.)
1632-
*/
1633-
if (contain_var_clause((Node *) expr))
1634-
return PARTCLAUSE_UNSUPPORTED;
1674+
/*
1675+
* Also, the comparison operator itself must be immutable.
1676+
*/
1677+
if (op_volatile(opno) != PROVOLATILE_IMMUTABLE)
1678+
return PARTCLAUSE_UNSUPPORTED;
1679+
}
1680+
else
1681+
{
1682+
/*
1683+
* Otherwise, non-consts are allowed, but we can't prune using an
1684+
* expression that contains Vars.
1685+
*/
1686+
if (contain_var_clause((Node *) expr))
1687+
return PARTCLAUSE_UNSUPPORTED;
1688+
1689+
/*
1690+
* And we must reject anything containing a volatile function.
1691+
* Stable functions are OK though. (We need not check this for
1692+
* the comparison operator itself: anything that belongs to a
1693+
* partitioning operator family must be at least stable.)
1694+
*/
1695+
if (contain_volatile_functions((Node *) expr))
1696+
return PARTCLAUSE_UNSUPPORTED;
1697+
}
16351698

16361699
/*
16371700
* Only allow strict operators. This will guarantee nulls are
@@ -1640,10 +1703,6 @@ match_clause_to_partition_key(RelOptInfo *rel,
16401703
if (!op_strict(opno))
16411704
return PARTCLAUSE_UNSUPPORTED;
16421705

1643-
/* We can't use any volatile expressions to prune partitions. */
1644-
if (contain_volatile_functions((Node *) expr))
1645-
return PARTCLAUSE_UNSUPPORTED;
1646-
16471706
/*
16481707
* See if the operator is relevant to the partitioning opfamily.
16491708
*
@@ -1772,20 +1831,51 @@ match_clause_to_partition_key(RelOptInfo *rel,
17721831
if (IsA(leftop, RelabelType))
17731832
leftop = ((RelabelType *) leftop)->arg;
17741833

1775-
/* Check it matches this partition key */
1834+
/* check if the LHS matches this partition key */
17761835
if (!equal(leftop, partkey) ||
17771836
!PartCollMatchesExprColl(partcoll, saop->inputcollid))
17781837
return PARTCLAUSE_NOMATCH;
17791838

17801839
/*
17811840
* Matched with this key. Check various properties of the clause to
1782-
* see if it can sanely be used for partition pruning (this is mostly
1783-
* the same as for a plain OpExpr).
1841+
* see if it can sanely be used for partition pruning (this is
1842+
* identical to the logic for a plain OpExpr).
17841843
*/
1844+
if (context->forplanner)
1845+
{
1846+
/*
1847+
* When pruning in the planner, we only support pruning using
1848+
* comparisons to constants. Immutable subexpressions will have
1849+
* been folded to constants already, and we cannot prune on the
1850+
* basis of anything that's not immutable.
1851+
*/
1852+
if (!IsA(rightop, Const))
1853+
return PARTCLAUSE_UNSUPPORTED;
17851854

1786-
/* We can't prune using an expression with Vars. */
1787-
if (contain_var_clause((Node *) rightop))
1788-
return PARTCLAUSE_UNSUPPORTED;
1855+
/*
1856+
* Also, the comparison operator itself must be immutable.
1857+
*/
1858+
if (op_volatile(saop_op) != PROVOLATILE_IMMUTABLE)
1859+
return PARTCLAUSE_UNSUPPORTED;
1860+
}
1861+
else
1862+
{
1863+
/*
1864+
* Otherwise, non-consts are allowed, but we can't prune using an
1865+
* expression that contains Vars.
1866+
*/
1867+
if (contain_var_clause((Node *) rightop))
1868+
return PARTCLAUSE_UNSUPPORTED;
1869+
1870+
/*
1871+
* And we must reject anything containing a volatile function.
1872+
* Stable functions are OK though. (We need not check this for
1873+
* the comparison operator itself: anything that belongs to a
1874+
* partitioning operator family must be at least stable.)
1875+
*/
1876+
if (contain_volatile_functions((Node *) rightop))
1877+
return PARTCLAUSE_UNSUPPORTED;
1878+
}
17891879

17901880
/*
17911881
* Only allow strict operators. This will guarantee nulls are
@@ -1794,10 +1884,6 @@ match_clause_to_partition_key(RelOptInfo *rel,
17941884
if (!op_strict(saop_op))
17951885
return PARTCLAUSE_UNSUPPORTED;
17961886

1797-
/* We can't use any volatile expressions to prune partitions. */
1798-
if (contain_volatile_functions((Node *) rightop))
1799-
return PARTCLAUSE_UNSUPPORTED;
1800-
18011887
/*
18021888
* In case of NOT IN (..), we get a '<>', which we handle if list
18031889
* partitioning is in use and we're able to confirm that it's negator
@@ -2918,8 +3004,14 @@ analyze_partkey_exprs(PartitionedRelPruneInfo *pinfo, List *steps,
29183004

29193005
/*
29203006
* Steps require run-time pruning if they contain EXEC_PARAM Params.
2921-
* Otherwise, if their expressions aren't simple Consts, they require
2922-
* startup-time pruning.
3007+
* Otherwise, if their expressions aren't simple Consts or they involve
3008+
* non-immutable comparison operators, they require startup-time pruning.
3009+
* (Otherwise, the pruning would have been done at plan time.)
3010+
*
3011+
* Notice that what we actually check for mutability is the comparison
3012+
* functions, not the original operators. This relies on the support
3013+
* functions of the btree or hash opfamily being marked consistently with
3014+
* the operators.
29233015
*/
29243016
pinfo->nexprs = list_length(steps) * partnatts;
29253017
pinfo->hasexecparam = (bool *) palloc0(sizeof(bool) * pinfo->nexprs);
@@ -2931,15 +3023,18 @@ analyze_partkey_exprs(PartitionedRelPruneInfo *pinfo, List *steps,
29313023
{
29323024
PartitionPruneStepOp *step = (PartitionPruneStepOp *) lfirst(lc);
29333025
ListCell *lc2;
3026+
ListCell *lc3;
29343027
int keyno;
29353028

29363029
if (!IsA(step, PartitionPruneStepOp))
29373030
continue;
29383031

29393032
keyno = 0;
2940-
foreach(lc2, step->exprs)
3033+
Assert(list_length(step->exprs) == list_length(step->cmpfns));
3034+
forboth(lc2, step->exprs, lc3, step->cmpfns)
29413035
{
29423036
Expr *expr = lfirst(lc2);
3037+
Oid fnoid = lfirst_oid(lc3);
29433038

29443039
if (!IsA(expr, Const))
29453040
{
@@ -2962,6 +3057,12 @@ analyze_partkey_exprs(PartitionedRelPruneInfo *pinfo, List *steps,
29623057

29633058
doruntimeprune = true;
29643059
}
3060+
else if (func_volatile(fnoid) != PROVOLATILE_IMMUTABLE)
3061+
{
3062+
/* No exec params here, but must do initial pruning */
3063+
pinfo->do_initial_prune = true;
3064+
doruntimeprune = true;
3065+
}
29653066
keyno++;
29663067
}
29673068
}

0 commit comments

Comments
 (0)