[PATCH] Prevent lateral dependency cycles in self-join elimination

From: Haibo Yan <tristan(dot)yim(at)gmail(dot)com>
To: pgsql-hackers(at)lists(dot)postgresql(dot)org
Subject: [PATCH] Prevent lateral dependency cycles in self-join elimination
Date: 2026-10-27 23:10:54
Message-ID: CABXr29HQ1jLEMvOQ6bdXYM67U+Lwkn3tJ5hwOM2SQCp46V4e6g@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hello,

Commit 3138f248cd3 fixes a different planner problem: removing an
RTE_RESULT that is needed by a nested PlaceHolderVar. It does not fix
the lateral-dependency cycle triggered later by self-join elimination.
The following query still fails on baseline
663c34c1eb2c71f35955814923ad578d0d9c0cf1, which contains that commit.

Minimal reproducer:

CREATE TABLE t (a int PRIMARY KEY);
INSERT INTO t VALUES (1), (2);

SELECT s.b
FROM t x,
abs(x.a) b,
LATERAL (SELECT b FROM t c WHERE c.a = x.a) s
GROUP BY CUBE (1);

With enable_self_join_elimination = on, an assertion-enabled build
fails at !bms_is_member(rti, lateral_relids) in
create_lateral_join_info(); a non-assertion build reports “failed to
build any 2-way joins”. With self-join elimination disabled, the query
returns the expected multiset {1, 2, NULL}.

The original outer-join form also fails during planning:

CREATE TABLE t (a int PRIMARY KEY);
INSERT INTO t VALUES (1), (2);

SELECT s.y
FROM t d LEFT JOIN
(t x CROSS JOIN LATERAL (SELECT x.a AS v OFFSET 0) b
CROSS JOIN LATERAL (SELECT coalesce(b.v, 0) FROM t c
WHERE c.a = x.a) s(y))
ON true;

With self-join elimination disabled, this returns four rows: two
copies each of 1 and 2.

The earlier fix does not apply because the failure follows a different
planner path. During self-join elimination, x and c are identified as
a removable self-join. Substituting one relation for the other
rewrites the correlated reference while a PlaceHolderVar still carries
a lateral dependency through the intermediate LATERAL relation.
Rebuilding lateral join information then finds a dependency cycle.

The attached patch checks the candidate relation substitution before
removing the self-join. It simulates the substitution, recomputes
PlaceHolderVar evaluation and lateral-dependency sets, and rejects a
merge if the resulting dependency graph contains a cycle. After safe
merges, it updates the relation and PlaceHolderVar state used by later
candidates in the same pass.

The patch is based on 663c34c1eb2c71f35955814923ad578d0d9c0cf1. The
assertion-enabled and non-assertion builds passed the 239-test core
regression schedule. The regression additions cover the minimal and
outer-join reproducers, PHV evaluation-site changes, nested PHVs,
multiple candidate joins, safe merge controls, and an empty physical
table.

Patch SHA-256: 978f682836a50ecd39453cb333c9396403d836e6c60a57d729c7947eccb3ff15

Regards,
Haibo Yan

Attachment Content-Type Size
v1-0001-Prevent-lateral-dependency-cycles-in-self-join-elimination.patch application/octet-stream 26.8 KB

Browse pgsql-hackers by date

  From Date Subject
Previous Message surya poondla 2026-10-27 22:50:07 Re: A bug in recovery prefetcher, triggering asserts / FATAL: checkpoint request failed