diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index 3254c83cc6c..b8340557b34 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -38,6 +38,8 @@ typedef struct ParseState *pstate; int min_varlevel; int min_agglevel; + int min_ctelevel; + RangeTblEntry *min_cte; int sublevels_up; } check_agg_arguments_context; @@ -58,7 +60,8 @@ typedef struct static int check_agg_arguments(ParseState *pstate, List *directargs, List *args, - Expr *filter); + Expr *filter, + int agglocation); static bool check_agg_arguments_walker(Node *node, check_agg_arguments_context *context); static Node *substitute_grouped_columns(Node *node, ParseState *pstate, Query *qry, @@ -339,7 +342,8 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr) min_varlevel = check_agg_arguments(pstate, directargs, args, - filter); + filter, + location); *p_levelsup = min_varlevel; @@ -641,7 +645,8 @@ static int check_agg_arguments(ParseState *pstate, List *directargs, List *args, - Expr *filter) + Expr *filter, + int agglocation) { int agglevel; check_agg_arguments_context context; @@ -649,6 +654,8 @@ check_agg_arguments(ParseState *pstate, context.pstate = pstate; context.min_varlevel = -1; /* signifies nothing found yet */ context.min_agglevel = -1; + context.min_ctelevel = -1; + context.min_cte = NULL; context.sublevels_up = 0; (void) check_agg_arguments_walker((Node *) args, &context); @@ -686,6 +693,20 @@ check_agg_arguments(ParseState *pstate, parser_errposition(pstate, aggloc))); } + /* + * If there's a non-local CTE that's below the aggregate's semantic level, + * complain. It's not quite clear what we should do to fix up such a case + * (treating the CTE reference like a Var seems wrong), and it's also + * unclear whether there is a real-world use for such cases. + */ + if (context.min_ctelevel >= 0 && context.min_ctelevel < agglevel) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("outer-level aggregate cannot use a nested CTE"), + errdetail("CTE \"%s\" is below the aggregate's semantic level.", + context.min_cte->eref->aliasname), + parser_errposition(pstate, agglocation))); + /* * Now check for vars/aggs in the direct arguments, and throw error if * needed. Note that we allow a Var of the agg's semantic level, but not @@ -699,6 +720,7 @@ check_agg_arguments(ParseState *pstate, { context.min_varlevel = -1; context.min_agglevel = -1; + context.min_ctelevel = -1; (void) check_agg_arguments_walker((Node *) directargs, &context); if (context.min_varlevel >= 0 && context.min_varlevel < agglevel) ereport(ERROR, @@ -714,6 +736,13 @@ check_agg_arguments(ParseState *pstate, parser_errposition(pstate, locate_agg_of_level((Node *) directargs, context.min_agglevel)))); + if (context.min_ctelevel >= 0 && context.min_ctelevel < agglevel) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("outer-level aggregate cannot use a nested CTE"), + errdetail("CTE \"%s\" is below the aggregate's semantic level.", + context.min_cte->eref->aliasname), + parser_errposition(pstate, agglocation))); } return agglevel; } @@ -794,11 +823,6 @@ check_agg_arguments_walker(Node *node, 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) @@ -810,9 +834,12 @@ check_agg_arguments_walker(Node *node, /* ignore local CTEs of subqueries */ if (ctelevelsup >= 0) { - if (context->min_varlevel < 0 || - context->min_varlevel > ctelevelsup) - context->min_varlevel = ctelevelsup; + if (context->min_ctelevel < 0 || + context->min_ctelevel > ctelevelsup) + { + context->min_ctelevel = ctelevelsup; + context->min_cte = rte; + } } } return false; /* allow range_table_walker to continue */ diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out index 86fdb85c6c5..f4caedf272f 100644 --- a/src/test/regress/expected/with.out +++ b/src/test/regress/expected/with.out @@ -2300,36 +2300,44 @@ 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 +-- For now, we just throw an error if there's a use of a CTE below the +-- semantic level that the SQL standard assigns to the aggregate. +-- It's not entirely clear what we could do instead that doesn't risk +-- breaking more things than it fixes. +select f1, (with cte1(x,y) as (select 1,2) + select count((select i4.f1 from cte1))) as ss +from int4_tbl i4; +ERROR: outer-level aggregate cannot use a nested CTE +LINE 2: select count((select i4.f1 from cte1))) as ss + ^ +DETAIL: CTE "cte1" is below the aggregate's semantic level. +-- +-- test for bug #19106: interaction of WITH with aggregates +-- +-- the initial fix for #19055 was too aggressive and broke this case 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 expr_1) - SubPlan expr_1 +with a as ( select id from (values (1), (2)) as v(id) ), + b as ( select max((select sum(id) from a)) as agg ) +select agg from b; + QUERY PLAN +-------------------------------------------- + Aggregate + Output: max((InitPlan expr_1).col1) + InitPlan expr_1 -> Aggregate - Output: count((InitPlan expr_2).col1) - InitPlan expr_2 - -> Result - Output: i4.f1 - -> Result -(9 rows) + Output: sum("*VALUES*".column1) + -> Values Scan on "*VALUES*" + Output: "*VALUES*".column1 + -> Result +(8 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) +with a as ( select id from (values (1), (2)) as v(id) ), + b as ( select max((select sum(id) from a)) as agg ) +select agg from b; + agg +----- + 3 +(1 row) -- -- test for nested-recursive-WITH bug diff --git a/src/test/regress/sql/with.sql b/src/test/regress/sql/with.sql index d88d5abb91a..cd25a5e7154 100644 --- a/src/test/regress/sql/with.sql +++ b/src/test/regress/sql/with.sql @@ -1100,16 +1100,26 @@ 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) +-- For now, we just throw an error if there's a use of a CTE below the +-- semantic level that the SQL standard assigns to the aggregate. +-- It's not entirely clear what we could do instead that doesn't risk +-- breaking more things than it fixes. 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 bug #19106: interaction of WITH with aggregates +-- +-- the initial fix for #19055 was too aggressive and broke this case +explain (verbose, costs off) +with a as ( select id from (values (1), (2)) as v(id) ), + b as ( select max((select sum(id) from a)) as agg ) +select agg from b; + +with a as ( select id from (values (1), (2)) as v(id) ), + b as ( select max((select sum(id) from a)) as agg ) +select agg from b; -- -- test for nested-recursive-WITH bug