[RFC PATCH v1] FULL JOIN decomposition using LEFT and ANTI joins

From: ChenhuiMo <chenhuimo(dot)mch(at)qq(dot)com>
To: pgsql-hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org>
Subject: [RFC PATCH v1] FULL JOIN decomposition using LEFT and ANTI joins
Date: 2026-09-22 06:44:04
Message-ID: tencent_D477D9333D45FCB6BE69D15A7F254A6AEE06@qq.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi,

I would like to propose an experimental FULL JOIN rewrite for discussion:

A FULL JOIN B ON q
-> (A LEFT JOIN B ON q)
UNION ALL
(B ANTI JOIN A ON q)

The second arm supplies typed NULLs for A's columns and preserves the
original column order. This preserves duplicates without relying on a
non-nullable key to identify unmatched rows.

The motivation is to reuse LEFT and ANTI join planning, including for
conditions that native hash and merge full joins cannot handle. This RFC
focuses on correctness; performance has not been evaluated.

The rewrite runs before planner preprocessing. It adjusts Var references,
nullingrels and correlated references across the new subquery boundary.
Expressions above the original join, including WHERE, grouping, ordering
and limits, remain in the parent query.

The experimental enable_full_join_rewrite GUC defaults to off. When enabled,
it allows at most eight decomposition steps per statement. Each step
duplicates its inputs, and nested steps can duplicate already expanded
subtrees. There is currently no cost comparison with native FULL join paths.

The guards exclude volatile join conditions or inputs, CTE/LATERAL inputs,
RLS, security-barrier views, foreign tables and several other input shapes.
The foreign-table check includes inheritance and partition descendants,
while respecting ONLY. These are conservative limits of the prototype.

The series contains two patches:

Allow SubLink pull-up to encounter pre-existing ANTI joins, leaving their
ON SubLinks for expression preprocessing.

Add the rewrite, GUC, build integration and documentation.

The separate full_join_rewrite.sql attachment is a standalone correctness
test script, currently outside the regression suite. Run it with:

psql -X -f full_join_rewrite.sql

The file_fdw cases use server-side printf and run automatically when the
extension is available and the user is a superuser; otherwise they report
a skip.

In my local run, all 199 numbered cases executed, including RLS and FDW.
All 249 pass values were 1, including the 24 expected-error checks for
SQLSTATE 0A000. I also inspected the EXPLAIN output for the foreign-descendant
guards, ONLY cases and mixed eligible/ineligible joins.

I would appreciate feedback on the integration point and scope. Is an early
query-tree rewrite appropriate, or should this start as a fallback for
otherwise unplannable FULL joins, or as a costed alternative? Are there
semantic cases or preprocessing interactions that the guards still miss?

Regards,
ChenHui Mo

Attachment Content-Type Size
full_join_rewrite.sql application/octet-stream 209.5 KB
v1-0001-Handle-pre-existing-anti-joins-in-sublink-pull-up.patch application/octet-stream 1.9 KB
v1-0002-Add-an-opt-in-FULL-JOIN-decomposition-into-UNION-ALL.patch application/octet-stream 22.1 KB

Browse pgsql-hackers by date

  From Date Subject
Previous Message Nikolay Samokhvalov 2026-09-22 06:32:16 Re: Fix "unexpected logical decoding status change" error; from concurrent logical decoding activation