diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 477b9f7fb8..6ac8d9767b 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -102,8 +102,9 @@ static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel, static Path *get_cheapest_parameterized_child_path(PlannerInfo *root, RelOptInfo *rel, Relids required_outer); -static void accumulate_append_subpath(Path *path, - List **subpaths, List **special_subpaths); +static void accumulate_append_subpath(RelOptInfo *parentrel, + RelOptInfo *childrel, Path *path, List **subpaths, + List **special_subpaths); static void set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, @@ -1395,17 +1396,6 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, * AppendPath generated for partitioned tables must record the RT indexes * of partitioned tables that are direct or indirect children of this * Append rel. - * - * AppendPath may be for a sub-query RTE (UNION ALL), in which case, 'rel' - * itself does not represent a partitioned relation, but the child sub- - * queries may contain references to partitioned relations. The loop - * below will look for such children and collect them in a list to be - * passed to the path creation function. (This assumes that we don't need - * to look through multiple levels of subquery RTEs; if we ever do, we - * could consider stuffing the list we generate here into sub-query RTE's - * RelOptInfo, just like we do for partitioned rels, which would be used - * when populating our parent rel with paths. For the present, that - * appears to be unnecessary.) */ if (rel->part_scheme != NULL) { @@ -1435,10 +1425,16 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, Assert(list_length(partitioned_rels) >= 1); } - else if (rel->rtekind == RTE_SUBQUERY) - build_partitioned_rels = true; /* + * For childrels that are themselves inheritance or UNION ALL parents, + * we recursively pullup their Append and MergeAppend subpaths into rel's + * path lists. This effectively flattens the hierarchy and stops nested + * Append/MergeAppend paths forming. This is not done for UNION ALL + * parents with partitioned tables in their subpaths. These are left + * unflattened as run-time partition pruning requires partitioned_rels to + * only contain partitions which belong to a single hierarchy. + * * For every non-dummy child, remember the cheapest path. Also, identify * all pathkeys (orderings) and parameterizations (required_outer sets) * available for the non-dummy member relations. @@ -1471,7 +1467,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, */ if (childrel->pathlist != NIL && childrel->cheapest_total_path->param_info == NULL) - accumulate_append_subpath(childrel->cheapest_total_path, + accumulate_append_subpath(rel, childrel, childrel->cheapest_total_path, &subpaths, NULL); else subpaths_valid = false; @@ -1480,7 +1476,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, if (childrel->partial_pathlist != NIL) { cheapest_partial_path = linitial(childrel->partial_pathlist); - accumulate_append_subpath(cheapest_partial_path, + accumulate_append_subpath(rel, childrel, cheapest_partial_path, &partial_subpaths, NULL); } else @@ -1508,7 +1504,8 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, { /* Partial path is cheaper or the only option. */ Assert(cheapest_partial_path != NULL); - accumulate_append_subpath(cheapest_partial_path, + accumulate_append_subpath(rel, childrel, + cheapest_partial_path, &pa_partial_subpaths, &pa_nonpartial_subpaths); @@ -1528,7 +1525,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, * be given to different workers. For now, we don't try to * figure that out. */ - accumulate_append_subpath(nppath, + accumulate_append_subpath(rel, childrel, nppath, &pa_nonpartial_subpaths, NULL); } @@ -1754,7 +1751,8 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, subpaths_valid = false; break; } - accumulate_append_subpath(subpath, &subpaths, NULL); + accumulate_append_subpath(rel, childrel, subpath, &subpaths, + NULL); } if (subpaths_valid) @@ -1845,9 +1843,9 @@ generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel, if (cheapest_startup != cheapest_total) startup_neq_total = true; - accumulate_append_subpath(cheapest_startup, + accumulate_append_subpath(rel, childrel, cheapest_startup, &startup_subpaths, NULL); - accumulate_append_subpath(cheapest_total, + accumulate_append_subpath(rel, childrel, cheapest_total, &total_subpaths, NULL); } @@ -1946,10 +1944,13 @@ get_cheapest_parameterized_child_path(PlannerInfo *root, RelOptInfo *rel, * accumulate_append_subpath * Add a subpath to the list being built for an Append or MergeAppend. * - * It's possible that the child is itself an Append or MergeAppend path, in - * which case we can "cut out the middleman" and just add its child paths to - * our own list. (We don't try to do this earlier because we need to apply - * both levels of transformation to the quals.) + * For UNION ALL or inheritance parent childrels, we pullup the childrels + * Append and MergeAppend subpaths into 'subpaths' effectively bypassing the + * childrel's Append and MergeAppend paths. We don't do this for partitioned + * childrels which are parented by UNION ALL parents as mixed partition + * hierarchies are not compatible with run-time partition pruning. + * (We perform the pullup operation here rather than earlier because we need + * to apply both levels of transformation to the quals.) * * Note that if we omit a child MergeAppend in this way, we are effectively * omitting a sort step, which seems fine: if the parent is to be an Append, @@ -1965,8 +1966,15 @@ get_cheapest_parameterized_child_path(PlannerInfo *root, RelOptInfo *rel, * paths). */ static void -accumulate_append_subpath(Path *path, List **subpaths, List **special_subpaths) +accumulate_append_subpath(RelOptInfo *parentrel, RelOptInfo *childrel, + Path *path, List **subpaths, + List **special_subpaths) { + if (parentrel->rtekind == RTE_SUBQUERY && childrel->part_scheme != NULL) + { + *subpaths = lappend(*subpaths, path); + return; + } if (IsA(path, AppendPath)) { AppendPath *apath = (AppendPath *) path; diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index cf82b7052d..b848897693 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -1065,6 +1065,12 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path) return plan; } + /* + * Ensure that partitioned_rels is set for partitioned tables, and never + * set otherwise. + */ + Assert((best_path->partitioned_rels != NIL) == (rel->part_scheme != NULL)); + /* Build the plan for each child */ foreach(subpaths, best_path->subpaths) { @@ -1077,8 +1083,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path) subplans = lappend(subplans, subplan); } - if (enable_partition_pruning && - rel->reloptkind == RELOPT_BASEREL && + if (enable_partition_pruning && IS_SIMPLE_REL(rel) && best_path->partitioned_rels != NIL) { List *prunequal; diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index ab32c7d67e..6ad59a1afe 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -2768,6 +2768,27 @@ select * from boolp where a = (select value from boolvalues where not value); Filter: (a = $0) (9 rows) +-- Ensure runtime pruning works when partitioned tables are parented by +-- UNION ALL parents +explain (analyze, costs off, summary off, timing off) +select * from (select * from boolp union all select * from boolp) b where a = (select true); + QUERY PLAN +------------------------------------------------------------------- + Append (actual rows=0 loops=1) + InitPlan 1 (returns $0) + -> Result (actual rows=1 loops=1) + -> Append (actual rows=0 loops=1) + -> Seq Scan on boolp_f (never executed) + Filter: (a = $0) + -> Seq Scan on boolp_t (actual rows=0 loops=1) + Filter: (a = $0) + -> Append (actual rows=0 loops=1) + -> Seq Scan on boolp_f boolp_f_1 (never executed) + Filter: (a = $0) + -> Seq Scan on boolp_t boolp_t_1 (actual rows=0 loops=1) + Filter: (a = $0) +(13 rows) + drop table boolp; reset enable_indexonlyscan; -- diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql index 609fe09aeb..a7610ef550 100644 --- a/src/test/regress/sql/partition_prune.sql +++ b/src/test/regress/sql/partition_prune.sql @@ -701,6 +701,11 @@ select * from boolp where a = (select value from boolvalues where value); explain (analyze, costs off, summary off, timing off) select * from boolp where a = (select value from boolvalues where not value); +-- Ensure runtime pruning works when partitioned tables are parented by +-- UNION ALL parents +explain (analyze, costs off, summary off, timing off) +select * from (select * from boolp union all select * from boolp) b where a = (select true); + drop table boolp; reset enable_indexonlyscan;