Hi Thibaut, On 2019/03/19 23:58, Thibaut Madelaine wrote: > I kept on testing with sub-partitioning. Thanks.
> I found a case, using 2 default partitions, where a default partition is > not pruned: > > -------------- > > create table test2(id int, val text) partition by range (id); > create table test2_20_plus_def partition of test2 default; > create table test2_0_20 partition of test2 for values from (0) to (20) > partition by range (id); > create table test2_0_10 partition of test2_0_20 for values from (0) to (10); > create table test2_10_20_def partition of test2_0_20 default; > > # explain (costs off) select * from test2 where id=5 or id=25; > QUERY PLAN > ----------------------------------------- > Append > -> Seq Scan on test2_0_10 > Filter: ((id = 5) OR (id = 25)) > -> Seq Scan on test2_10_20_def > Filter: ((id = 5) OR (id = 25)) > -> Seq Scan on test2_20_plus_def > Filter: ((id = 5) OR (id = 25)) > (7 rows) > > -------------- > > I have the same output using Amit's v1-delta.patch or Hosoya's > v2_default_partition_pruning.patch. I think I've figured what may be wrong. Partition pruning step generation code should ignore any arguments of an OR clause that won't be true for a sub-partitioned partition, given its partition constraint. In this case, id = 25 contradicts test2_0_20's partition constraint (which is, a IS NOT NULL AND a >= 0 AND a < 20), so the OR clause should really be simplified to id = 5, ignoring the id = 25 argument. Note that we remove id = 25 only for the considerations of pruning and not from the actual clause that's passed to the final plan, although it wouldn't be a bad idea to try to do that. Attached revised delta patch, which includes the fix described above. Thanks, Amit
diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 8317318156..aa32a50477 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -795,6 +795,27 @@ gen_partprune_steps_internal(GeneratePruningStepsContext *context, Expr *arg = lfirst(lc1); bool arg_contradictory; List *argsteps; + List *partconstr = rel->partition_qual; + + /* + * If this OR argument can be proved false for this + * partition, given its partition constraint, we can + * ignore it, that is not try to pass it to the pruning + * code. We should do that especially to avoid pruning + * code wrongly failing to prune the default partition. + */ + if (partconstr) + { + partconstr = (List *) + expression_planner((Expr *) partconstr); + if (rel->relid != 1) + ChangeVarNodes((Node *) partconstr, 1, + rel->relid, 0); + if (predicate_refuted_by(partconstr, + list_make1(arg), + false)) + continue; + } argsteps = gen_partprune_steps_internal(context, rel, @@ -822,32 +843,13 @@ gen_partprune_steps_internal(GeneratePruningStepsContext *context, * an arg. To indicate that to the pruning code, we * must construct a dummy PartitionPruneStepCombine * 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 - * bother with that. That is, we effectively ignore - * this OR arm. */ - List *partconstr = rel->partition_qual; PartitionPruneStep *orstep; /* Just ignore this argument. */ if (arg_contradictory) continue; - if (partconstr) - { - partconstr = (List *) - expression_planner((Expr *) partconstr); - if (rel->relid != 1) - ChangeVarNodes((Node *) partconstr, 1, - rel->relid, 0); - if (predicate_refuted_by(partconstr, - list_make1(arg), - false)) - continue; - } - orstep = gen_prune_step_combine(context, NIL, PARTPRUNE_COMBINE_UNION); arg_stepids = lappend_int(arg_stepids, orstep->step_id); @@ -2759,15 +2761,19 @@ get_matching_range_bounds(PartitionPruneContext *context, * instead as the offset of the upper bound of the greatest * partition that may contain lookup value. If the lookup * value had exactly matched the bound, but it isn't - * inclusive, no need add the adjacent partition. If 'off' is - * -1 indicating that all bounds are greater, then we simply - * end up adding the first bound's offset, that is, 0. + * inclusive, no need add the adjacent partition. */ - else if (off < 0 || !is_equal || inclusive) + else if (!is_equal || inclusive) maxoff = off + 1; else maxoff = off; } + else + /* + * 'off' is -1 indicating that all bounds are greater, so just + * set the first bound's offset as maxoff. + */ + maxoff = off + 1; break; default: