Hi, On Fri, 16 Oct 2026 at 06:28, Richard Guo <[email protected]> wrote: > > The query below triggers an assertion failure on v14 to master: > > create table t (a int); > > select ss.z > from (select 1) s1 left join > ((select 1) s2 left join (select 1 as x) v on true > cross join (lateral (select coalesce(v.x, 0) as z, a from t) ss > cross join lateral (select ss.a offset 0) g)) > on true; > > TRAP: failed Assert("!bms_is_member(rti, lateral_relids)") > > The problem is in remove_useless_result_rtes(), which gets rid of > RTE_RESULT RTEs from the jointree, since an RTE_RESULT returns one row > and no columns. The tricky part is PHVs that are supposed to be > evaluated at the RTE_RESULT; they have to go somewhere else once it's > removed. If the RTE_RESULT is under an inner join, remove_result_refs() > makes them be evaluated at the join of its sibling rels instead. That > is fine as long as no sibling needs the PHVs before that join is > formed, and find_dependent_phvs_in_jointree() is what checks this, by > looking for the PHVs in the siblings' join quals and LATERAL RTEs. > > In this query, the reference to v.x inside ss sits above the s2/v left > join, so it gets wrapped as PHV(1). That left join is then removed, > and PHV(1) is relabeled to be evaluated at s2. The reference to ss.z > in the targetlist sits above the outer left join, and since coalesce() > is not strict, it gets wrapped as PHV(coalesce(PHV(1), 0)), which is > evaluated at t. Meanwhile g laterally references t.
Thanks for the script, I could repro it. > Now when we consider removing s2, whose sibling is (ss cross join g), > the check doesn't find anything, because PHV(1) only shows up inside > the outer PHV in the targetlist. So s2 is removed and PHV(1) is moved > up to the join of t and g. But the outer PHV is still evaluated at t, > and it now needs a value that only exists at the join of t and g. > That makes t laterally dependent on g, and since g is already > laterally dependent on t, we end up with a cycle! :-O > > So ISTM the problem is that find_dependent_phvs_in_jointree() doesn't > notice a PHV that is evaluated at some but not all of the sibling rels > and contains one of the RTE_RESULT's PHVs. Such a PHV can live > anywhere in the query, so looking at the jointree alone isn't enough. > > I'm trying to fix this in the attached patch by also searching the > whole query for such PHVs with a new walker, and keeping the > RTE_RESULT if we find one. I also thought about a more unified > approach that we use a single walk over the query that keeps track of > where each expression gets evaluated, and refuses the removal whenever > one of the RTE_RESULT's PHVs is used below the join of all the sibling > rels. But that seems too risky for a fix that needs to be > back-patched. The fix makes sense to me. Do we also need to change the comment above find_dependent_phvs()? It still says find_dependent_phvs_in_jointree() is for checking a subtree of the join tree. (With the patch it also walks the whole query for nested PHVs..) And +1 on the risky aspect of back-patching. Regards, Ayush
