Re: PostgreSQL select-only CTE removal is too aggressive?

From: Kirill Reshke <reshkekirill(at)gmail(dot)com>
To: PostgreSQL Hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org>
Subject: Re: PostgreSQL select-only CTE removal is too aggressive?
Date: 2026-06-29 09:02:38
Message-ID: CALdSSPjkzXEbvb6QCrAciPYv-cX6SAJ3_LN_AnBZtFGfsYUY_g@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

On Mon, 29 Jun 2026 at 13:52, Kirill Reshke <reshkekirill(at)gmail(dot)com> wrote:
>
> Hi!
>
> PostgreSQL planner currently replaces not-referenced CTE with dummy
> relations here [0]
>
> But it seems to be too aggressive for subqueries with side-effects:
>
> ```
> create table z(i int, j int, k int);
> create or replace function f() returns void as $$ insert into z
> values(1,1,1) $$ language sql;
> reshke=# explain with d as (select f(1)) select 1;
> QUERY PLAN
> ------------------------------------------
> Result (cost=0.00..0.01 rows=1 width=4)
> (1 row)
>
> ```
>
> I don't find where this behaviour is actually explicitly documented.
>
> For data-modifying CTEs we have this[1]:
>
> ```
> Data-modifying statements in WITH are executed exactly once, and
> always to completion, independently of whether the primary query reads
> all (or indeed any) of their output. Notice that this is different
> from the rule for SELECT in WITH: as stated in the previous section,
> execution of a SELECT is carried only as far as the primary query
> demands its output.
> ```
>
> Maybe we should add here a notice, that execution of an unreferenced
> SELECT CTE, even one containing a data-modifying function, is not
> guaranteed?
>
> [0] https://git.postgresql.org/cgit/postgresql.git/tree/src/backend/optimizer/plan/subselect.c?id=26255a320733de2d91a30bd6ae529dd01e7f3409#n905
> [1] https://www.postgresql.org/docs/current/queries-with.html#QUERIES-WITH-MODIFYING
>
>
> --
> Best regards,
> Kirill Reshke

Also, PFA simple patch that fixes exactly my case and maybe breaks
many others. WDYT?

```
reshke(at)yezzey-cbdb-bench:~/pg$ git diff src/backend/optimizer/plan/subselect.c
diff --git a/src/backend/optimizer/plan/subselect.c
b/src/backend/optimizer/plan/subselect.c
index 6aa8971c95d..967fd4e7bf8 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -904,7 +904,7 @@ SS_process_ctes(PlannerInfo *root)
/*
* Ignore SELECT CTEs that are not actually referenced anywhere.
*/
- if (cte->cterefcount == 0 && cmdType == CMD_SELECT)
+ if (cte->cterefcount == 0 && cmdType == CMD_SELECT &&
!contain_volatile_functions(cte->ctequery))
{
/* Make a dummy entry in cte_plan_ids */
root->cte_plan_ids =
lappend_int(root->cte_plan_ids, -1);
```

--
Best regards,
Kirill Reshke

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Ashutosh Sharma 2026-06-29 09:08:38 Re: Report bytes and transactions actually sent downtream
Previous Message Kirill Reshke 2026-06-29 08:52:40 PostgreSQL select-only CTE removal is too aggressive?