Commits on Aug 22, 2026
-
Perform join removal by editing the query's jointree.
analyzejoins.c decided which joins could be dropped by consulting the planner's derived data structures, but then implemented the removal by updating those structures in-place. That is a lot of fiddly work, and nothing keeps it in step with the rest of the planner: remove_leftjoinrel_from_query only bothered to update "parts of the planner's data structures that will actually be consulted later", with no good way to know what those are. Bug #19560 is one consequence. In that report, removing a join leaves an EquivalenceClass that now gives rise to a base restriction clause, but base restriction clauses have already been generated and nothing reconsiders them, so the WHERE condition disappears from the plan and we return wrong answers. The self-join elimination code has the same design and the same type of hazard. We have seen many related bugs over the years too, so it's time to do something drastic. To fix, do the removals by editing root->parse->jointree (which is a far simpler and more stable representation than the derived data), and then have query_planner() discard everything it computed from the jointree and derive it over again. This requires quite a bit less code, and doesn't require touching analyzejoins.c every time we change the data derived by query_planner(), and it doesn't seem to result in any significant planning-time penalty. reduce_unique_semijoins() gets the same treatment: rather than deleting the semijoin's SpecialJoinInfo and relying on the jointree not being consulted again, it now changes the JoinExpr's jointype to JOIN_INNER. Some plans change in the join regression test. Qual evaluation order shifts in a few cases, because the conditions now reach later planning in jointree order rather than in whatever order the removal code re-distributed them. A few plans improve, since the rebuilt relation targetlists no longer carry columns that only a removed join needed. We also detect a constant-false filter condition whose test used to carry a FIXME label. One plan gets marginally worse, because the old code recomputed attr_needed from equivalence classes after a join removal; that is more accurate than what deconstruct_jointree() derives from the original clauses, but we no longer do that. Making that recomputation happen anyway could be worth doing, but it should be considered independently and perhaps implemented differently. Full disclosure: initial drafts of this patch were made with Claude Opus 4.8. Bug: #19560 Reported-by: Orestis Markou <orestis@orestis.gr> Author: Tom Lane <tgl@sss.pgh.pa.us> Reviewed-by: Thom Brown <thom@linux.com> Reviewed-by: Jacob Brazeal <jacob.brazeal@gmail.com> Discussion: https://postgr.es/m/1186816.1784573544@sss.pgh.pa.us Backpatch-through: 16