postgres.git / summary / log / commit / refs

commit    afd18f27615b0e452373acbc8de2e5282dfeb31b
Author:   Tom Lane <tgl@sss.pgh.pa.us>
Date:     Wed Sep 17 20:32:57 2025 +0000
First released in: 13.23, 14.20, 15.15, 16.11, 17.7, 18.0

    Calculate agglevelsup correctly when Aggref contains a CTE.
    
    If an aggregate function call contains a sub-select that has
    an RTE referencing a CTE outside the aggregate, we must treat
    that reference like a Var referencing the CTE's query level
    for purposes of determining the aggregate's level.  Otherwise
    we might reach the nonsensical conclusion that the aggregate
    should be evaluated at some query level higher than the CTE,
    ending in a planner error or a broken plan tree that causes
    executor failures.
    
    Bug: #19055
    Reported-by: BugForge <dllggyx@outlook.com>
    Author: Tom Lane <tgl@sss.pgh.pa.us>
    Discussion: https://postgr.es/m/19055-6970cfa8556a394d@postgresql.org
    Backpatch-through: 13


src/backend/parser/parse_agg.c | 28 +++++++++++++++++++++++++++- src/test/regress/expected/with.out | 34 ++++++++++++++++++++++++++++++++++ src/test/regress/sql/with.sql | 14 ++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index 3115296f330..216c4880f19 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -783,6 +783,32 @@ check_agg_arguments_walker(Node *node, parser_errposition(context->pstate, ((WindowFunc *) node)->location))); } + + if (IsA(node, RangeTblEntry)) + { + /* + * CTE references act similarly to Vars of the CTE's level. Without + * this we might conclude that the Agg can be evaluated above the CTE, + * leading to trouble. + */ + RangeTblEntry *rte = (RangeTblEntry *) node; + + if (rte->rtekind == RTE_CTE) + { + int ctelevelsup = rte->ctelevelsup; + + /* convert levelsup to frame of reference of original query */ + ctelevelsup -= context->sublevels_up; + /* ignore local CTEs of subqueries */ + if (ctelevelsup >= 0) + { + if (context->min_varlevel < 0 || + context->min_varlevel > ctelevelsup) + context->min_varlevel = ctelevelsup; + } + } + return false; /* allow range_table_walker to continue */ + } if (IsA(node, Query)) { /* Recurse into subselects */ @@ -792,7 +818,7 @@ check_agg_arguments_walker(Node *node, result = query_tree_walker((Query *) node, check_agg_arguments_walker, (void *) context, - 0); + QTW_EXAMINE_RTES_BEFORE); context->sublevels_up--; return result; } diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out index ea85a175252..5fc248411d1 100644 --- a/src/test/regress/expected/with.out +++ b/src/test/regress/expected/with.out @@ -2223,6 +2223,40 @@ from int4_tbl; -2147483647 (5 rows) +-- +-- test for bug #19055: interaction of WITH with aggregates +-- +-- The reference to cte1 must determine the aggregate's level, +-- even though it contains no Vars referencing cte1 +explain (verbose, costs off) +select f1, (with cte1(x,y) as (select 1,2) + select count((select i4.f1 from cte1))) as ss +from int4_tbl i4; + QUERY PLAN +----------------------------------- + Seq Scan on public.int4_tbl i4 + Output: i4.f1, (SubPlan 2) + SubPlan 2 + -> Aggregate + Output: count($1) + InitPlan 1 (returns $1) + -> Result + Output: i4.f1 + -> Result +(9 rows) + +select f1, (with cte1(x,y) as (select 1,2) + select count((select i4.f1 from cte1))) as ss +from int4_tbl i4; + f1 | ss +-------------+---- + 0 | 1 + 123456 | 1 + -123456 | 1 + 2147483647 | 1 + -2147483647 | 1 +(5 rows) + -- -- test for nested-recursive-WITH bug -- diff --git a/src/test/regress/sql/with.sql b/src/test/regress/sql/with.sql index d93f16f362b..5602a676eb0 100644 --- a/src/test/regress/sql/with.sql +++ b/src/test/regress/sql/with.sql @@ -1064,6 +1064,20 @@ select ( with cte(foo) as ( values(f1) ) values((select foo from cte)) ) from int4_tbl; +-- +-- test for bug #19055: interaction of WITH with aggregates +-- +-- The reference to cte1 must determine the aggregate's level, +-- even though it contains no Vars referencing cte1 +explain (verbose, costs off) +select f1, (with cte1(x,y) as (select 1,2) + select count((select i4.f1 from cte1))) as ss +from int4_tbl i4; + +select f1, (with cte1(x,y) as (select 1,2) + select count((select i4.f1 from cte1))) as ss +from int4_tbl i4; + -- -- test for nested-recursive-WITH bug -- [parent: f00ad440a5b6]