diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c index e1ffc5271f..d6bce9f348 100644 --- a/src/backend/catalog/partition.c +++ b/src/backend/catalog/partition.c @@ -1784,7 +1784,7 @@ perform_pruning_base_step(PartitionPruneContext *context, /* * Generate the partition look-up key that will be used by one of - * the get_partitions_from_keys_* functions called below. + * the get_matching_*_bounds functions called below. */ for (keyno = 0; keyno < context->partnatts; keyno++) { @@ -1884,12 +1884,11 @@ perform_pruning_combine_step(PartitionPruneContext *context, { ListCell *lc1; PruneStepResult *result = NULL; + bool firststep; /* - * In some cases, the planner generates a combine step that doesn't - * contain any argument steps, to signal us to not prune any partitions. - * So, return indexes of all datums in that case, including null and/or - * default partition, if any. + * A combine step without any source steps is an indication to not perform + * any partition pruning, we just return all partitions. */ result = (PruneStepResult *) palloc0(sizeof(PruneStepResult)); if (list_length(cstep->source_stepids) == 0) @@ -1901,86 +1900,77 @@ perform_pruning_combine_step(PartitionPruneContext *context, result->scan_null = partition_bound_accepts_nulls(boundinfo); return result; } - else - { - bool firststep; - switch (cstep->combineOp) - { - case COMBINE_UNION: - foreach(lc1, cstep->source_stepids) - { - int step_id = lfirst_int(lc1); - PruneStepResult *step_result; + switch (cstep->combineOp) + { + case COMBINE_UNION: + foreach(lc1, cstep->source_stepids) + { + int step_id = lfirst_int(lc1); + PruneStepResult *step_result; - /* - * step_results[step_id] must contain a valid result, - * which is confirmed by the fact that cstep's step_id is - * greater than step_id and the fact that results of the - * individual steps are evaluated in sequence of their - * step_ids. - */ - if (step_id >= cstep->step.step_id) - elog(ERROR, "invalid pruning combine step argument"); - step_result = step_results[step_id]; - Assert(step_result != NULL); + /* + * step_results[step_id] must contain a valid result, which is + * confirmed by the fact that cstep's step_id is greater than + * step_id and the fact that results of the individual steps + * are evaluated in sequence of their step_ids. + */ + if (step_id >= cstep->step.step_id) + elog(ERROR, "invalid pruning combine step argument"); + step_result = step_results[step_id]; + Assert(step_result != NULL); - /* Record any additional datum indexes from this step */ - result->bound_offsets = - bms_add_members(result->bound_offsets, + /* Record any additional datum indexes from this step */ + result->bound_offsets = bms_add_members(result->bound_offsets, step_result->bound_offsets); - /* Update whether to scan null and default partitions. */ - if (!result->scan_null) - result->scan_null = step_result->scan_null; - if (!result->scan_default) - result->scan_default = step_result->scan_default; - } - break; + /* Update whether to scan null and default partitions. */ + if (!result->scan_null) + result->scan_null = step_result->scan_null; + if (!result->scan_default) + result->scan_default = step_result->scan_default; + } + break; - case COMBINE_INTERSECT: - firststep = true; - foreach(lc1, cstep->source_stepids) - { - int step_id = lfirst_int(lc1); - PruneStepResult *step_result; + case COMBINE_INTERSECT: + firststep = true; + foreach(lc1, cstep->source_stepids) + { + int step_id = lfirst_int(lc1); + PruneStepResult *step_result; - if (step_id >= cstep->step.step_id) - elog(ERROR, "invalid pruning combine step argument"); - step_result = step_results[step_id]; - Assert(step_result != NULL); + if (step_id >= cstep->step.step_id) + elog(ERROR, "invalid pruning combine step argument"); + step_result = step_results[step_id]; + Assert(step_result != NULL); - if (firststep) - { - /* Copy step's result the first time. */ - result->bound_offsets = step_result->bound_offsets; + if (firststep) + { + /* Copy step's result the first time. */ + result->bound_offsets = step_result->bound_offsets; + result->scan_null = step_result->scan_null; + result->scan_default = step_result->scan_default; + firststep = false; + } + else + { + /* Record datum indexes common to both steps */ + result->bound_offsets = + bms_int_members(result->bound_offsets, + step_result->bound_offsets); + + /* Update whether to scan null and default partitions. */ + if (result->scan_null) result->scan_null = step_result->scan_null; + if (result->scan_default) result->scan_default = step_result->scan_default; - firststep = false; - } - else - { - /* Record datum indexes common to both steps */ - result->bound_offsets = - bms_int_members(result->bound_offsets, - step_result->bound_offsets); - - /* - * Update whether to scan null and default partitions. - */ - if (result->scan_null) - result->scan_null = step_result->scan_null; - if (result->scan_default) - result->scan_default = - step_result->scan_default; - } } - break; + } + break; - default: - elog(ERROR, "invalid pruning combine op: %d", - (int) cstep->combineOp); - } + default: + elog(ERROR, "invalid pruning combine op: %d", + (int) cstep->combineOp); } return result; @@ -1988,7 +1978,8 @@ perform_pruning_combine_step(PartitionPruneContext *context, /* * partkey_datum_from_expr - * Set *value to the constant value if 'expr' provides one + * Evaluate 'expr', set *value to the resulting Datum. Return true if + * evaluation was possible, otherwise false. */ static bool partkey_datum_from_expr(PartitionPruneContext *context, @@ -2009,26 +2000,25 @@ partkey_datum_from_expr(PartitionPruneContext *context, /* * get_matching_hash_bounds - * Determine offset of the hash bound matching the specified value, + * Determine offset of the hash bound matching the specified values, * considering that all the non-null values come from clauses containing - * a compatible hash eqaulity operator and any keys that are null come - * from an IS NULL clause + * a compatible hash equality operator and any keys that are null come + * from an IS NULL clause. * - * In most cases, the result would contain just one bound's offset, although - * the set may be empty if the corresponding hash partition has not been - * created. Also, if insufficient number of values were provided, all bounds - * are returned. + * Generally this function will return a single matching bound offset, + * although if a partition has not been setup for a given modulus then we may + * return no matches. If the number of clauses found don't cover the entire + * partition key, then we'll need to return all offsets. * - * 'nvalues', if non-zero, denotes the number of values contained in 'values' - - * 'values' contains values to be used for pruning appearing in the array in - * respective partition key position. - * 'opstrategy' if non-zero must be HTEqualStrategyNumber. - + * + * 'values' contains Datums indexed by the partition key to use for pruning. + * + * 'nvalues', the number of Datums in the 'values' array. + * * 'partsupfunc' contains partition hashing functions that can produce correct - * hash for the type of the values contained in 'values' - + * hash for the type of the values contained in 'values'. + * * 'nullkeys' is the set of partition keys that are null. */ static PruneStepResult * @@ -2087,15 +2077,16 @@ get_matching_hash_bounds(PartitionPruneContext *context, * get_matching_list_bounds * Determine the offsets of list bounds matching the specified value, * according to the semantics of the given operator strategy + * 'opstrategy' if non-zero must be a btree strategy number. + * + * 'value' contains the value to use for pruning. * * 'nvalues', if non-zero, should be exactly 1, because of list partitioning. - - * 'value' contains the value to use for pruning - - * 'opstrategy' if non-zero must be a btree strategy number - + * * 'partsupfunc' contains the list partitioning comparison function to be used * to perform partition_list_bsearch + * + * 'nullkeys' is the set of partition keys that are null. */ static PruneStepResult * get_matching_list_bounds(PartitionPruneContext *context, @@ -2159,15 +2150,18 @@ get_matching_list_bounds(PartitionPruneContext *context, /* Speical case handling of values coming from a <> operator clause. */ if (opstrategy == InvalidStrategy) { + /* + * First match to all bounds. We'll remove any matching datums below. + */ result->bound_offsets = bms_add_range(NULL, 0, - boundinfo->ndatums - 1);; + boundinfo->ndatums - 1); off = partition_list_bsearch(partsupfunc, partcollation, boundinfo, value, &is_equal); if (off >= 0 && is_equal) { - /* All bounds except this one qualify. */ + /* We have a match. Remove from the result. */ Assert(boundinfo->indexes[off] >= 0); result->bound_offsets = bms_del_member(result->bound_offsets, off); @@ -2284,13 +2278,12 @@ get_matching_list_bounds(PartitionPruneContext *context, * If default partition needs to be scanned for given values, set scan_default * in result if present. * - * 'nvalues', if non-zero, should be <= context->partnatts - 1 - - * 'values' contains values for partition keys (or a prefix) to be used for - * pruning - - * 'opstrategy' if non-zero must be a btree strategy number - + * 'opstrategy' if non-zero must be a btree strategy number. + * + * 'values' contains Datums indexed by the partition key to use for pruning. + * + * 'nvalues', number of Datums in 'values' array. Must be <= context->partnatts. + * * 'partsupfunc' contains the range partitioning comparison functions to be * used to perform partition_range_datum_bsearch or partition_rbound_datum_cmp * using. @@ -2361,9 +2354,7 @@ get_matching_range_bounds(PartitionPruneContext *context, switch (opstrategy) { case BTEqualStrategyNumber: - /* - * Look for the smallest bound that is = look-up value. - */ + /* Look for the smallest bound that is = look-up value. */ off = partition_range_datum_bsearch(partsupfunc, partcollation, boundinfo, @@ -2420,12 +2411,12 @@ get_matching_range_bounds(PartitionPruneContext *context, boundinfo->kind[off], values, nvalues)); /* - * We can treat off as the offset of the smallest bound to - * be included in the result, if we know it is the upper - * bound of the partition in which the look-up value could - * possibly exist. One case it couldn't is if the bound, - * or precisely the matched portion of its prefix, is not - * inclusive. + * We can treat 'off' as the offset of the smallest bound + * to be included in the result, if we know it is the + * upper bound of the partition in which the look-up value + * could possibly exist. One case it couldn't is if the + * bound, or precisely the matched portion of its prefix, + * is not inclusive. */ if (boundinfo->kind[off][nvalues] == PARTITION_RANGE_DATUM_MINVALUE) @@ -2552,11 +2543,11 @@ get_matching_range_bounds(PartitionPruneContext *context, * offset of one of them, find others by checking adjacent * bounds. * - * Based on whether the look-up values is inclusive or + * Based on whether the look-up values are inclusive or * not, we must either include the indexes of all such * bounds in the result (that is, set minoff to the index * of smallest such bound) or find the smallest one that's - * greater than the look-up value and set minoff to that. + * greater than the look-up values and set minoff to that. */ while (off >= 1 && off < boundinfo->ndatums - 1) { @@ -2729,9 +2720,8 @@ get_matching_range_bounds(PartitionPruneContext *context, } Assert(minoff >= 0 && maxoff >= 0); - if (minoff > maxoff) - return result; - result->bound_offsets = bms_add_range(NULL, minoff, maxoff); + if (minoff <= maxoff) + result->bound_offsets = bms_add_range(NULL, minoff, maxoff); return result; } diff --git a/src/backend/optimizer/util/partprune.c b/src/backend/optimizer/util/partprune.c index cc98dc5ea0..75b7232f5d 100644 --- a/src/backend/optimizer/util/partprune.c +++ b/src/backend/optimizer/util/partprune.c @@ -1,15 +1,10 @@ /*------------------------------------------------------------------------- * * partprune.c - * Provides the functionality to match the provided set of clauses with - * the partition key to partition pruning "steps" - * - * If the "steps" contain enough information, partitions can be pruned right - * away in this module, which is called "static pruning", as all the needed - * information is statically available in the query being planned. Otherwise, - * they'd need to be delivered to the executor where the missing information - * can be filled and pruning tried one more time, which would be called - * "dynamic pruning". + * Parses clauses attempting to match them up to partition keys of a + * given relation and generates a set of "pruning steps", which can be + * later "executed" either from the planner or the executor to determine + * the minimum set of partitions which match the given clauses. * * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California @@ -186,9 +181,8 @@ prune_append_rel_partitions(RelOptInfo *rel) * generate_partition_pruning_steps * Processes 'clauses' and returns a list of "partition pruning steps" * - * If static_pruning is true, include in the result only steps that contain at - * least one Const. If any of the clause in the input list is a - * pseudo-constant "false", *constfalse is set to true upon return. + * If any of the clause in the input list is a pseudo-constant "false", + * *constfalse is set to true upon return. */ List * generate_partition_pruning_steps(RelOptInfo *rel, List *clauses, @@ -253,9 +247,8 @@ generate_partition_pruning_steps(RelOptInfo *rel, List *clauses, * arguments and generate PartitionPruneStepCombine step that will combine * results of those steps. * - * All of the generated steps are added to the global array context->steps and - * each one gets an identifier which is unique across all recursive - * invocations. + * All of the generated steps are added to the context's steps List and each + * one gets an identifier which is unique across all recursive invocations. * * If when going through clauses, we find any that are marked as pseudoconstant * and contains a constant false value, we stop generating any further steps @@ -294,6 +287,7 @@ generate_partition_pruning_steps_internal(RelOptInfo *rel, clause = rinfo->clause; if (rinfo->pseudoconstant && + IsA(rinfo->clause, Const) && !DatumGetBool(((Const *) clause)->constvalue)) { *constfalse = true; @@ -309,8 +303,8 @@ generate_partition_pruning_steps_internal(RelOptInfo *rel, * * While steps generated for the arguments themselves will be * added to context->steps during recursion and will be evaluated - * indepdently, collect their step IDs to be stored in the combine - * step we'll be creating. + * independently, collect their step IDs to be stored in the + * combine step we'll be creating. */ if (or_clause((Node *) clause)) { @@ -353,7 +347,7 @@ generate_partition_pruning_steps_internal(RelOptInfo *rel, * In case of the latter, we cannot prune using such * an arg. To indicate that to the pruning code, we * must construct a dummy PartitionPruneStepCombine - * whose source_stepids is set to to an empty List. + * whose source_stepids is set to an empty List. * However, if we can prove using constraint exclusion * that the clause refutes the table's partition * constraint (if it's sub-partitioned), we need not @@ -440,7 +434,9 @@ generate_partition_pruning_steps_internal(RelOptInfo *rel, /* * Fall-through for a NOT clause, which if it's a Boolean clause - * clause, will be handled in match_clause_to_partition_key(). + * clause, will be handled in match_clause_to_partition_key(). We + * currently don't perform any pruning for more complex NOT + * clauses. */ } @@ -538,8 +534,8 @@ generate_partition_pruning_steps_internal(RelOptInfo *rel, } /* - * generate_opsteps set to false means no OpExprs were directly present in - * the input list. + * If generate_opsteps is set to false it means no OpExprs were directly + * present in the input list. */ if (!generate_opsteps) { @@ -582,7 +578,7 @@ generate_partition_pruning_steps_internal(RelOptInfo *rel, /* * Finally, results from all entries appearing in result should be - * combined using an AND combine step, if there are more than 1. + * combined using an INTERSECT combine step, if there are more than 1. */ if (list_length(result) > 1) { @@ -616,7 +612,7 @@ generate_partition_pruning_steps_internal(RelOptInfo *rel, /* * match_clause_to_partition_key - * Match a given clause with the specified partition key + * Attempt to match the given 'clause' with the specified partition key. * * Return value: * @@ -669,13 +665,15 @@ match_clause_to_partition_key(RelOptInfo *rel, */ if (match_boolean_partition_clause(partopfamily, clause, partkey, &expr)) { - *pc = palloc0(sizeof(PartClauseInfo)); + *pc = palloc(sizeof(PartClauseInfo)); (*pc)->keyno = partkeyidx; /* Do pruning with the Boolean equality operator. */ (*pc)->opno = BooleanEqualOperator; + (*pc)->isopne = false; (*pc)->expr = expr; /* We know that expr is of Boolean type. */ (*pc)->cmpfn = rel->part_scheme->partsupfunc[partkeyidx].fn_oid; + (*pc)->op_strategy = InvalidStrategy; return PARTCLAUSE_MATCH_CLAUSE; } @@ -808,16 +806,23 @@ match_clause_to_partition_key(RelOptInfo *rel, else cmpfn = part_scheme->partsupfunc[partkeyidx].fn_oid; - *pc = palloc0(sizeof(PartClauseInfo)); + *pc = palloc(sizeof(PartClauseInfo)); (*pc)->keyno = partkeyidx; /* For <> operator clauses, pass on the negator. */ (*pc)->isopne = false; + (*pc)->op_strategy = InvalidStrategy; + if (is_opne_listp) { Assert(OidIsValid(negator)); (*pc)->opno = negator; (*pc)->isopne = true; + /* + * We already know the strategy in this case, so may as well set + * it rather than having to look it up later. + */ + (*pc)->op_strategy = BTEqualStrategyNumber; } /* And if commuted before matching, pass on the commutator */ else if (OidIsValid(commutator)) @@ -1033,10 +1038,11 @@ match_boolean_partition_clause(Oid partopfamily, Expr *clause, Expr *partkey, { Expr *leftop; + *rightop = NULL; + if (!IsBooleanOpfamily(partopfamily)) return false; - *rightop = NULL; if (IsA(clause, BooleanTest)) { BooleanTest *btest = (BooleanTest *) clause; @@ -1124,9 +1130,9 @@ generate_pruning_steps_from_opexprs(PartitionScheme part_scheme, break; /* - * For hash partitioning, if a column doesn't have necessary equality - * clause, there should be an IS NULL clause, otherwise pruning is not - * possible. + * For hash partitioning, if a column doesn't have the necessary + * equality clause, there should be an IS NULL clause, otherwise + * pruning is not possible. */ if (part_scheme->strategy == PARTITION_STRATEGY_HASH && clauselist == NIL && !bms_is_member(i, nullkeys)) @@ -1220,7 +1226,7 @@ generate_pruning_steps_from_opexprs(PartitionScheme part_scheme, /* * If we've decided that clauses for subsequent partition keys - * wouldn't be useful for pruning, don't look. + * wouldn't be useful for pruning, don't search any further. */ if (!consider_next_key) break; @@ -1252,7 +1258,7 @@ generate_pruning_steps_from_opexprs(PartitionScheme part_scheme, * contain multiple clauses for the same key, in which case, * we must generate steps for various combinations of * expressions of different keys, which get_steps_using_prefix - * takes care of doing. + * takes care of for us. */ for (i = 0; i < BTMaxStrategyNumber; i++) { @@ -1357,15 +1363,16 @@ generate_pruning_steps_from_opexprs(PartitionScheme part_scheme, ListCell *lc1; /* - * Locate the clause for the greatest column (which may - * not be the last partition key column). Actually, the - * last element of eq_clauses must give us what we need. + * Locate the clause for the greatest column. This may + * not belong to the last partition key, but it is the + * clause belonging to the last partition key we found a + * clause for above. */ pc = llast(eq_clauses); /* - * But there might be multiple clauses that we matched to - * that column; go to the first such clause. While at it, + * There might be multiple clauses which matched to that + * partition key; find the first such clause. While at it, * add all the clauses before that one to 'prefix'. */ last_keyno = pc->keyno; @@ -1385,7 +1392,7 @@ generate_pruning_steps_from_opexprs(PartitionScheme part_scheme, * contain multiple clauses for the same key, in which * case, we must generate steps for various combinations * of expressions of different keys, which - * get_steps_using_prefix takes care of doing. + * get_steps_using_prefix will take care of for us. */ for_each_cell(lc1, lc) { @@ -1394,7 +1401,8 @@ generate_pruning_steps_from_opexprs(PartitionScheme part_scheme, /* * Note that we pass nullkeys for step_nullkeys, * because we need to tell hash partition bound search - * function which of the keys are NULL. + * function which of the keys we found IS NULL clauses + * for. */ Assert(pc->op_strategy == HTEqualStrategyNumber); pc_steps = @@ -1432,7 +1440,7 @@ generate_pruning_steps_from_opexprs(PartitionScheme part_scheme, if (opstep_ids != NIL) return generate_pruning_step_combine(context, opstep_ids, - COMBINE_INTERSECT); + COMBINE_INTERSECT); return NULL; } else if (opsteps != NIL) @@ -1496,7 +1504,7 @@ get_steps_using_prefix(GeneratePruningStepsContext *context, * * 'start' is where we should start iterating for the current invocation. * 'step_exprs' and 'step_cmpfns' each contains the expressions and cmpfns - * we've generated so far from the clauses for the still earlier columns. + * we've generated so far from the clauses for the previous part keys. */ static List * get_steps_using_prefix_recurse(GeneratePruningStepsContext *context, @@ -1618,11 +1626,10 @@ get_steps_using_prefix_recurse(GeneratePruningStepsContext *context, } /* - * Following functions generate pruning steps of various types. Each step - * that's created is added to a global context->steps and receive a globally - * unique identifier that's sourced from context->next_step_id. + * The following functions generate pruning steps of various types. Each step + * that's created is added to a context's 'steps' List and receives unique + * step identifier. */ - static PartitionPruneStep * generate_pruning_step_op(GeneratePruningStepsContext *context, int opstrategy, bool isopne, diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index c9d2187631..965eb656a8 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -1527,13 +1527,13 @@ typedef struct PartitionPruneStep * This contains information extracted from up to partnatts OpExpr clauses, * where partnatts is the number of partition key columns. 'opstrategy' is the * strategy of the operator in the clause matched to the last partition key. - * 'exprs' contains expressions which comprise the look-up key to be passed to + * 'exprs' contains expressions which comprise the lookup key to be passed to * the partition bound search function. 'cmpfns' contains the OIDs of * comparison function used to compare aforementioned expressions with * partition bounds. Both 'exprs' and 'cmpfns' contain the same number of * items up to partnatts items. * - * Once we find the offset of a partition bound using the look-up key, we + * Once we find the offset of a partition bound using the lookup key, we * determine which partitions to include in the result based on the value of * 'opstrategy'. For example, if it were equality, we'd return just the * partition that would contain that key or a set of partitions if the key diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index 0be31cce7e..2d77b3edd4 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -1079,8 +1079,7 @@ explain (costs off) select * from boolpart where a is not unknown; -- -- pruning for partitioned table appearing inside a sub-query -- --- pruning won't work for mc3p, because the leading key (a) is compared to a --- Param, which turns off the static pruning +-- pruning won't work for mc3p, because some keys are Params explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2 where t2.a = t1.b and abs(t2.b) = 1 and t2.c = 1) s where t1.a = 1; QUERY PLAN ----------------------------------------------------------------------- diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql index 8377671cde..ad5177715c 100644 --- a/src/test/regress/sql/partition_prune.sql +++ b/src/test/regress/sql/partition_prune.sql @@ -159,9 +159,7 @@ explain (costs off) select * from boolpart where a is not unknown; -- -- pruning for partitioned table appearing inside a sub-query -- - --- pruning won't work for mc3p, because the leading key (a) is compared to a --- Param, which turns off the static pruning +-- pruning won't work for mc3p, because some keys are Params explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2 where t2.a = t1.b and abs(t2.b) = 1 and t2.c = 1) s where t1.a = 1; -- pruning should work fine, because values for a prefix of keys (a, b) are