Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wEts4-004XQN-2H for pgsql-bugs@arkaria.postgresql.org; Mon, 20 Apr 2026 19:01:25 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wEts4-004Ils-00 for pgsql-bugs@arkaria.postgresql.org; Mon, 20 Apr 2026 19:01:24 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wEts3-004Ilk-2Q for pgsql-bugs@lists.postgresql.org; Mon, 20 Apr 2026 19:01:23 +0000 Received: from sss.pgh.pa.us ([68.162.161.243]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1wEts1-00000001xvl-2Wpm for pgsql-bugs@lists.postgresql.org; Mon, 20 Apr 2026 19:01:22 +0000 Received: from sss1.sss.pgh.pa.us (localhost [127.0.0.1]) by sss.pgh.pa.us (8.15.2/8.15.2) with ESMTP id 63KJ1KLg418975; Mon, 20 Apr 2026 15:01:20 -0400 From: Tom Lane To: Richard Guo cc: francois.jehl@pigment.com, pgsql-bugs@lists.postgresql.org, Robert Haas Subject: Re: BUG #19460: FULL JOIN rewriting issue on empty queries In-reply-to: References: <19460-5625143cef66012f@postgresql.org> <53936.1776633020@sss.pgh.pa.us> <81857.1776648374@sss.pgh.pa.us> <88899.1776652344@sss.pgh.pa.us> Comments: In-reply-to Richard Guo message dated "Mon, 20 Apr 2026 11:51:02 +0900" MIME-Version: 1.0 Content-Type: text/plain; charset="UTF-8" Content-ID: <418973.1776711680.1@sss.pgh.pa.us> Content-Transfer-Encoding: 8bit Date: Mon, 20 Apr 2026 15:01:20 -0400 Message-ID: <418974.1776711680@sss.pgh.pa.us> List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk Richard Guo writes: > On Mon, Apr 20, 2026 at 11:32 AM Tom Lane wrote: >> Hmm. The bug also goes away if "t" doesn't have a unique/pkey >> constraint, and I find that easy to understand: we can't apply outer >> join removal unless rel_supports_distinctness/rel_is_distinct_for >> succeed, so that this buggy code in remove_rel_from_restrictinfo >> is not reached. But that logic doesn't consider WHERE constraints >> AFAICS. So I think there is some other code path involved. > Hmm, relation_has_unique_index_for does consider the lower "WHERE t.id > = ..." clause, as that clause is a restriction clause for "t", and > relation_has_unique_index_for automatically adds any usable > restriction clauses for the rel. Ah, I finally got it through my head that there are two distinct proof paths by which we might reach the conclusion that the lower left join is removable. I had been thinking that we were proving that from the combination of the "sub.id = empty_source.id" clause with the unique index on t.id. But we're not, in the query as-submitted, because we pull up the NULL::UUID constant and const-fold that clause to NULL. Instead, it's the lowest "WHERE t.id = ..." that is combined with the unique index to make the proof. So without an equality test there, we don't think the inner side is unique and don't do join removal, thus dodging the bug. The WHERE FALSE bit masks this omission because it causes us to reduce the outer join to a dummy relation anyway, later on. But if you take that out, you can see that join removal is not being performed. I thought it was worth memorializing these two variants in separate test queries, so I did that. The variant without the lowest WHERE has a non-null constant in the LOJ's left-hand side, so it's able to make the removal proof from the "sub.id = empty_source.id" clause. Pushed at cfcd57111 et al. Thanks again for the report! regards, tom lane