postgres.git / summary / log / commit / refs

commit    1c7358099cbe77bc622bc817ec4e9d919ca91fcf
Author:   Richard Guo <rguo@postgresql.org>
Date:     Wed Feb 25 02:13:21 2026 +0000
First released in: 18.4

    Fix unsafe RTE_GROUP removal in simplify_EXISTS_query
    
    When simplify_EXISTS_query removes the GROUP BY clauses from an EXISTS
    subquery, it previously deleted the RTE_GROUP RTE directly from the
    subquery's range table.
    
    This approach is dangerous because deleting an RTE from the middle of
    the rtable list shifts the index of any subsequent RTE, which can
    silently corrupt any Var nodes in the query tree that reference those
    later relations.  (Currently, this direct removal has not caused
    problems because the RTE_GROUP RTE happens to always be the last entry
    in the rtable list.  However, relying on that is extremely fragile and
    seems like trouble waiting to happen.)
    
    Instead of deleting the RTE_GROUP RTE, this patch converts it in-place
    to be RTE_RESULT type and clears its groupexprs list.  This preserves
    the length and indexing of the rtable list, ensuring all Var
    references remain intact.
    
    Reported-by: Tom Lane <tgl@sss.pgh.pa.us>
    Author: Richard Guo <guofenglinux@gmail.com>
    Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
    Discussion: https://postgr.es/m/3472344.1771858107@sss.pgh.pa.us
    Backpatch-through: 18


src/backend/optimizer/plan/subselect.c | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index e7cb3fede66..0e567890c72 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -1618,8 +1618,6 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink, static bool simplify_EXISTS_query(PlannerInfo *root, Query *query) { - ListCell *lc; - /* * We don't try to simplify at all if the query uses set operations, * aggregates, grouping sets, SRFs, modifying CTEs, HAVING, OFFSET, or FOR @@ -1689,25 +1687,27 @@ simplify_EXISTS_query(PlannerInfo *root, Query *query) query->hasDistinctOn = false; /* - * Since we have thrown away the GROUP BY clauses, we'd better remove the - * RTE_GROUP RTE and clear the hasGroupRTE flag. + * Since we have thrown away the GROUP BY clauses, we'd better get rid of + * the RTE_GROUP RTE and clear the hasGroupRTE flag. To safely get rid of + * the RTE_GROUP RTE without shifting the index of any subsequent RTE in + * the rtable, we convert the RTE to be RTE_RESULT type in-place, and zero + * out RTE_GROUP-specific fields. */ - foreach(lc, query->rtable) + if (query->hasGroupRTE) { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - /* - * Remove the RTE_GROUP RTE and clear the hasGroupRTE flag. (Since - * we'll exit the foreach loop immediately, we don't bother with - * foreach_delete_current.) - */ - if (rte->rtekind == RTE_GROUP) + foreach_node(RangeTblEntry, rte, query->rtable) { - Assert(query->hasGroupRTE); - query->rtable = list_delete_cell(query->rtable, lc); - query->hasGroupRTE = false; - break; + if (rte->rtekind == RTE_GROUP) + { + rte->rtekind = RTE_RESULT; + rte->groupexprs = NIL; + + /* A query should only have one RTE_GROUP, so we can stop. */ + break; + } } + + query->hasGroupRTE = false; } return true; [parent: 1b2773179f31]