From aa4d8f39ee0022797d416da0008c2a28ef8fc92b Mon Sep 17 00:00:00 2001 From: Ayush Tiwari Date: Tue, 28 Jul 2026 05:42:13 +0000 Subject: [PATCH v2] Fix create_gating_plan() dropping a child Result's one-time filter Commit f2bae51dfd5 refactored create_gating_plan(). When it places a gating Result node atop an existing Result, it tries to avoid stacking two Result nodes by absorbing the child. The refactor began doing this unconditionally for any Result child, whereas the previous code only discarded the child when it was trivial, i.e. it had neither a subplan nor a resconstantqual. As a result, when the child Result carried a one-time filter (resconstantqual), that filter was silently discarded, producing wrong query results. For example, a pseudoconstant qual such as "c0 IS NOT NULL" where c0 is a constant NULL becomes a one-time filter on a Result; if another gating qual is applied on top, the original filter was lost and rows that should have been rejected were returned. Restore the original behavior: only absorb the child Result when it is trivial, and otherwise keep it as the subplan so its resconstantqual (or its own subplan) is preserved. This retains the relids/result_type attribution added by f2bae51dfd5 for the trivial case, and respects the invariant that a Result carries relids only when it has no subplan. Add a regression test to join.sql. Bug: #19579 Reported-by: Viktor Leis --- src/backend/optimizer/plan/createplan.c | 21 +++++++++++---------- src/test/regress/expected/join.out | 10 ++++++++++ src/test/regress/sql/join.sql | 7 +++++++ 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 2c696ea0268..ab392ef60fe 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -1033,21 +1033,22 @@ create_gating_plan(PlannerInfo *root, Path *path, Plan *plan, (Node *) gating_quals, plan); /* - * We might have had a trivial Result plan already. Stacking one Result - * atop another is silly, so if that applies, just discard the input plan. - * (We're assuming its targetlist is uninteresting; it should be either - * the same as the result of build_path_tlist, or a simplified version. - * However, we preserve the set of relids that it purports to scan and - * attribute that to our replacement Result instead, and likewise for the - * result_type.) + * See if we can reduce down stacked Result nodes to a single node. This + * is only possible when the nested Result has no subplan and no gating + * qual. If we do remove the nested Result, we maintain the relids and + * result_type for EXPLAIN. */ if (IsA(plan, Result)) { Result *rplan = (Result *) plan; - gplan->plan.lefttree = NULL; - gplan->relids = rplan->relids; - gplan->result_type = rplan->result_type; + if (rplan->plan.lefttree == NULL && + rplan->resconstantqual == NULL) + { + gplan->plan.lefttree = NULL; + gplan->relids = rplan->relids; + gplan->result_type = rplan->result_type; + } } /* diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 19e2cca548b..a7ce08f7263 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -6707,6 +6707,16 @@ select p.* from One-Time Filter: false (3 rows) +select * from ( + select c0 from + (select null::int as c0 from ((select 1) union all (select 2))) t1 + full join (select 1) t2 on true + where c0 is not null +) t3 where now() is not null; + c0 +---- +(0 rows) + -- bug 5255: this is not optimizable by join removal begin; CREATE TEMP TABLE a (id int PRIMARY KEY); diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index 85aed7bf704..836d4b3db07 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -2487,6 +2487,13 @@ select p.* from (parent p left join child c on (p.k = c.k)) join parent x on p.k = x.k where p.k = 1 and p.k = 2; +select * from ( + select c0 from + (select null::int as c0 from ((select 1) union all (select 2))) t1 + full join (select 1) t2 on true + where c0 is not null +) t3 where now() is not null; + -- bug 5255: this is not optimizable by join removal begin; -- 2.43.0