Re: [PATCH] Allow subquery pull-up past inlineable CTEs

From: Andrey Kazarinov <a(dot)kazarinov(at)postgrespro(dot)ru>
To: Clemenza Zhang <zxlmgsps2(at)gmail(dot)com>
Cc: pgsql-hackers(at)lists(dot)postgresql(dot)org
Subject: Re: [PATCH] Allow subquery pull-up past inlineable CTEs
Date: 2026-09-08 14:19:21
Message-ID: 2f11cdbdd5d905c0a01ef2b2e26e3034@postgrespro.ru
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

> I could not reproduce this exact before-patch plan on current master.
> With the NOT MATERIALIZED example, current unpatched master already
> produces a direct Seq Scan on cte_pullup_t rather than a CTE Scan on
> cte.
>
> Hash Right Join
> Hash Cond: (cte_pullup_t.id = s.tid)
> -> Seq Scan on cte_pullup_t
> -> Hash
> -> Seq Scan on cte_pullup_s s
> Filter: (id < 5)
>
> The patch still changes the higher-level plan shape in my test: the
> inlineable-CTE case becomes the same general shape as the equivalent
> no-CTE query.

Hi Clemenza,

Thank you for testing and for the detailed feedback.

Here is a reproducible example using the standard regression tables
tenk1 and tenk2 (both have 10000 rows; tenk1.unique1 is a primary key):

explain (costs off)
select * from tenk2 s left join (
with cte as not materialized (select unique1, two from tenk1)
select * from (select unique1, two from cte) sub
) t on t.unique1 = s.unique1
where s.unique1 < 10;

Before the patch (unpatched master):
Hash Right Join
Hash Cond: (tenk1.unique1 = s.unique1)
-> Seq Scan on tenk1
-> Hash
-> Bitmap Heap Scan on tenk2 s
Recheck Cond: (unique1 < 10)
-> Bitmap Index Scan on tenk2_unique1
Index Cond: (unique1 < 10)

After the patch:
Nested Loop Left Join
-> Seq Scan on tenk2 s
Filter: (unique1 < 10)
-> Index Scan using tenk1_pkey on tenk1
Index Cond: (unique1 = s.unique1)

Without the patch, the subquery is planned separately: SS_process_ctes
inlines the CTE, but is_simple_subquery still rejects the subquery
because cteList is non-empty - the planner cannot see that the join
condition t.unique1 = s.unique1 could use the primary key index on
tenk1,
so it falls back to a hash join with a full seq scan.

With the patch, the subquery is pulled up into the parent query.
The planner can now see through the former subquery boundary and chooses
a
nested loop with index scan (only 10 index lookups instead of scanning
10000 rows).

> I also noticed a small inconsistency in the comment above
> SS_all_ctes_inlineable(). It says that every CTE is either
> "unreferenced (SELECT) or passes the inlineability checks", but the
> implementation explicitly returns false for:
> ```
> if (cte->cterefcount == 0 && cmdType == CMD_SELECT)
> return false;
> ```
> This behavior matches the explanation in your email, so I think the
> comment may just need adjustment.
>
> Regards,
> Clemenza Zhang

I have fixed the comment above SS_all_ctes_inlineable().
The function header comment now briefly notes that unreferenced CTEs
cause
it to return false, and the inline comment at the check site explains
the reason in detail: unreferenced SELECT CTEs (cterefcount == 0) are
neither inlined nor materialized by SS_process_ctes -- they are simply
skipped with a dummy entry in cte_plan_ids.

Updated patch attached.

P.S. I most likely continue discussion from another email:
kazarandrey(at)yandex(dot)ru

Regards,
Andrey Kazarinov

Attachment Content-Type Size
v2-0001-Allow-subquery-pull-up-past-inlineable-CTEs.patch text/x-diff 14.4 KB

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Richard Guo 2026-09-08 14:43:02 Re: Assert failure in try_nestloop_path()
Previous Message Daniel Gustafsson 2026-09-08 14:13:34 Re: [PATCH v4] Add ssl_cert_files/ssl_key_files for multi-certificate support