Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vIu9T-0026BJ-0R for pgsql-bugs@arkaria.postgresql.org; Tue, 11 Nov 2025 19:35:37 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1vIu9Q-007IoR-2h for pgsql-bugs@arkaria.postgresql.org; Tue, 11 Nov 2025 19:35:36 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vIu9Q-007IoJ-1s for pgsql-bugs@lists.postgresql.org; Tue, 11 Nov 2025 19:35:36 +0000 Received: from sss.pgh.pa.us ([68.162.161.243]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vIu9K-006b0r-0j for pgsql-bugs@lists.postgresql.org; Tue, 11 Nov 2025 19:35:35 +0000 Received: from sss1.sss.pgh.pa.us (localhost [127.0.0.1]) by sss.pgh.pa.us (8.15.2/8.15.2) with ESMTP id 5ABJZQHD2221262; Tue, 11 Nov 2025 14:35:26 -0500 From: Tom Lane To: "Kamil Monicz" cc: pgsql-bugs@lists.postgresql.org, Peter Eisentraut , Vik Fearing Subject: Re: BUG #19106: Potential regression with CTE materialization planning in Postgres 18 In-reply-to: <1980046.1762808758@sss.pgh.pa.us> References: <19106-9dd3668a0734cd72@postgresql.org> <1650412.1762703636@sss.pgh.pa.us> <1886977.1762794560@sss.pgh.pa.us> <1980046.1762808758@sss.pgh.pa.us> Comments: In-reply-to Tom Lane message dated "Mon, 10 Nov 2025 16:05:58 -0500" MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-ID: <2221260.1762889726.1@sss.pgh.pa.us> Content-Transfer-Encoding: quoted-printable Date: Tue, 11 Nov 2025 14:35:26 -0500 Message-ID: <2221261.1762889726@sss.pgh.pa.us> List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk I wrote: > Here is a draft fix for this. What it basically decides is that > commit b0cc0a71e was in error to suppose that an outer CTE reference > should work like an outer Var reference. In this even-more-simplified > test case: > 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; I wanted to post a little more analysis of what's happening here, mostly for the archives' sake. Since b0cc0a71e, check_agg_arguments_walker mistakenly decides that the max() aggregate ought to belong to the query level where the "a" CTE is, that is the outer "WITH ... SELECT agg FROM b". In an assert-enabled build, check_agglevels_and_constraints promptly crashes at switch (pstate->p_expr_kind) { case EXPR_KIND_NONE: Assert(false); /* can't happen */ break; because it's looking at the ParseState for that outer query level, where we are not examining any particular subexpression. In a non-assert build, we more-or-less-accidentally get through check_agglevels_and_constraints unscathed, but then the parser fails with ERROR: column "b.agg" must appear in the GROUP BY clause or be used in an= aggregate function LINE 7: SELECT agg FROM b; ^ because we've marked the outer query level with p_hasAggs =3D true. (This seems like sufficient proof that we're assigning the aggregate to the wrong level, if you were doubting that conclusion.) The submitted problem query is shaped a little differently, though. It's more nearly WITH a AS ( SELECT id FROM (VALUES (1), (2)) AS v(id) ), b AS ( SELECT max((SELECT sum(id) FROM a)) AS agg ) SELECT (SELECT agg FROM b); and that extra level of sub-select avoids the aforesaid error message, since the sub-select where the "agg" reference is doesn't get marked p_hasAggs. (I wonder whether the OP introduced that extra sub-select in trying to work around this bug.) Rather remarkably, this formulation actually gets the expected answer "3", although if you look at the generated plan it's fairly wacko: postgres=3D# 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 (SELECT agg FROM b); QUERY PLAN = ----------------------------------------------- Aggregate Output: (SubPlan expr_1) InitPlan expr_2 -> Aggregate Output: sum("*VALUES*".column1) -> Values Scan on "*VALUES*" Output: "*VALUES*".column1 -> Result SubPlan expr_1 -> Result Output: max((InitPlan expr_2).col1) (11 rows) I don't quite understand how that works, since the max() call is not within the upper Aggregate's expression trees: why isn't it triggering "Aggref found in non-Agg plan node"? It's probably not worth figuring out though; this is all garbage-in-garbage-out behavior so far as the planner is concerned. Variants of this, such as inserting MATERIALIZED for "b", lead to planner failures like ERROR: unexpected outer reference in CTE query which is similar to the original report. Anyway, the point is that we are assigning the wrong semantic level to the max() aggregate, and all the rest of this behavior is just follow-on effects of that. regards, tom lane