Skip to content

[CALCITE-7711] Add a rule to convert LEFT or RIGHT OUTER JOIN with IS NULL to ANTI JOIN - #5175

Open
zzwqqq wants to merge 2 commits into
apache:mainfrom
zzwqqq:outer_join_to_anti
Open

[CALCITE-7711] Add a rule to convert LEFT or RIGHT OUTER JOIN with IS NULL to ANTI JOIN#5175
zzwqqq wants to merge 2 commits into
apache:mainfrom
zzwqqq:outer_join_to_anti

Conversation

@zzwqqq

@zzwqqq zzwqqq commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Jira Link

CALCITE-7711

Changes Proposed

Add an optional rule for LEFT and RIGHT joins. The rule should match a top-level IS NULL conjunct on the null-generating input. The conversion is safe when the underlying input field is non-nullable, or when the join condition cannot be TRUE while that field is nullable. Other filter conjuncts and the original row type should be preserved.

Representing the operation as an ANTI join allows subsequent rules to produce physical anti joins and the planner to cost those alternatives.

*
* <p>is equivalent to
*
* <pre>{@code

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think having a plan before and after is more useful than having this equivalent SQL.
You can keep the SQL for the original query, though.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for reviewing. I've added a plan example.

}

/** Returns an {@code IS NULL} condition that identifies a null-generated
* outer-join row, or null if there is no such condition. */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition must be over an input field which is not nullable in the input.
That is implied by the JavaDoc, but is subtle, it deserves to be in the JavaDoc.
You can say rightField IS NULL where rightField is a non-nullable column from the right input (for a left join).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I've updated the Javadoc to describe the requirement.

@xiedeyantu xiedeyantu left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since Mihai has already reviewed it, I only raised a few minor issues.

import org.junit.jupiter.api.Test;

/** Tests for {@link OuterJoinToAntiJoinRule}. */
class OuterJoinToAntiJoinRuleTest {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can add JIRA title in the java doc.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I've added the title.

Comment thread core/src/test/resources/sql/planner.iq Outdated
!ok
!set planner-rules original

# OuterJoinToAntiJoinRule converts the null-generating side's IS NULL filter.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JIRA title also should be added here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I've added the title here too.

join.getCluster().getRexBuilder());
final RelTraitSet traitSet = join.getTraitSet();
final Join antiJoin =
join.copy(traitSet, condition, newLeft, newRight,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better to switch to RelBuilder.join?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Changed the rule to use RelBuilder.join and retain the original join hints. Correlated RIGHT joins are left unchanged because swapping the inputs would require remapping correlation references.

@sonarqubecloud

Copy link
Copy Markdown

@mihaibudiu

Copy link
Copy Markdown
Contributor

@xiedeyantu please approve if you are happy

@mihaibudiu mihaibudiu added the LGTM-will-merge-soon Overall PR looks OK. Only minor things left. label Aug 12, 2026
@mihaibudiu

Copy link
Copy Markdown
Contributor

next step: squashing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

LGTM-will-merge-soon Overall PR looks OK. Only minor things left.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants