While looking into issue [1], I came across $subject on master. Below is how to reproduce it.
DROP TABLE IF EXISTS t1,t2,t3,t4 CASCADE; CREATE TABLE t1 AS SELECT true AS x FROM generate_series(0,1) x; CREATE TABLE t2 AS SELECT true AS x FROM generate_series(0,1) x; CREATE TABLE t3 AS SELECT true AS x FROM generate_series(0,1) x; CREATE TABLE t4 AS SELECT true AS x FROM generate_series(0,1) x; ANALYZE; explain (costs off) select * from t1 left join (t2 left join t3 on t2.x) on t2.x left join t4 on t3.x and t2.x where t1.x = coalesce(t2.x,true); I've looked into this a little bit. For the join of t2/t3 to t4, since it can commute with the join of t1 to t2/t3 according to identity 3, we would generate multiple versions for its joinquals. In particular, the qual 't3.x' would have two versions, one with varnullingrels as {t2/t3, t1/t2}, the other one with varnullingrels as {t2/t3}. So far so good. Assume we've determined to build the join of t2/t3 to t4 after we've built t1/t2 and t2/t3, then we'd find that both versions of qual 't3.x' would be accepted by clause_is_computable_at. This is not correct. We are supposed to accept only the one marked as {t2/t3, t1/t2}. The other one is not rejected mainly because we found that the qual 't3.x' does not mention any nullable Vars of outer join t1/t2. I wonder if we should consider syn_xxxhand rather than min_xxxhand in clause_is_computable_at when we check if clause mentions any nullable Vars. But I'm not sure about that. [1] https://www.postgresql.org/message-id/flat/0b819232-4b50-f245-1c7d-c8c61bf41827%40postgrespro.ru Thanks Richard