From ef5a832a7fb1f3b2d90381c1184adf1b1218e5cb Mon Sep 17 00:00:00 2001 From: Richard Guo Date: Fri, 6 Nov 2026 16:39:55 +0900 Subject: [PATCH v1] Fix stale initplans after removing a plan level in setrefs.c When set_plan_references() removes a trivial SubqueryScan or a single-child Append or MergeAppend, clean_up_removed_plan_level() moves the removed node's initplans to its child. However, the child's extParam and allParam sets, which SS_finalize_plan() computed before the move, do not include the Params those initplans depend on. At rescan, UpdateChangedParamSet() therefore does not propagate a change in such a Param to the child, and the initplans are not re-evaluated, so the child keeps using their stale output values, producing wrong results. This can happen, for example, with a lateral subquery that scans only one partition of a partitioned table and whose scalar sub-select references an outer column. To fix, merge the removed node's extParam and allParam sets into the child's. Back-patch to all supported branches. Author: Richard Guo Reviewed-by: David Rowley Discussion: https://postgr.es/m/CAMbWs4_JzG96+RjzdEpRQ0-rWkuzU1cmj7xybpzFg6GYdGcE8Q@mail.gmail.com Backpatch-through: 14 --- src/backend/optimizer/plan/setrefs.c | 8 +++++++ src/test/regress/expected/subselect.out | 31 +++++++++++++++++++++++++ src/test/regress/sql/subselect.sql | 17 ++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index af78892fafc..7a04de86879 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -1622,6 +1622,14 @@ clean_up_removed_plan_level(Plan *parent, Plan *child) */ child->initPlan = list_concat(parent->initPlan, child->initPlan); + + /* + * The child must also take over the Params that the initplans depend + * on, else a change in them would not cause the initplans to be + * re-evaluated at rescan. + */ + child->extParam = bms_add_members(child->extParam, parent->extParam); + child->allParam = bms_add_members(child->allParam, parent->allParam); } /* diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out index c78267d1dd9..ef73218d9db 100644 --- a/src/test/regress/expected/subselect.out +++ b/src/test/regress/expected/subselect.out @@ -1636,6 +1636,37 @@ where o.ten = 1; 1700 | 5350 (1 row) +-- +-- Test rescan of an initplan moved to a lower plan node by setrefs.c, here +-- from a removed single-child Append +-- +create temp table rescan_ptbl (a int) partition by list (a); +create temp table rescan_ptbl_1 partition of rescan_ptbl for values in (1); +insert into rescan_ptbl values (1); +explain (costs off) +select v.x, s.y from (values (1), (2)) v(x), + lateral (select (select v.x) as y from rescan_ptbl offset 0) s + where s.y > 0; + QUERY PLAN +--------------------------------------------------- + Nested Loop + -> Values Scan on "*VALUES*" + -> Subquery Scan on s + Filter: (s.y > 0) + -> Seq Scan on rescan_ptbl_1 rescan_ptbl + InitPlan expr_1 + -> Result +(7 rows) + +select v.x, s.y from (values (1), (2)) v(x), + lateral (select (select v.x) as y from rescan_ptbl offset 0) s + where s.y > 0; + x | y +---+--- + 1 | 1 + 2 | 2 +(2 rows) + -- -- Check we don't misoptimize a NOT IN where the subquery returns no rows. -- diff --git a/src/test/regress/sql/subselect.sql b/src/test/regress/sql/subselect.sql index c046b0ebb0d..6db56b24b60 100644 --- a/src/test/regress/sql/subselect.sql +++ b/src/test/regress/sql/subselect.sql @@ -827,6 +827,23 @@ select sum(o.four), sum(ss.a) from ) ss where o.ten = 1; +-- +-- Test rescan of an initplan moved to a lower plan node by setrefs.c, here +-- from a removed single-child Append +-- +create temp table rescan_ptbl (a int) partition by list (a); +create temp table rescan_ptbl_1 partition of rescan_ptbl for values in (1); +insert into rescan_ptbl values (1); + +explain (costs off) +select v.x, s.y from (values (1), (2)) v(x), + lateral (select (select v.x) as y from rescan_ptbl offset 0) s + where s.y > 0; + +select v.x, s.y from (values (1), (2)) v(x), + lateral (select (select v.x) as y from rescan_ptbl offset 0) s + where s.y > 0; + -- -- Check we don't misoptimize a NOT IN where the subquery returns no rows. -- -- 2.37.1 (Apple Git-137.1)