]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Calculate agglevelsup correctly when Aggref contains a CTE.
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 17 Sep 2025 20:32:57 +0000 (16:32 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 17 Sep 2025 20:32:57 +0000 (16:32 -0400)
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
src/test/regress/expected/with.out
src/test/regress/sql/with.sql

index 3115296f3305d99b678394ab3a5743c2286d14ff..216c4880f190270a6444aeaa82bd5f60e5346d51 100644 (file)
@@ -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;
        }
index ea85a175252bfb38daef65e3ce5aa50a2b164f34..5fc248411d1b523d619b4400241a4cf50cc42062 100644 (file)
@@ -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
 --
index d93f16f362b508c2828d2380b5d0e7b75f1c169d..5602a676eb0add0a7911038088046c6a4073e248 100644 (file)
@@ -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
 --