diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index d668128ccb1..65a2e38fc33 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -937,10 +937,12 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path) if (newitems || best_path->umethod == UNIQUE_PATH_SORT) { /* - * If the top plan node can't do projections, we need to add a Result - * node to help it along. + * If the top plan node can't do projections and its existing target + * list isn't already what we need, we need to add a Result node to + * help it along. */ - if (!is_projection_capable_plan(subplan)) + if (!is_projection_capable_plan(subplan) && + !tlist_same_exprs(newtlist, subplan->targetlist)) subplan = (Plan *) make_result(root, newtlist, NULL, subplan); else subplan->targetlist = newtlist; diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 0847e787c39..670776b032b 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -1379,10 +1379,12 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) { /* * If the top-level plan node is one that cannot do expression - * evaluation, we must insert a Result node to project the + * evaluation and its existing target list isn't already what + * we need, we must insert a Result node to project the * desired tlist. */ - if (!is_projection_capable_plan(result_plan)) + if (!is_projection_capable_plan(result_plan) && + !tlist_same_exprs(sub_tlist, result_plan->targetlist)) { result_plan = (Plan *) make_result(root, sub_tlist, @@ -1542,10 +1544,13 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) * If the top-level plan node is one that cannot do expression * evaluation, we must insert a Result node to project the desired * tlist. (In some cases this might not really be required, but - * it's not worth trying to avoid it.) Note that on second and - * subsequent passes through the following loop, the top-level - * node will be a WindowAgg which we know can project; so we only - * need to check once. + * it's not worth trying to avoid it. In particular, think not to + * skip adding the Result if the initial window_tlist matches the + * top-level plan node's output, because we might change the tlist + * inside the following loop.) Note that on second and subsequent + * passes through the following loop, the top-level node will be a + * WindowAgg which we know can project; so we only need to check + * once. */ if (!is_projection_capable_plan(result_plan)) { diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c index 9728ac8333b..5dc4b835fa3 100644 --- a/src/backend/optimizer/util/tlist.c +++ b/src/backend/optimizer/util/tlist.c @@ -158,6 +158,43 @@ get_tlist_exprs(List *tlist, bool includeJunk) } +/* + * tlist_same_exprs + * Check whether two target lists contain the same expressions + * + * Note: this function is used to decide whether it's safe to jam a new tlist + * into a non-projection-capable plan node. Obviously we can't do that unless + * the node's tlist shows it already returns the column values we want. + * However, we can ignore the TargetEntry attributes resname, ressortgroupref, + * resorigtbl, resorigcol, and resjunk, because those are only labelings that + * don't affect the row values computed by the node. (Moreover, if we didn't + * ignore them, we'd frequently fail to make the desired optimization, since + * the planner tends to not bother to make resname etc. valid in intermediate + * plan nodes.) Note that on success, the caller must still jam the desired + * tlist into the plan node, else it won't have the desired labeling fields. + */ +bool +tlist_same_exprs(List *tlist1, List *tlist2) +{ + ListCell *lc1, + *lc2; + + if (list_length(tlist1) != list_length(tlist2)) + return false; /* not same length, so can't match */ + + forboth(lc1, tlist1, lc2, tlist2) + { + TargetEntry *tle1 = (TargetEntry *) lfirst(lc1); + TargetEntry *tle2 = (TargetEntry *) lfirst(lc2); + + if (!equal(tle1->expr, tle2->expr)) + return false; + } + + return true; +} + + /* * Does tlist have same output datatypes as listed in colTypes? * diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h index 6053ef0188d..0d6f05199ae 100644 --- a/src/include/optimizer/tlist.h +++ b/src/include/optimizer/tlist.h @@ -25,6 +25,9 @@ extern List *flatten_tlist(List *tlist, PVCAggregateBehavior aggbehavior, extern List *add_to_flat_tlist(List *tlist, List *exprs); extern List *get_tlist_exprs(List *tlist, bool includeJunk); + +extern bool tlist_same_exprs(List *tlist1, List *tlist2); + extern bool tlist_same_datatypes(List *tlist, List *colTypes, bool junkOK); extern bool tlist_same_collations(List *tlist, List *colCollations, bool junkOK); diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index 8392c27b368..18daf95c668 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -385,61 +385,57 @@ create table nv_child_2011 () inherits (nv_parent); alter table nv_child_2010 add check (d between '2010-01-01'::date and '2010-12-31'::date) not valid; alter table nv_child_2011 add check (d between '2011-01-01'::date and '2011-12-31'::date) not valid; explain (costs off) select * from nv_parent where d between '2011-08-01' and '2011-08-31'; - QUERY PLAN ---------------------------------------------------------------------------------- - Result - -> Append - -> Seq Scan on nv_parent - Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date)) - -> Seq Scan on nv_child_2010 - Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date)) - -> Seq Scan on nv_child_2011 - Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date)) -(8 rows) + QUERY PLAN +--------------------------------------------------------------------------- + Append + -> Seq Scan on nv_parent + Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date)) + -> Seq Scan on nv_child_2010 + Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date)) + -> Seq Scan on nv_child_2011 + Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date)) +(7 rows) create table nv_child_2009 (check (d between '2009-01-01'::date and '2009-12-31'::date)) inherits (nv_parent); explain (costs off) select * from nv_parent where d between '2011-08-01'::date and '2011-08-31'::date; - QUERY PLAN ---------------------------------------------------------------------------------- - Result - -> Append - -> Seq Scan on nv_parent - Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date)) - -> Seq Scan on nv_child_2010 - Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date)) - -> Seq Scan on nv_child_2011 - Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date)) -(8 rows) + QUERY PLAN +--------------------------------------------------------------------------- + Append + -> Seq Scan on nv_parent + Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date)) + -> Seq Scan on nv_child_2010 + Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date)) + -> Seq Scan on nv_child_2011 + Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date)) +(7 rows) explain (costs off) select * from nv_parent where d between '2009-08-01'::date and '2009-08-31'::date; - QUERY PLAN ---------------------------------------------------------------------------------- - Result - -> Append - -> Seq Scan on nv_parent - Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date)) - -> Seq Scan on nv_child_2010 - Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date)) - -> Seq Scan on nv_child_2011 - Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date)) - -> Seq Scan on nv_child_2009 - Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date)) -(10 rows) + QUERY PLAN +--------------------------------------------------------------------------- + Append + -> Seq Scan on nv_parent + Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date)) + -> Seq Scan on nv_child_2010 + Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date)) + -> Seq Scan on nv_child_2011 + Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date)) + -> Seq Scan on nv_child_2009 + Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date)) +(9 rows) -- after validation, the constraint should be used alter table nv_child_2011 VALIDATE CONSTRAINT nv_child_2011_d_check; explain (costs off) select * from nv_parent where d between '2009-08-01'::date and '2009-08-31'::date; - QUERY PLAN ---------------------------------------------------------------------------------- - Result - -> Append - -> Seq Scan on nv_parent - Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date)) - -> Seq Scan on nv_child_2010 - Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date)) - -> Seq Scan on nv_child_2009 - Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date)) -(8 rows) + QUERY PLAN +--------------------------------------------------------------------------- + Append + -> Seq Scan on nv_parent + Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date)) + -> Seq Scan on nv_child_2010 + Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date)) + -> Seq Scan on nv_child_2009 + Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date)) +(7 rows) -- Foreign key adding test with mixed types -- Note: these tables are TEMP to avoid name conflicts when this test diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out index 906a928b0c0..cc3670bd914 100644 --- a/src/test/regress/expected/inherit.out +++ b/src/test/regress/expected/inherit.out @@ -1210,24 +1210,22 @@ select * from matest0 order by 1-id; reset enable_indexscan; set enable_seqscan = off; -- plan with fewest seqscans should be merge explain (verbose, costs off) select * from matest0 order by 1-id; - QUERY PLAN ------------------------------------------------------------------------- - Result - Output: matest0.id, matest0.name, ((1 - matest0.id)) - -> Merge Append - Sort Key: ((1 - matest0.id)) - -> Index Scan using matest0i on public.matest0 - Output: matest0.id, matest0.name, (1 - matest0.id) - -> Index Scan using matest1i on public.matest1 - Output: matest1.id, matest1.name, (1 - matest1.id) - -> Sort - Output: matest2.id, matest2.name, ((1 - matest2.id)) - Sort Key: ((1 - matest2.id)) - -> Seq Scan on public.matest2 - Output: matest2.id, matest2.name, (1 - matest2.id) - -> Index Scan using matest3i on public.matest3 - Output: matest3.id, matest3.name, (1 - matest3.id) -(15 rows) + QUERY PLAN +------------------------------------------------------------------ + Merge Append + Sort Key: ((1 - matest0.id)) + -> Index Scan using matest0i on public.matest0 + Output: matest0.id, matest0.name, (1 - matest0.id) + -> Index Scan using matest1i on public.matest1 + Output: matest1.id, matest1.name, (1 - matest1.id) + -> Sort + Output: matest2.id, matest2.name, ((1 - matest2.id)) + Sort Key: ((1 - matest2.id)) + -> Seq Scan on public.matest2 + Output: matest2.id, matest2.name, (1 - matest2.id) + -> Index Scan using matest3i on public.matest3 + Output: matest3.id, matest3.name, (1 - matest3.id) +(13 rows) select * from matest0 order by 1-id; id | name @@ -1258,48 +1256,45 @@ SELECT thousand, tenthous FROM tenk1 UNION ALL SELECT thousand, thousand FROM tenk1 ORDER BY thousand, tenthous; - QUERY PLAN -------------------------------------------------------------------------------- - Result - -> Merge Append - Sort Key: tenk1.thousand, tenk1.tenthous - -> Index Only Scan using tenk1_thous_tenthous on tenk1 - -> Sort - Sort Key: tenk1_1.thousand, tenk1_1.thousand - -> Index Only Scan using tenk1_thous_tenthous on tenk1 tenk1_1 -(7 rows) + QUERY PLAN +------------------------------------------------------------------------- + Merge Append + Sort Key: tenk1.thousand, tenk1.tenthous + -> Index Only Scan using tenk1_thous_tenthous on tenk1 + -> Sort + Sort Key: tenk1_1.thousand, tenk1_1.thousand + -> Index Only Scan using tenk1_thous_tenthous on tenk1 tenk1_1 +(6 rows) explain (costs off) SELECT thousand, tenthous, thousand+tenthous AS x FROM tenk1 UNION ALL SELECT 42, 42, hundred FROM tenk1 ORDER BY thousand, tenthous; - QUERY PLAN ------------------------------------------------------------------------- - Result - -> Merge Append - Sort Key: tenk1.thousand, tenk1.tenthous - -> Index Only Scan using tenk1_thous_tenthous on tenk1 - -> Sort - Sort Key: (42), (42) - -> Index Only Scan using tenk1_hundred on tenk1 tenk1_1 -(7 rows) + QUERY PLAN +------------------------------------------------------------------ + Merge Append + Sort Key: tenk1.thousand, tenk1.tenthous + -> Index Only Scan using tenk1_thous_tenthous on tenk1 + -> Sort + Sort Key: (42), (42) + -> Index Only Scan using tenk1_hundred on tenk1 tenk1_1 +(6 rows) explain (costs off) SELECT thousand, tenthous FROM tenk1 UNION ALL SELECT thousand, random()::integer FROM tenk1 ORDER BY thousand, tenthous; - QUERY PLAN -------------------------------------------------------------------------------- - Result - -> Merge Append - Sort Key: tenk1.thousand, tenk1.tenthous - -> Index Only Scan using tenk1_thous_tenthous on tenk1 - -> Sort - Sort Key: tenk1_1.thousand, ((random())::integer) - -> Index Only Scan using tenk1_thous_tenthous on tenk1 tenk1_1 -(7 rows) + QUERY PLAN +------------------------------------------------------------------------- + Merge Append + Sort Key: tenk1.thousand, tenk1.tenthous + -> Index Only Scan using tenk1_thous_tenthous on tenk1 + -> Sort + Sort Key: tenk1_1.thousand, ((random())::integer) + -> Index Only Scan using tenk1_thous_tenthous on tenk1 tenk1_1 +(6 rows) -- Check min/max aggregate optimization explain (costs off) @@ -1345,16 +1340,15 @@ SELECT x, y FROM UNION ALL SELECT unique2 AS x, unique2 AS y FROM tenk1 b) s ORDER BY x, y; - QUERY PLAN -------------------------------------------------------------------- - Result - -> Merge Append - Sort Key: a.thousand, a.tenthous - -> Index Only Scan using tenk1_thous_tenthous on tenk1 a - -> Sort - Sort Key: b.unique2, b.unique2 - -> Index Only Scan using tenk1_unique2 on tenk1 b -(7 rows) + QUERY PLAN +------------------------------------------------------------- + Merge Append + Sort Key: a.thousand, a.tenthous + -> Index Only Scan using tenk1_thous_tenthous on tenk1 a + -> Sort + Sort Key: b.unique2, b.unique2 + -> Index Only Scan using tenk1_unique2 on tenk1 b +(6 rows) reset enable_seqscan; reset enable_indexscan; diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out index f78218822f5..031be5389eb 100644 --- a/src/test/regress/expected/union.out +++ b/src/test/regress/expected/union.out @@ -474,15 +474,14 @@ explain (costs off) UNION ALL SELECT * FROM t2) t WHERE ab = 'ab'; - QUERY PLAN ---------------------------------------------------- - Result - -> Append - -> Index Scan using t1_ab_idx on t1 - Index Cond: ((a || b) = 'ab'::text) - -> Index Only Scan using t2_pkey on t2 - Index Cond: (ab = 'ab'::text) -(6 rows) + QUERY PLAN +--------------------------------------------- + Append + -> Index Scan using t1_ab_idx on t1 + Index Cond: ((a || b) = 'ab'::text) + -> Index Only Scan using t2_pkey on t2 + Index Cond: (ab = 'ab'::text) +(5 rows) explain (costs off) SELECT * FROM @@ -510,10 +509,9 @@ explain (costs off) UNION ALL SELECT 2 AS t, * FROM tenk1 b) c WHERE t = 2; - QUERY PLAN ---------------------------------- - Result - -> Append - -> Seq Scan on tenk1 b -(3 rows) + QUERY PLAN +--------------------------- + Append + -> Seq Scan on tenk1 b +(2 rows)